This commit is contained in:
Zack Meier
2026-04-15 15:45:50 -05:00
commit 1d304511b8
613 changed files with 140998 additions and 0 deletions
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.All-General'
variables:
major: 1
minor: 0
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.All-General.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.All-General.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.All-General'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.All-General).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.All-General</id>
<version>$VERSIONHERE$</version>
<authors>Zack Meier</authors>
<description>QoL functions that don't fit into another module</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>
@@ -0,0 +1,11 @@
@{
RootModule = 'ITD.All-General.psm1'
ModuleVersion = '<ModuleVersion>'
GUID = '9c9219d5-d7be-49e5-8448-49c50df94eda'
Author = 'Zack Meier'
CompanyName = 'State of North Dakota'
Description = "QoL functions that don't fit into another module"
PowerShellVersion = '5.1'
CompatiblePSEditions = 'Desktop', 'Core'
FunctionsToExport = @(<FunctionsToExport>)
}
@@ -0,0 +1,23 @@
#Get public and private function definition files.
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in @($Public + $Private))
{
Try
{
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
# Here I might...
# Read in or create an initial config file and variable
# Export Public functions ($Public.BaseName) for WIP modules
# Set variables visible to the module and its functions only
Export-ModuleMember -Function $Public.Basename
@@ -0,0 +1,32 @@
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
$servers=@"
server1.xyz.com
servers2.xyz.com
@"
$servers = ConvertTo-Array -MultiLineString $servers
.EXAMPLE
Another example of how to use this cmdlet
#>
function ConvertTo-Array {
[CmdletBinding()]
Param
(
[string]
$MultiLineString
)
Begin {
}
Process {
$result = @($MultiLineString -split '[\r\n]+')
}
End {
return $result
}
}
@@ -0,0 +1,43 @@
<#
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
Test-MyTestFunction -Verbose
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
#>
function Get-SslCertificate {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$DNSName
)
process {
Write-Verbose "Checking certificate for $DNSName"
$tcp = [Net.Sockets.TcpClient]::new($DNSName, 443)
$ssl = [Net.Security.SslStream]::new(
$tcp.GetStream(),
$false,
{ $true }
)
$ssl.AuthenticateAsClient($DNSName)
$cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new(
$ssl.RemoteCertificate
)
$ssl.Dispose()
$tcp.Dispose()
$cert
}
}
@@ -0,0 +1,49 @@
<#
.Synopsis
Verify AD credentials are valid
.DESCRIPTION
Verify AD credentials are valid ##
.EXAMPLE
Test-ADCredential -Credential <PSCredential>
#>
function Test-ADCredential {
[CmdletBinding()]#
Param
(
[Parameter(Mandatory = $true)]
[PSCredential]
$Credential
)
Begin {
}
Process {
If ($Credential -eq $null) {
Write-Warning "Credentials empty"
$status = $true
}
Else {
$username = $Credential.username
$password = $Credential.GetNetworkCredential().password
$CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('domain')
#($ValidateCredential = ) | Out-Null
If ($DS.ValidateCredentials($UserName, $Password) -eq $false) {
$password = $null
Write-Error "Invalid credentials or locked account."
$status = $false
}
Else {
$status = $true
}
$password = $null
}
}
End {
}
}
@@ -0,0 +1,50 @@
<#
.SYNOPSIS
Removes old versions of ITD modules
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
.INPUTS
Inputs to this cmdlet (if any)
.OUTPUTS
Output from this cmdlet (if any)
.NOTES
General notes
.COMPONENT
The component this cmdlet belongs to
.ROLE
The role this cmdlet belongs to
.FUNCTIONALITY
The functionality that best describes this cmdlet
#>
function Uninstall-ITDModuleOldVersion {
[CmdletBinding()]
Param
(
)
begin {
}
process {
$InstalledModules = Get-InstalledModule -Name ITD.*
try {
$InstalledModules | ForEach-Object {
$CurrentVersion = $_.Version
Get-InstalledModule -Name $_.Name -AllVersions | Where-Object -Property Version -LT -Value $CurrentVersion
} | Uninstall-Module -Verbose
}
catch {
}
}
end {
}
}
@@ -0,0 +1,33 @@
<#
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
Test-MyTestFunction -Verbose
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
#>
function Update-ITDModule {
[CmdletBinding()]
param (
)
begin {
}
process {
$InstalledModules = Get-InstalledModule -Name ITD.*
Update-Module -Name $InstalledModules.Name
}
end {
}
}
@@ -0,0 +1,20 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
# Build and Test
TODO: Describe and show how to build your code and run the tests.
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.ITD-WindowsServer.FileManagement'
variables:
major: 0
minor: 7
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.ITD-WindowsServer.FileManagement.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.ITD-WindowsServer.FileManagement.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.ITD-WindowsServer.FileManagement'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.ITD-WindowsServer.FileManagement).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,21 @@
{
"ComputerName": "itdxyz.nd.gov",
"NotifyEmail": [
"emailA@nd.gov",
"emailB@nd.gov"
],
"Directory": [
{
"Path": "C:\\inetpub\\logs\\LogFiles",
"Extension": "log",
"DaysToKeep": 90,
"Recursive": true
},
{
"Path": "C:\\temp",
"Extension": "txt",
"DaysToKeep": 30,
"Recursive": false
}
]
}
@@ -0,0 +1,26 @@
{
"ComputerName": "itddohslimst2.nd.gov",
"NotifyEmail": [
"zmeier@nd.gov"
],
"Directory": [
{
"Path": "C:\\inetpub\\logs",
"Extension": "log",
"DaysToKeep": 120,
"Recursive": true
},
{
"Path": "C:\\Program Files\\STARLIMS\\STARLIMStst\\Log",
"Extension": "log",
"DaysToKeep": 120,
"Recursive": true
},
{
"Path": "E:\\Program Files\\STARLIMS\\STARLIMSdev\\Log",
"Extension": "log",
"DaysToKeep": 120,
"Recursive": true
}
]
}
@@ -0,0 +1,14 @@
{
"ComputerName": "itdernappt01.nd.gov",
"NotifyEmail": [
"zmeier@nd.gov"
],
"Directory": [
{
"Path": "C:\\inetpub\\logs",
"Extension": "log",
"DaysToKeep": 90,
"Recursive": true
}
]
}
@@ -0,0 +1,14 @@
{
"ComputerName": "itdernappu01.nd.gov",
"NotifyEmail": [
"zmeier@nd.gov"
],
"Directory": [
{
"Path": "C:\\inetpub\\logs",
"Extension": "log",
"DaysToKeep": 90,
"Recursive": true
}
]
}
@@ -0,0 +1,20 @@
{
"ComputerName": "itdscmt1.nd.gov",
"NotifyEmail": [
"zmeier@nd.gov"
],
"Directory": [
{
"Path": "C:\\inetpub\\logs",
"Extension": "log",
"DaysToKeep": 90,
"Recursive": true
},
{
"Path": "C:\\temp",
"Extension": "txt",
"DaysToKeep": 30,
"Recursive": false
}
]
}
@@ -0,0 +1,14 @@
{
"ComputerName": "itdvmvc1script.nd.gov",
"NotifyEmail": [
"zmeier@nd.gov"
],
"Directory": [
{
"Path": "C:\\inetpub\\logs\\LogFiles",
"Extension": "log",
"DaysToKeep": 90,
"Recursive": true
}
]
}
@@ -0,0 +1,14 @@
{
"ComputerName": "itdwinautot1.nd.gov",
"NotifyEmail": [
"zmeier@nd.gov"
],
"Directory": [
{
"Path": "C:\\temp",
"Extension": "txt",
"DaysToKeep": 30,
"Recursive": false
}
]
}
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.ITD-WindowsServer.FileManagement</id>
<version>$VERSIONHERE$</version>
<authors>Zack Meier</authors>
<description>Functions for Windows Server file management</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>
@@ -0,0 +1,132 @@
#
# Module manifest for module 'ITD.ITD-WindowsServer.FileManagement'
#
# Generated by: zmeier
#
# Generated on: 6/14/2022
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'ITD.ITD-WindowsServer.FileManagement.psm1'
# Version number of this module.
ModuleVersion = '<ModuleVersion>'
# Supported PSEditions
CompatiblePSEditions = 'Desktop', 'Core'
# ID used to uniquely identify this module
GUID = '85e7676b-f0e3-4908-aafa-25c9606ca8b7'
# Author of this module
Author = 'zmeier'
# Company or vendor of this module
CompanyName = 'State of North Dakota'
# Copyright statement for this module
Copyright = '(c) zmeier. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Functions for Windows Server file management'
# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# ClrVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(<FunctionsToExport>)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,23 @@
#Get public and private function definition files.
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in @($Public + $Private))
{
Try
{
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
# Here I might...
# Read in or create an initial config file and variable
# Export Public functions ($Public.BaseName) for WIP modules
# Set variables visible to the module and its functions only
Export-ModuleMember -Function $Public.Basename
@@ -0,0 +1,26 @@
[
{
"ComputerName": "itdscmt1.nd.gov",
"NotifyEmail": "zmeier@nd.gov",
"Directory": [
{
"Path": "C:\\inetpub\\logs\\LogFiles",
"DaysToKeep": 120,
"Extension": "log",
"Recursive": true
}
]
},
{
"ComputerName": "itdzmtest555.nd.gov",
"NotifyEmail": "zmeier@nd.gov",
"Directory": [
{
"Path": "C:\\windows\\temp",
"DaysToKeep": 99,
"Extension": "log",
"Recursive": false
}
]
}
]
@@ -0,0 +1,22 @@
- ComputerName: itdscmt1.nd.gov
Directory:
- Path: C:\windows\temp
DaysToKeep: 30
Extension: txt
Recursive: false
- ComputerName: itdsccmp2.nd.gov
Directory:
- Path: C:\windows\temp
DaysToKeep: 15
Extension: txt
Recursive: false
- Path: C:\inetpub\logs
DaysToKeep: 120
Extension: txt
Recursive: false
- ComputerName: itdzmtest555.nd.gov
Directory:
- Path: "C:\windows\temp",
DaysToKeep": 99,
Extension": "log",
Recursive": false
@@ -0,0 +1,14 @@
[
{
"Path": "C:\\temp",
"Extension": "log",
"DaysToKeep": 30,
"Recursive": true
},
{
"Path": "C:\\temp2",
"Extension": "txt",
"DaysToKeep": 45,
"Recursive": false
}
]
@@ -0,0 +1,89 @@
<#
.SYNOPSIS
Script will discover all files that are considered "expired".
.DESCRIPTION
Script will discover all files that are considered "expired" based on the parameters in the ./Helpers/ITDExpiredFiles.json files in the ITD-WindowsServer module
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
.EXAMPLE
Get-ITDExpiredFiles
.EXAMPLE
Get-ITDExpiredFiles -Credential $PrvCred
.EXAMPLE
Get-ITDExpiredFiles -ComputerName itdxyz.nd.gov -Credential $PrvCred
#>
function Get-ITDExpiredFiles {
[CmdletBinding()]
param (
[string]
$ComputerName,
[PSCredential]
$Credential
)
Begin {
Write-Verbose -Message "Load json files into memory"
$JsonFiles = Get-ChildItem -Path "$PSScriptRoot\..\Helpers\*.json" | Where-Object Name -NE ^template.json
$MachineInfo = ForEach ($file in $JsonFiles) {
Get-Content -Path $file.FullName | ConvertFrom-Json
}
If ($PSBoundParameters.ContainsKey('ComputerName')) {
Write-Verbose -Message "ComputerName found"
$MachineInfo = $MachineInfo | Where-Object ComputerName -EQ $ComputerName
}
}
Process {
Write-Verbose -Message "Prep discovery function"
$GetExpiredFilesFunc = {
Write-Verbose -Message ($env:COMPUTERNAME + " " + $args[0] + " " + $args[1] + " " + $args[2])
$GetChildItemParams = @{
Path = $args[0];
Filter = $args[1];
Recurse = $args[2];
}
$FilesFound = (Get-ChildItem @GetChildItemParams | Where-Object LastWriteTime -LT ((Get-Date).AddDays(-$args[3])))
Write-Output $FilesFound
}
$GetITDExpiredFilesResult = [System.Collections.ArrayList]@()
ForEach ($Server in $MachineInfo) {
Write-Verbose -Message ("Start " + $Server.ComputerName)
Write-Verbose -Message "Ping test before any Invoke-Command"
If ((Test-NetConnection -ComputerName $Server.ComputerName).PingSucceeded) {
ForEach ($Directory in $Server.Directory) {
Write-Verbose -Message ("Start " + $server.ComputerName + " " + $Directory.Path)
$FilesFoundOnServer = $null
$InvokeCommandParams = $null
$InvokeResult = $null
$InvokeCommandParams = @{
ComputerName = $Server.ComputerName;
Credential = $Credential;
ScriptBlock = $GetExpiredFilesFunc;
ArgumentList = @($Directory.Path, ("*" + $Directory.Extension), $Directory.Recursive, $Directory.DaysToKeep);
}
$FilesFoundOnServer = Invoke-Command @InvokeCommandParams
Write-Output $FilesFoundOnServer
#$null = $GetITDExpiredFilesResult.Add($FilesFoundOnServer)
Write-Verbose -Message ("End " + $server.ComputerName + " " + $Directory.Path)
}
}
Else {
Write-Error -Message ($Server.ComputerName + " ping test failed, generate ticket someday.")
}
}
Write-Verbose -Message ("End " + $server.ComputerName)
}
End {
#Write-Output $GetITDExpiredFilesResult
Write-Verbose -Message "End Get-ITDExpiredFiles"
}
}
@@ -0,0 +1,99 @@
function Remove-ITDExpiredFiles {
[CmdletBinding()]
param (
[string]
$ComputerName,
[switch]
$WhatIf,
[PSCredential]
$Credential
)
Begin {
Write-Verbose -Message "Start Remove-ITDExpiredFiles"
}
Process {
$FilesRemovedSuccess = @()
$FilesRemovedFailure = @()
$GetITDExpiredFilesAutoParams += @{}
If ($PSBoundParameters.ContainsKey('ComputerName')) {
Write-Verbose -Message "ComputerName parameter found"
$GetITDExpiredFilesParams += @{
ComputerName = $ComputerName;
}
}
If ($PSBoundParameters.ContainsKey('Credential')) {
Write-Verbose -Message "Credential parameter found"
$GetITDExpiredFilesParams += @{
Credential = $Credential;
}
}
$FilesToRemove = Get-ITDExpiredFiles @GetITDExpiredFilesParams
Write-Verbose -Message ("Found " + $FilesToRemove.count + " expired files to remove")
ForEach ($File in $FilesToRemove) {
Write-Verbose -Message ("Start~" + $File.PSComputerName + "~" + $File.FullName )
$InvokeCommandParams = @{
ComputerName = $File.PSComputerName;
Credential = $Credential;
ErrorAction = 'Stop';
ArgumentList = @($File.FullName);
ScriptBlock = { Get-Item -Path $args[0] | Remove-Item }
}
switch ($WhatIf) {
$true {
try {
Write-Verbose -Message ("Process~" + $File.PSComputerName + "~" + $File.FullName + " removed")
Write-Host -Message ($Server.ComputerName + " -- " + 'What if: Performing the operation "Remove File" on target ' + $File.FullName)
# log success
$FilesRemovedSuccess += [PSCustomObject]@{
ComputerName = $File.PSComputerName;
Name = $File.Fullname;
Timestamp = (Get-Date).tostring("yyyy/MM/dd HH:mm:ss")
}
Write-Output $File
}
catch {
Write-Verbose -Message ("Process~" + $File.PSComputerName + "~" + $File.FullName + " failure")
# log failure
$FilesRemovedFailure += [PSCustomObject]@{
ComputerName = $File.PSComputerName;
Name = $File.Fullname;
Timestamp = (Get-Date).tostring("yyyy/MM/dd HH:mm:ss")
}
}
}
Default {
try {
Invoke-Command @InvokeCommandParams
Write-Verbose -Message ("Process~" + $File.PSComputerName + "~" + $File.FullName + " removed")
# log success
$FilesRemovedSuccess += [PSCustomObject]@{
ComputerName = $File.PSComputerName;
Name = $File.Fullname;
Timestamp = (Get-Date).tostring("yyyy/MM/dd HH:mm:ss")
}
Write-Output $File
}
catch {
Write-Verbose -Message ("Start~" + $File.PSComputerName + "~" + $File.FullName + " failure")
# log failure
$FilesRemovedFailure += [PSCustomObject]@{
ComputerName = $File.PSComputerName;
Name = $File.Fullname;
Timestamp = (Get-Date).tostring("yyyy/MM/dd HH:mm:ss")
}
}
}
}
Write-Verbose -Message ("End~" + $File.PSComputerName + "~" + $File.FullName )
}
}
End {
Write-Verbose -Message "End Remove-ITDExpiredFilesAuto"
}
}
@@ -0,0 +1,20 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
# Build and Test
TODO: Describe and show how to build your code and run the tests.
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.ITD-WindowsServer.General'
variables:
major: 0
minor: 7
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.ITD-WindowsServer.General.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.ITD-WindowsServer.General.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.ITD-WindowsServer.General'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.ITD-WindowsServer.General).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,93 @@
<#
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
Test-MyTestFunction -Verbose
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
#>
function New-ITDWindowsVM {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[ValidateSet('VMware', 'Azure')]
[string]
$Platform,
[Parameter(Mandatory = $true)]
[string]
$ComputerName,
[Parameter(Mandatory = $true)]
[int]
$CPU,
[Parameter(Mandatory = $true)]
[int]
$MemoryGB,
[Parameter(Mandatory = $true)]
[int]
$DiskOS,
[Parameter(Mandatory = $true)]
[int]
$DiskSwap,
[int]
$DiskData = 0,
[Parameter(Mandatory = $true)] # this will decide Azure subscription
[string]
$Subnet,
[Parameter(Mandatory = $true)]
[string]
$OS,
[Parameter(Mandatory = $true)]
[string]
$Environment,
[Parameter(Mandatory = $true)]
[string]
$Datacenter,
[Parameter(Mandatory = $true)]
[string]
$AppName,
[Parameter(Mandatory = $true)]
[string]
$LicensingRestrictions,
[Parameter(Mandatory = $true)]
[PSCredential]
$Credential
)
begin {
}
process {
switch ($Platform) {
'VMware' {
# New-ITDWindowsVmVmware
}
'Azure' {
# New-ITDWindowsVmAzure
}
}
}
end {
}
}
@@ -0,0 +1,694 @@
<# ################
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
$NewVMWindowsAzureParams = @{
ComputerName = 'itdzmtest701.nd.gov';
ResourceGroupNameOverride = 'rg-shared-iis-tst';
AvailabilityZone = 2;
CPU = 2;
MemoryGB = 16;
DiskOsGB = 128;
DiskDataGB = 0;
Subnet = '10.21.8.0/22';
OS = 'Windows Server 2022 Datacenter';
Environment = 'Test';
AppName = 'ITD-POC-zmeier';
LicensingRestrictions = "No Licensing Restrictions";
}
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
#>
function New-ITDWindowsVmAzure {
[CmdletBinding()]
param (
[string]
$ComputerName,
[string]
$ResourceGroupNameOverride,
[string]
$AppName,
[ValidateSet(1, 2, 3)]
[int]
$AvailabilityZone,
[int]
$CPU,
[int]
$MemoryGB,
[int]
$DiskOsGB,
[int]
$DiskDataGB,
[string]
$Subnet,
[string]
$OS,
[string]
$Environment,
#[string]
#$Subscription,
[string]
$LicensingRestrictions,
[PSCredential]
$Credential
)
begin {
}
process {
$ComputerName = $ComputerName.ToLower()
$FQDN = $ComputerName
$Hostname = $FQDN.split('.')[0]
Write-Verbose -Message "Prepare Connections"
#$tenantId = '2dea0464-da51-4a88-bae2-b3db94bc0c54'
#$AppId = '60244573-7130-4026-9c6d-47de73f8ca29'
#$SecureStringPwd = '' #$Secret:AzureVMServicePrincipal #Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx
#$SecureStringPwd = 'Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx'
#$PSCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, ($SecureStringPwd | ConvertTo-SecureString -AsPlainText -Force)
#Connect-AzAccount -ServicePrincipal -Credential $PSCredential -Tenant $tenantId
Write-Verbose -Message "Prepare Credentials"
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one"
Clear-DnsClientCache
$Cidr = $Subnet
[Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -Server 10.10.10.10 -ErrorAction SilentlyContinue).IPAddress
$SubnetMaskInt = $CIDR.split('/')[1]
$Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2))
[Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(),
([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(),
([math]::Truncate(($Int64 % 65536) / 256)).ToString(),
([math]::Truncate($Int64 % 256)).ToString()
$IPSplit = $Subnet.Split('.')
[Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) )
If ($null -ne $IpAddress) {
If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) {
Write-Warning "DNS record already exists, CIDR Block match"
}
Else {
Write-Error "DNS record already exists, and does not match CIDR Block"
Break
}
}
Else {
Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record."
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
Start-Sleep -Seconds 5
Write-Verbose -Message ("FQDN is " + $FQDN)
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
#[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress
[Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address
If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) {
Write-Error "IP Address already in use." -ErrorAction Stop
}
}
Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it."
If ($FQDN -like "itdcnd*") {
$PasswordStateList = "Peoplesoft Share PW"
}
Else {
$PasswordStateList = "CSRC"
}
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential -ErrorAction SilentlyContinue
If ($GuestVMLocalCredential) {
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
}
Else {
Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials"
$GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential
}
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
Write-Verbose -Message "Determine environment"
switch ($Environment) {
{ $_ -eq 'Test' -or $_ -eq 'Development' } {
Write-Verbose -Message "Environment is Test or Development"
$EnvShortString = 'tst'
}
'Production' {
Write-Verbose -Message "Environment is Production"
$EnvShortString = 'prd'
}
}
Write-Verbose -Message "Determine subscription, via Subnet"
$AllSubscriptions = Get-AzSubscription
$AllAzVirtualNetworks = ForEach ($Subscription in $AllSubscriptions) {
Set-AzContext -SubscriptionObject $Subscription | Out-Null
Get-AzVirtualNetwork | ForEach-Object {
[PSCustomObject]@{
Subscription = $Subscription.Name;
VirtualNetwork = $_
}
}
}
$Subscription = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).Subscription
$VirtualNetwork = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).VirtualNetwork
$VirtualNetworkSubnet = $VirtualNetwork.Subnets | Where-Object { $_.AddressPrefix -match $Subnet }
$VNetName = $VirtualNetwork.Name
$VNetSubnet = $VirtualNetworkSubnet.Name
switch ($OS) {
"Windows Server 2019 Datacenter" {
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "WindowsServer"
$sku = "2019-Datacenter"
}
"Windows Server 2022 Datacenter" {
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "WindowsServer"
$sku = "2022-datacenter"
}
Default { Write-Error "Invalid operating system" -ErrorAction Stop }
}
# finalize VM location and size
$location = "centralus"
$VMSizeFilter1 = @(
"Standard_D2ds_v5",
"Standard_D4ds_v5",
"Standard_D8ds_v5",
"Standard_D16ds_v5",
"Standard_D32ds_v5",
"Standard_E2ds_v5",
"Standard_E4ds_v5",
"Standard_E8ds_v5",
"Standard_E16ds_v5",
"Standard_E20ds_v5",
"Standard_E32ds_v5",
"Standard_F2s_v2",
"Standard_F4s_v2",
"Standard_F8s_v2",
"Standard_F16s_v2",
"Standard_F32s_v2"
)
$VMSize = Get-AzVMSize -Location centralus | `
Where-Object { $VMSizeFilter1 -contains $_.Name } | `
Where-Object { $_.NumberOfCores -ge $CPU -and $_.MemoryInMB -ge ($Memory * 1024) } | `
Where-Object Name -NotMatch "_Promo" | `
Sort-Object NumberOfCores, MemoryInMB | `
Select-Object -First 1
Write-Verbose -Message "Determine ResourceGroupName, and create Resource Group if needed"
If ($PSBoundParameters.ContainsKey('ResourceGroupNameOverride')) {
# use the name in the variable
Write-Verbose -Message "ResourceGroupName parameter found, is $ResourceGroupNameOverride"
$ResourceGroupName = $ResourceGroupNameOverride
}
Else {
Write-Verbose -Message "ResourceGroupName parameter not found, determine now"
# if name is not given, determine the name
$VMOwner = $AppName.split('-')[0].ToLower()
$VMFunction = (($AppName -replace "$VMOwner-") -replace "-").ToLower() -replace " "
$ResourceGroupName = "rg-$VMOwner-$VMFunction-$EnvShortString"
}
Write-Verbose -Message "ResourceGroupName is $ResourceGroupName"
Write-Verbose -Message "Change to selected subscription and validate resource group exists"
Set-AzContext $Subscription | Out-Null
$ResGroupExist = $null
$ResGroupExist = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
If (@($ResGroupExist).count -gt 1) {
Write-Error "Multiple Resource Groups matched" -ErrorAction Stop
}
If (@($ResGroupExist).count -lt 1) {
Write-Warning "No matching resource group found, creating it now"
New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Tag @{ApplicationName = $AppName }
}
If (@($ResGroupExist).count -eq 1) {
Write-Verbose -Message "Exactly one matching resource group found"
}
$VMName = "vm-$HostName-$EnvShortString"
$VMIPConfigName = "ipconfig-$HostName-$EnvShortString"
$VMNicName = "nic-$HostName-$EnvShortString"
$VMObjectName = "vm-$HostName-$EnvShortString"
$VMOsDiskName = "vm-$HostName-os-$EnvShortString"
Write-Verbose -Message "Verifying Applicable Inputs are Lowercase"
$VMIPConfigName = $VMIPConfigName.ToLower()
$VMNicName = $VMNicName.ToLower()
$VMObjectName = $VMObjectName.ToLower()
$VMOsDiskName = $VMOSDiskName.ToLower()
Write-Verbose -Message "Creating IPConfig and NIC"
$IPConfig = New-AzNetworkInterfaceIpConfig -Name $VMIPConfigName -PrivateIpAddress $IPAddress.IPAddressToString -PrivateIpAddressVersion IPv4 -Subnet $VirtualNetworkSubnet
$VMNIC = New-AzNetworkInterface -IpConfigurationName $IPConfig -Location $Location -Name $VMNICName -ResourceGroupName $ResourceGroupName -Subnet $VirtualNetworkSubnet -Force
$VMNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
$VMNIC.IpConfigurations[0].PrivateIpAddress = $IPAddress.IPAddressToString
Set-AzNetworkInterface -NetworkInterface $VMNIC
Write-Verbose -Message "Build VM Config"
$vmConfigParams = @{
VMName = $VMObjectName
VMSize = $VMSize.Name
}
If ($PSBoundParameters.ContainsKey('AvailabilityZone')) {
Write-Verbose -Message "AvailabilityZone parameter found, adding to vmConfigParams"
$vmConfigParams += @{
Zone = $AvailabilityZone
}
}
$vmConfig = New-AzVMConfig @vmConfigParams
$vmConfig | Set-AzVMOSDisk -Name $VMOSDiskName -CreateOption FromImage
If ($VMOS -eq "Windows") {
$vmConfig | Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $GuestVMLocalCredential
$vmConfig | Set-AzVMSourceImage -PublisherName $Publisher -Offer $Offer -Skus $Sku -Version latest
$vmConfig.OSProfile.ComputerName = $HostName
}
If ($VMOS -eq "Linux") {
$vmConfig | Set-AzVMOperatingSystem -Linux -ComputerName $VMFQDN -Credential $GuestVMLocalCredential
Switch ($VMSubscription) {
"npd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/76297098-764c-43de-8525-c9fda1b237be/resourceGroups/rg-infra-templates-tst-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-103" }
"infra01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/e53aa0c7-824d-40a2-b420-4ab77b1051d2/resourceGroups/rg-infra-templates-prd-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-403" }
"prd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/437b2bfa-850e-4464-b6c2-38a68cda7c69/resourceGroups/rg-infra-templates-prd-002/providers/Microsoft.Compute/images/vm-rhel74template-prd-003" }
}
}
$vmConfig | Add-AzVMNetworkInterface -Id $VMNIC.ID
Set-AzVMBootDiagnostic -VM $vmConfig -Enable -ResourceGroupName $ResourceGroupName
Write-Verbose "Creating VM"
New-AzVM -VM $vmConfig -ResourceGroupName $ResourceGroupName -Location $Location -DisableBginfoExtension -LicenseType "Windows_Server" #-AsJob
Start-Sleep -Seconds 60
$VM = Get-AzVM -Name $VMObjectName -ResourceGroupName $ResourceGroupName
If ($PSBoundParameters.ContainsKey("DiskDataGB")) {
$ExistingDisks = @($VM.StorageProfile.DataDisks | Select-Object *, @{n = 'ItdId'; e = { [int]($_.Name -replace "vm-$hostname-app-$environment-") } })
$NewDiskItdIdInt = ($ExistingDisks | Sort-Object ItdId -Descending | Select-Object -First 1).ItdId + 1
$NewDiskItdIdStr = $NewDiskItdIdInt.ToString("000")
$NewDiskName = "vm-$Hostname-app-$EnvShortString-$NewDiskItdIdStr" #vm-itduc4p1-app-tst-001
$LunID = ($ExistingDisks | Sort-Object Lun -Descending | Select-Object -First 1).Lun + 1
$count = 0
If ($ExistingDisks) {
while ($Size -match $ExistingDisks.DiskSizeGB) {
$count++
Write-Verbose -Message "SizeGB: $Size, Count: $count"
If ($count -ge 11) {
Write-Error "Disk size not available" -ErrorAction Stop
}
Else {
$Size = $Size - 1
}
}
}
Write-Verbose -Message "SizeGB: $Size, Count: $count"
$AzureRmDiskConfigParams = @{
DiskSizeGB = $DiskDataGB
Location = $Location
CreateOption = "Empty"
SkuName = "Premium_LRS"
}
If ($Zone) {
Write-Verbose "VM is located in Zone $Zone"
$AzureRmDiskConfigParams += @{Zone = $Zone }
}
#$DiskConfig = New-AzureRmDiskConfig -DiskSizeGB $Size -Location $Location -CreateOption Empty -SkuName Premium_LRS
$DiskConfig = New-AzDiskConfig @AzureRmDiskConfigParams
If (!(Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName -ErrorAction SilentlyContinue)) {
$NewDisk = New-AzDisk -DiskName $NewDiskName -Disk $DiskConfig -ResourceGroupName $ResourceGroupName
$VM = Add-AzVMDataDisk -Name $NewDiskName -CreateOption Attach -ManagedDiskId $NewDisk.Id -VM $VM -Lun $LunID -Caching ReadOnly
Update-AzVM -VM $VM -ResourceGroupName $ResourceGroupName -AsJob
}
}
Write-Verbose -Message "Wait two minutes before Pre-Firewall Guest OS customization"
Start-Sleep -Seconds 120
Write-Verbose -Message "Begin Pre-Firewall Guest OS customization"
$InvokeAzVMRunCommandParams = @{
ResourceGroupName = $ResourceGroupName;
Name = $VMName;
CommandId = 'RunPowerShellScript'
}
Write-Verbose -Message "1-Set WMI Tags"
$ScriptBlock = {
try {
Write-Verbose "Create new Class"
$Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);
$Class["__CLASS"] = "ITD";
$Class.Qualifiers.Add("Static", $true)
$Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false)
$Class.Properties["MyKey"].Qualifiers.Add("Key", $true)
$Class.Properties.Add("LastModified", [System.Management.CimType]::String, $false)
$Class.Properties.Add("DTAP", [System.Management.CimType]::String, $false)
$Class.Properties.Add("Baseline", [System.Management.CimType]::String, $false)
$Class.Put()
Write-Verbose "Create single ITD Object"
Set-WmiInstance -Class ITD -Arguments @{LastModified = (Get-Date); DTAP = "Prod"; Baseline = "000" }
}
catch {
Throw $_
Break
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "3-Disk Configuration"
$ScriptBlock = {
try {
# Non-initialized and MBR-initialized disks will have 0 partitions by default, but GPT-initialized disks will have 1 system reserved partition by default
$disks = Get-Disk | Where-Object { $_.NumberOfPartitions -eq 0 -or ( $_.PartitionStyle -eq 'GPT' -and $_.NumberOfPartitions -eq 1 ) } | Sort-Object -Property Number
if ($disks) {
Write-Verbose -Message "Found $(@($disks).Count) unpartitioned disks."
# Prevent the "You must format this partition before using it." popup
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Stop-Service ShellHWDetection -ErrorAction SilentlyContinue }
foreach ($disk in $disks) {
if ($disk.IsOffline) {
Set-Disk $disk.Number -IsOffline $false
Write-Verbose -Message "Brought disk $($disk.Number)($("{0:n0}GB" -f ($disk.Size / 1GB))) online..."
}
if ($disk.IsReadOnly) { Set-Disk $disk.Number -IsReadOnly $false }
if ($disk.PartitionStyle -eq 'RAW') { Initialize-Disk $disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue }
$diskParam = @{
FileSystem = 'NTFS'
Confirm = $false
}
$driveLetter = [Int][Char]'D'
while (Get-Volume -DriveLetter $([Char]$driveLetter) -ErrorAction SilentlyContinue) {
$driveLetter++
}
$diskParam.DriveLetter = [Char]$driveLetter
if (@($disks).IndexOf($disk) -eq 0 -and (-not (Get-Volume -DriveLetter D -ErrorAction SilentlyContinue))) {
$diskParam.NewFileSystemLabel = 'Temporary Storage'
}
elseif (@($disks).IndexOf($disk) -eq 1 -and (-not (Get-Volume -DriveLetter E -ErrorAction SilentlyContinue))) {
$diskParam.NewFileSystemLabel = 'Data'
}
[void](New-Partition -DiskNumber $disk.Number -DriveLetter $diskParam.DriveLetter -UseMaximumSize)
[void](Format-Volume @diskParam)
}
}
else {
Write-Verbose -Message "No unpartitioned disks found, continuing..."
}
}
catch {
Throw $_
Break
}
finally {
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Start-Service ShellHWDetection -ErrorAction SilentlyContinue }
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "5-Time Zone"
$ScriptBlock = {
if ((Get-TimeZone).Id -ne 'Central Standard Time') {
Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time."
Set-TimeZone -Id 'Central Standard Time'
Write-Verbose -Message "Time zone set to Central Standard Time."
}
else {
Write-Verbose -Message "Time zone is already set to Central Standard Time."
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "6-Enable Performance Counters"
$ScriptBlock = {
# Enable Performance Counters
try {
if (Get-ScheduledTask -TaskName "Server Manager Performance Monitor" | Where-Object State -NE "Running" -ErrorAction SilentlyContinue) {
Enable-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Server Manager Performance Monitor" | Start-ScheduledTask
Write-Verbose -Message "Performance monitors enabled."
}
else {
Write-Verbose -Message "Performance monitors already enabled, continuing..."
}
}
catch {
Throw $_
Break
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "7-Disable Windows Firewall"
$ScriptBlock = {
# Disable Windows Firewall
Write-Verbose -Message "Checking for active Windows Firewall..."
if ((Get-NetFirewallProfile).Enabled -contains 'True') {
Write-Verbose -Message "Windows Firewall is still enabled, disabling it..."
Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False
Write-Verbose -Message "Windows Firewall disabled."
}
else {
Write-Verbose -Message "Windows Firewall already disabled, continuing..."
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
# determine Active Directory Forest and OU
Write-Verbose -Message "8a-Determine domain join"
$DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1)
switch ($DomainName) {
'nd.gov' {
$SearchBaseDomain = "dc=nd,dc=gov"
}
'ndcloud.gov' {
$SearchBaseDomain = "dc=ndcloud,dc=gov"
}
}
If ($DomainName -eq "nd.gov") {
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq $AppName }
If (!($OUAppName)) {
$OUAppName = Get-ADOrganizationalUnit -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq 'All-General' }
}
$ExistingADComputer = Get-ADComputer -Filter { Name -eq $Hostname }
If ($ExistingADComputer) {
If ($ExistingADComputer.DistinguishedName -like ("*" + $AppName + "*") -or $ExistingADComputer.DistinguishedName -like "*All-General*") {
Write-Warning "AD object already exists, OU path does match"
$OuFinal = $ExistingADComputer.DistinguishedName -replace '^.+?(?<!\\),', ''
}
Else {
Write-Error "AD object already exists, OU path mismatch"
Exit
Exit
}
}
Else {
switch ($Environment) {
'Test' {
If ($AppName -like "Shared-Peoplesoft*") { $EnvString = "Non-Prod" }
Else { $EnvString = "Test" }
$OuAppNameEnv = Get-ADOrganizationalUnit -SearchBase $OUAppName.DistinguishedName -Filter * | Where-Object Name -EQ "$EnvString"
}
'Production' {
If ($AppName -like "Shared-Peoplesoft*") { $EnvString = "Prod" }
Else { $EnvString = "Prod" }
$OuAppNameEnv = Get-ADOrganizationalUnit -SearchBase $OUAppName.DistinguishedName -Filter * | Where-Object Name -EQ "$EnvString"
}
}
If ($OuAppNameEnv) { $OUFinal = $OUAppNameEnv.DistinguishedName }
Else { $OuFinal = $OUAppName.DistinguishedName }
}
}
# wait for firewall and reconnect
Write-Verbose -Message "8b-Verify if firewall is open before domain join attempt"
$connectivity = $false
while ($connectivity -eq $false) {
If (Test-NetConnection -ComputerName $IpAddress) {
Write-Verbose -Message "Ping successful" -Verbose
$connectivity = $true
}
Else {
Write-Verbose -Message "Ping failed, may need to wait for PA IP sync" -Verbose
Start-Sleep -Seconds 60
}
}
Write-Verbose -Message "8c-Attempt domain join"
switch ($DomainName) {
'nd.gov' {
Write-Verbose -Message "Attempting domain join nd.gov"
Write-Verbose -Message "Domain: $DomainName"
Write-Verbose -Message "OuPath: $OuFinal"
<#Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptPath 'C:\AzWinBuild\8-Domain-ndgov.ps1' -Parameter @{
"DomainName" = 'nd.gov';
"OuPath" = "$OuFinal"
}#>
$ScriptBlock = {
Param(
[string]
$DomainName,
[string]
$OuPath
)
Write-Host $OuPath
Test-NetConnection -ComputerName nd.gov
$DomainJoinCred = New-Object System.Management.Automation.PSCredential('ndgov\svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force))
Add-Computer -DomainName $DomainName -OUPath $OuPath -Credential $DomainJoinCred
Restart-Computer -Force
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock -Parameter @{
"DomainName" = 'nd.gov';
"OuPath" = "$OuFinal"
}
}
}
Start-Sleep -Seconds 120
Write-Verbose -Message "10a-Copy SCCM client from itdsccmp2.nd.gov" -Verbose
$ScriptBlock = { #### NEEDS WORK
Param(
)
Copy-Item -path "\\itdsccmp2.nd.gov\SCCM_Client\Client\" -Destination C:\temp\SCCM_Client -Recurse
}
$CopySuccess = $false
$CopyAttempts = 0
While ($CopySuccess -eq $false -and $CopyAttempts -lt 100) {
$Attempts++
$InvokeResult = Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
If ($InvokeResult.value.message -like "Copy-Item : Cannot find path*") {
Write-Verbose -Message "SCCM Client file copy failed. Looping until it works." -Verbose
$CopySuccess = $false
}
Else {
Write-Verbose -Message "SCCM Client file copy success." -Verbose
$CopySuccess = $true
}
}
# Check if SCCM automatically installed the SCCM client and registered it
$CcmRegistered = $false
$CcmRegistration = Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString {
Get-Content C:\Windows\CCM\Logs\ClientIDManagerStartup.log | Select-String RegTask
}
If ($CcmRegistration.value.message -match "Client is registered") {
Write-Verbose "Client is registered."
$CcmRegistered = $true
}
ElseIf ($CcmRegistration.value.message -match "Client is already registered") {
Write-Verbose "Client is already registered."
$CcmRegistered = $true
}
If ($CcmRegistered -eq $false) {
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString {
If (Get-Process -Name ccmsetup -ErrorAction SilentlyContinue) {
Write-Warning "CCM client is already installing"
$CcmRegistered = $true
}
ElseIf (Get-Process -Name ccmexec -ErrorAction SilentlyContinue) {
Write-Warning "CCM client is already installed"
$CcmRegistered = $true
}
Else {
Write-Warning -Message "Installing SCCM Client..."
Invoke-Expression -Command "C:\temp\SCCM_Client\ccmsetup.exe SMSSITECODE=ITD SMSMP=itdsccmp2.nd.gov DNSSUFFIX=nd.gov"
}
}
}
Write-Verbose -Message "[$FQDN]:Approve SCCM Client"
#Start-Sleep -Seconds 30 # ADD LOOP/SMARTS TO WAIT FOR DISCOVERY AND ANOTHER FOR APPROVAL
Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock {
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
$PSDrives = Get-PSDrive
If ($PSDrives | Where-Object Name -EQ "ITD") {
# ITD Drive exists, do nothing
}
else {
New-PSDrive -Name "ITD" -PSProvider AdminUI.PS.Provider\CMSite -Root itdsccmp2.nd.gov
}
Set-Location ITD:\
$Device = Get-CMDevice -Name $args[0]
If ($Device.IsApproved -eq 0) {
Approve-CMDevice -DeviceName $args[0]
}
}
}
end {
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.ITD-WindowsServer.General</id>
<version>$VERSIONHERE$</version>
<authors>Zack Meier</authors>
<description>Functions for Windows Server General administration</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>
@@ -0,0 +1,132 @@
#
# Module manifest for module 'ITD.ITD-WindowsServer.General'
#
# Generated by: zmeier
#
# Generated on: 6/14/2022
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'ITD.ITD-WindowsServer.General.psm1'
# Version number of this module.
ModuleVersion = '<ModuleVersion>'
# Supported PSEditions
CompatiblePSEditions = 'Desktop', 'Core'
# ID used to uniquely identify this module
GUID = '3bb063c4-d6c3-4775-8f25-4bdf31fe4b05'
# Author of this module
Author = 'zmeier'
# Company or vendor of this module
CompanyName = 'State of North Dakota'
# Copyright statement for this module
Copyright = '(c) zmeier. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Functions for Windows Server general administration'
# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# ClrVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(<FunctionsToExport>)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,23 @@
#Get public and private function definition files.
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in @($Public + $Private))
{
Try
{
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
# Here I might...
# Read in or create an initial config file and variable
# Export Public functions ($Public.BaseName) for WIP modules
# Set variables visible to the module and its functions only
Export-ModuleMember -Function $Public.Basename
@@ -0,0 +1,154 @@
<#
.SYNOPSIS
Generates a Certificate Signing Request based on values inputted. Any values not inputted will result in the use of default values.
.DESCRIPTION
Generates a Certificate Signing Request based on values inputted. Any values not inputted will result in the use of default values. CSR will be printed to the screen, but can be saved to the clipboard, or to a file.
Default values are:
.NOTES
Run as administrator is required.
.EXAMPLE
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov'
CSR is generated using the common name shown, and default values for everything else
.EXAMPLE
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -Organization "OrgNameHere" -OrganizationalUnit "OrgUnitHere" -Locality Mandan -State ND -Country US -KeyLength 4096
CSR is generated using the values specified, defaults for the rest
.EXAMPLE
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -Organization "OrgNameHere" -OrganizationalUnit "OrgUnitHere" -Locality Mandan -State ND -Country US -KeyLength 4096 -ToClipboard
CSR is generated using the values specified, defaults for the rest, and saved into the user's clipboard
.EXAMPLE
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -ToPath C:\temp.csr
CSR is generated using the common name shown, and default values for everything else, and saves the CSR to a local path
#>
function New-ITDSslCertificateSigningRequest {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]
$CommonName,
[string]
$Organization = "State of North Dakota",
[string]
$OrganizationalUnit = "NDIT",
[string]
$Locality = "Bismarck",
[string]
$State = "ND",
[string]
$Country = "US",
[ValidateSet(2048, 4096)]
[int]
$KeyLength = 4096,
[switch]
$Exportable = $true,
[ValidateSet('sha256','sha384','sha512','md5')]
[string]
$HashAlgorithm = "sha256",
[switch]
$ToClipboard,
[string]
$ToPath
)
begin {
if (-NOT([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Host "Administrator priviliges are required. Please restart this script with elevated rights." -ForegroundColor Red
Pause
Throw "Administrator priviliges are required. Please restart this script with elevated rights."
}
}
process {
$UID = [guid]::NewGuid()
$files = @{}
$files['settings'] = "$($env:TEMP)\$($UID)-settings.inf";
$files['csr'] = "$($env:TEMP)\$($UID)-csr.req"
$request = @{}
$request['SAN'] = @{}
#2048, sha256
$settingsInf = "
[Version]
Signature=`"`$Windows NT`$
[NewRequest]
KeyLength = {{KeyLength}}
Exportable = {{Exportable}}
MachineKeySet = TRUE
SMIME = FALSE
RequestType = PKCS10
ProviderName = `"Microsoft RSA SChannel Cryptographic Provider`"
ProviderType = 12
HashAlgorithm = {{HashAlgorithm}}
;Variables
Subject = `"CN={{CN}},OU={{OU}},O={{O}},L={{L}},S={{S}},C={{C}}`"
[Extensions]
{{SAN}}
;Certreq info
;http://technet.microsoft.com/en-us/library/dn296456.aspx
;CSR Decoder
;https://certlogik.com/decoder/
;https://ssltools.websecurity.symantec.com/checker/views/csrCheck.jsp
"
$request['SAN_string'] = & {
if ($request['SAN'].Count -gt 0) {
$san = "2.5.29.17 = `"{text}`"
"
Foreach ($sanItem In $request['SAN'].Values) {
$san += "_continue_ = `"dns=" + $sanItem + "&`"
"
}
return $san
}
}
$settingsInf = $settingsInf.Replace("{{CN}}", $CommonName)
$settingsInf = $settingsInf.Replace("{{O}}", $Organization)
$settingsInf = $settingsInf.Replace("{{OU}}", $OrganizationalUnit)
$settingsInf = $settingsInf.Replace("{{L}}", $Locality)
$settingsInf = $settingsInf.Replace("{{S}}", $State)
$settingsInf = $settingsInf.Replace("{{C}}", $Country)
$settingsInf = $settingsInf.Replace("{{SAN}}", $request['SAN_string'])
$settingsInf = $settingsInf.Replace("{{KeyLength}}",$KeyLength)
$settingsInf = $settingsInf.Replace("{{HashAlgorithm}}",$HashAlgorithm)
$settingsInf = $settingsInf.Replace("{{Exportable}}",$Exportable)
# Save settings to file in temp
$settingsInf > $files['settings']
certreq -new $files['settings'] $files['csr'] > $null
$CSR = Get-Content $files['csr']
Write-Output $CSR
If ($ToClipboard) {
$CSR | Set-Clipboard
}
If ($ToPath) {
$CSR | Out-File -FilePath $ToPath
}
$files.Values | ForEach-Object {
Remove-Item $_ -ErrorAction SilentlyContinue
}
}
end {
}
}
@@ -0,0 +1,154 @@
<#
.SYNOPSIS
Generates a Certificate Signing Request based on values inputted. Any values not inputted will result in the use of default values.
.DESCRIPTION
Generates a Certificate Signing Request based on values inputted. Any values not inputted will result in the use of default values. CSR will be printed to the screen, but can be saved to the clipboard, or to a file.
Default values are:
.NOTES
Run as administrator is required.
.EXAMPLE
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov'
CSR is generated using the common name shown, and default values for everything else
.EXAMPLE
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -Organization "OrgNameHere" -OrganizationalUnit "OrgUnitHere" -Locality Mandan -State ND -Country US -KeyLength 4096
CSR is generated using the values specified, defaults for the rest
.EXAMPLE
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -Organization "OrgNameHere" -OrganizationalUnit "OrgUnitHere" -Locality Mandan -State ND -Country US -KeyLength 4096 -ToClipboard
CSR is generated using the values specified, defaults for the rest, and saved into the user's clipboard
.EXAMPLE
New-ITDSslCertificateSigningRequest -CommonName 'commonname.nd.gov' -ToPath C:\temp.csr
CSR is generated using the common name shown, and default values for everything else, and saves the CSR to a local path
#>
function New-ITDSslCertificateSigningRequest {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]
$CommonName,
[string]
$Organization = "State of North Dakota",
[string]
$OrganizationalUnit = "NDIT",
[string]
$Locality = "Bismarck",
[string]
$State = "ND",
[string]
$Country = "US",
[ValidateSet(2048, 4096)]
[int]
$KeyLength = 4096,
[switch]
$Exportable = $true,
[ValidateSet('sha256','sha384','sha512','md5')]
[string]
$HashAlgorithm = "sha256",
[switch]
$ToClipboard,
[string]
$ToPath
)
begin {
if (-NOT([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Host "Administrator priviliges are required. Please restart this script with elevated rights." -ForegroundColor Red
Pause
Throw "Administrator priviliges are required. Please restart this script with elevated rights."
}
}
process {
$UID = [guid]::NewGuid()
$files = @{}
$files['settings'] = "$($env:TEMP)\$($UID)-settings.inf";
$files['csr'] = "$($env:TEMP)\$($UID)-csr.req"
$request = @{}
$request['SAN'] = @{}
#2048, sha256
$settingsInf = "
[Version]
Signature=`"`$Windows NT`$
[NewRequest]
KeyLength = {{KeyLength}}
Exportable = {{Exportable}}
MachineKeySet = TRUE
SMIME = FALSE
RequestType = PKCS10
ProviderName = `"Microsoft RSA SChannel Cryptographic Provider`"
ProviderType = 12
HashAlgorithm = {{HashAlgorithm}}
;Variables
Subject = `"CN={{CN}},OU={{OU}},O={{O}},L={{L}},S={{S}},C={{C}}`"
[Extensions]
{{SAN}}
;Certreq info
;http://technet.microsoft.com/en-us/library/dn296456.aspx
;CSR Decoder
;https://certlogik.com/decoder/
;https://ssltools.websecurity.symantec.com/checker/views/csrCheck.jsp
"
$request['SAN_string'] = & {
if ($request['SAN'].Count -gt 0) {
$san = "2.5.29.17 = `"{text}`"
"
Foreach ($sanItem In $request['SAN'].Values) {
$san += "_continue_ = `"dns=" + $sanItem + "&`"
"
}
return $san
}
}
$settingsInf = $settingsInf.Replace("{{CN}}", $CommonName)
$settingsInf = $settingsInf.Replace("{{O}}", $Organization)
$settingsInf = $settingsInf.Replace("{{OU}}", $OrganizationalUnit)
$settingsInf = $settingsInf.Replace("{{L}}", $Locality)
$settingsInf = $settingsInf.Replace("{{S}}", $State)
$settingsInf = $settingsInf.Replace("{{C}}", $Country)
$settingsInf = $settingsInf.Replace("{{SAN}}", $request['SAN_string'])
$settingsInf = $settingsInf.Replace("{{KeyLength}}",$KeyLength)
$settingsInf = $settingsInf.Replace("{{HashAlgorithm}}",$HashAlgorithm)
$settingsInf = $settingsInf.Replace("{{Exportable}}",$Exportable)
# Save settings to file in temp
$settingsInf > $files['settings']
certreq -new $files['settings'] $files['csr'] > $null
$CSR = Get-Content $files['csr']
Write-Output $CSR
If ($ToClipboard) {
$CSR | Set-Clipboard
}
If ($ToPath) {
$CSR | Out-File -FilePath $ToPath
}
$files.Values | ForEach-Object {
Remove-Item $_ -ErrorAction SilentlyContinue
}
New-ITDAutomationRecord -AppName "Windows-General" -Action "Provisioning" -Minutes 3 -Platform "PowerShell-ITD.Windows"
}
end {
}
}
@@ -0,0 +1,34 @@
function Remove-ITDSolarwindsNode {
[CmdletBinding()]
param (
[string]
$ComputerName,
[PSCredential]
$Credential
)
begin {
}
process {
$Func = {
Import-Module -Name ITDSolarwinds -Verbose
$test = Get-SWNode -ComputerName $args[0]
If ($test) {
Write-Warning "Solarwinds node found, removing it..."
Remove-SWNode -ComputerName $args[0]
}
Else {
Write-Warning "Solarwinds node not found"
}
}
Invoke-Command -ComputerName itdslrwnds.nd.gov -ScriptBlock $Func -ArgumentList $ComputerName -Credential $Credential
}
end {
}
}
@@ -0,0 +1,20 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
# Build and Test
TODO: Describe and show how to build your code and run the tests.
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
@@ -0,0 +1,19 @@
# figure out where to put it
$x = Get-ADComputer -Filter * -Server itdk12dc7.k12.nd.us -Credential $K12Cred -Properties CanonicalName
# enter this on the machine itself
$Credential = Get-Credential
$AddComputerParams = @{
DomainName = 'k12.nd.us';
OUPath = 'OU=Prod,OU=All-General,OU=Computers,OU=ITD,DC=k12,DC=nd,DC=us' ;
Credential = $Credential;
}
Add-Computer @AddComputerParams
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.ITD-WindowsServer.Lifecycle'
variables:
major: 0
minor: 1
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.ITD-WindowsServer.Lifecycle.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.ITD-WindowsServer.Lifecycle.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.ITD-WindowsServer.Lifecycle'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.ITD-WindowsServer.Lifecycle).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.ITD-WindowsServer.Lifecycle</id>
<version>$VERSIONHERE$</version>
<authors>Zack Meier</authors>
<description>Functions for Windows Server Lifecycle administration</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>
@@ -0,0 +1,132 @@
#
# Module manifest for module 'ITD.ITD-WindowsServer.Lifecycle'
#
# Generated by: zmeier
#
# Generated on: 6/14/2022
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'ITD.ITD-WindowsServer.Lifecycle.psm1'
# Version number of this module.
ModuleVersion = '<ModuleVersion>'
# Supported PSEditions
CompatiblePSEditions = 'Desktop', 'Core'
# ID used to uniquely identify this module
GUID = '42b3d283-1030-4bf9-afa7-4810061f74a8'
# Author of this module
Author = 'zmeier'
# Company or vendor of this module
CompanyName = 'State of North Dakota'
# Copyright statement for this module
Copyright = '(c) zmeier. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Functions for Windows Server general administration'
# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# ClrVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(<FunctionsToExport>)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,23 @@
#Get public and private function definition files.
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in @($Public + $Private))
{
Try
{
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
# Here I might...
# Read in or create an initial config file and variable
# Export Public functions ($Public.BaseName) for WIP modules
# Set variables visible to the module and its functions only
Export-ModuleMember -Function $Public.Basename
@@ -0,0 +1,447 @@
<# ################
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
$NewVMWindowsAzureParams = @{
ComputerName = 'itdzmtest901.nd.gov';
CPU = 2;
MemoryGB = 8;
DiskOsGB = 128;
DiskDataGB = 0;
Subnet = '10.21.8.0/22';
OS = "Windows Server 2025 Datacenter";
Environment = 'Test';
AppName = 'ITD-POC-zmeier';
LicensingRestrictions = "No Licensing Restrictions";
}
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
.EXAMPLE
$NewVMWindowsAzureParams = @{
ComputerName = 'itdzmtest701.nd.gov';
ResourceGroupNameOverride = 'rg-shared-iis-tst';
AvailabilityZone = 2;
CPU = 2;
MemoryGB = 8;
DiskOsGB = 128;
DiskDataGB = 0;
Subnet = '10.21.8.0/22';
OS = 'Windows Server 2022 Datacenter';
Environment = 'Test';
AppName = 'ITD-POC-zmeier';
LicensingRestrictions = "No Licensing Restrictions";
}
New-ITDWindowsVmAzure @NewVMWindowsAzureParams -Credential $PrvCred -Verbose
#>
function New-ITDWindowsVmAzureStep1 {
[CmdletBinding()]
param (
[string]
$FQDN,
[string]
$ResourceGroupNameOverride,
[string]
$AppName,
[ValidateSet(1, 2, 3)]
[int]
$AvailabilityZone,
[Parameter(ParameterSetName = 'VmSizeOverride')]
[string]
$VMSizeOverride,
[Parameter(ParameterSetName = 'VmSizeDetermine')]
[int]
$CPU,
[Parameter(ParameterSetName = 'VmSizeDetermine')]
[int]
$MemoryGB,
[int]
$DiskOsGB,
[int]
$DiskDataGB,
[string]
$Subnet,
[string]
$OS,
[string]
$VMEnvironment,
#[string]
#$Subscription,
[string]
$LicensingRestrictions,
[PSCredential]
$Credential
)
begin {
}
process {
$FQDN = $FQDN.ToLower()
$Hostname = $FQDN.split('.')[0]
Write-Verbose -Message "Prepare Connections"
#$tenantId = '2dea0464-da51-4a88-bae2-b3db94bc0c54'
#$AppId = '60244573-7130-4026-9c6d-47de73f8ca29'
#$SecureStringPwd = '' #$Secret:AzureVMServicePrincipal #Pqt8Q~E-dDmQugcPPWdaK2t_4retS41VVVVOZbOx
#$SecureStringPwd = 'sQV8Q~sNLcrDl2RgB32gsSEsDVgdFhNMcBdPoaEX'
#$PSCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, ($SecureStringPwd | ConvertTo-SecureString -AsPlainText -Force)
#Connect-AzAccount -ServicePrincipal -Credential $PSCredential -Tenant $tenantId
Write-Verbose -Message "Prepare Credentials"
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one"
Clear-DnsClientCache
$Cidr = $Subnet
[Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -Server 10.10.10.10 -ErrorAction SilentlyContinue).IPAddress
$SubnetMaskInt = $CIDR.split('/')[1]
$Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2))
[Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(),
([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(),
([math]::Truncate(($Int64 % 65536) / 256)).ToString(),
([math]::Truncate($Int64 % 256)).ToString()
$IPSplit = $Subnet.Split('.')
[Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) )
If ($null -ne $IpAddress) {
If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) {
Write-Warning "DNS record already exists, CIDR Block match"
}
Else {
Write-Error "DNS record already exists, and does not match CIDR Block"
Break
}
}
Else {
Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record."
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
Start-Sleep -Seconds 5
Write-Verbose -Message ("FQDN is " + $FQDN)
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
#[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress
[Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address
If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) {
Write-Error "IP Address already in use." -ErrorAction Stop
}
}
Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it."
If ($FQDN -like "itdcnd*") {
$PasswordStateList = "Peoplesoft Share PW"
}
Else {
$PasswordStateList = "CSRC"
}
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential
If ($null -eq $GuestVMLocalCredential) {
Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials"
$GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential
}
Else {
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
}
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
Write-Verbose -Message "Determine environment"
switch ($VMEnvironment) {
{ $_ -eq 'Test' -or $_ -eq 'Development' } {
Write-Verbose -Message "Environment is Test or Development"
$EnvShortString = 'tst'
}
'Production' {
Write-Verbose -Message "Environment is Production"
$EnvShortString = 'prd'
}
}
Write-Verbose -Message "Determine subscription, via Subnet"
$AllSubscriptions = Get-AzSubscription
$AllAzVirtualNetworks = ForEach ($Subscription in $AllSubscriptions) {
Set-AzContext -SubscriptionObject $Subscription | Out-Null
Get-AzVirtualNetwork | ForEach-Object {
[PSCustomObject]@{
Subscription = $Subscription.Name;
VirtualNetwork = $_
}
}
}
$Subscription = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).Subscription
$VirtualNetwork = ($AllAzVirtualNetworks | Where-Object { $_.VirtualNetwork.Subnets.AddressPrefix -match $Subnet }).VirtualNetwork
$VirtualNetworkSubnet = $VirtualNetwork.Subnets | Where-Object { $_.AddressPrefix -match $Subnet }
$VNetName = $VirtualNetwork.Name
$VNetSubnet = $VirtualNetworkSubnet.Name
Write-Verbose -Message "OS is $OS"
switch ($OS) {
{ $_ -eq "Windows Server 2019 Datacenter" -or $_ -eq "Windows Server 2019" } {
Write-Verbose -Message "OS is Windows Server 2019"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "WindowsServer"
$sku = "2019-Datacenter"
$SecurityType = ''
}
{$_ -eq "Windows Server 2022 Datacenter" -or $_ -eq "Windows Server 2022" } {
Write-Verbose -Message "OS is Windows Server 2022"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "windowsserver2022"
$sku = "2022-datacenter"
$SecurityType = ''
}
{ $_ -eq "Windows Server 2025 Datacenter" -or $_ -eq "Windows Server 2025" } {
Write-Verbose -Message "OS is Windows Server 2025"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsServer"
$Offer = "WindowsServer"
$sku = "2025-datacenter"
$SecurityType = ''
}
"Windows 11 24H2" {
Write-Verbose -Message "OS is Windows 11 24H2"
$VMOS = "Windows"
$Publisher = "MicrosoftWindowsDesktop"
$Offer = "Windows-11"
$sku = "win11-24h2-ent"
$SecurityType = ''
}
Default { Write-Error "Invalid operating system" -ErrorAction Stop }
}
# finalize VM location and size
$location = "centralus"
switch ($PSCmdLet.ParameterSetName) {
"VmSizeOverride" {
Write-Verbose -Message "ParameterSet is VmSizeOverride"
$VMSize = Get-AzVMSize -Location centralus | Where-Object { $_.Name -eq $VMSizeOverride }
}
"VmSizeDetermine" {
Write-Verbose -Message "ParameterSet is VmSizeDetermine"
$VMSizeFilter1 = @(
"Standard_D2ds_v5",
"Standard_D4ds_v5",
"Standard_D8ds_v5",
"Standard_D16ds_v5",
"Standard_D32ds_v5",
"Standard_E2ds_v5",
"Standard_E4ds_v5",
"Standard_E8ds_v5",
"Standard_E16ds_v5",
"Standard_E20ds_v5",
"Standard_E32ds_v5",
"Standard_F2s_v2",
"Standard_F4s_v2",
"Standard_F8s_v2",
"Standard_F16s_v2",
"Standard_F32s_v2"
)
$VMSize = Get-AzVMSize -Location centralus | `
Where-Object { $VMSizeFilter1 -contains $_.Name } | `
Where-Object { $_.NumberOfCores -ge $CPU -and $_.MemoryInMB -ge ($Memory * 1024) } | `
Where-Object Name -NotMatch "_Promo" | `
Sort-Object NumberOfCores, MemoryInMB | `
Select-Object -First 1
}
}
Write-Verbose -Message "Determine ResourceGroupName, and create Resource Group if needed"
If ($PSBoundParameters.ContainsKey('ResourceGroupNameOverride')) {
# use the name in the variable
Write-Verbose -Message "ResourceGroupName parameter found, is $ResourceGroupNameOverride"
$ResourceGroupName = $ResourceGroupNameOverride
}
Else {
Write-Verbose -Message "ResourceGroupName parameter not found, determine now"
# if name is not given, determine the name
$VMOwner = $AppName.split('-')[0].ToLower()
$VMFunction = (($AppName -replace "$VMOwner-") -replace "-").ToLower() -replace " "
$ResourceGroupName = "rg-$VMOwner-$VMFunction-$EnvShortString"
}
Write-Verbose -Message "ResourceGroupName is $ResourceGroupName"
Write-Verbose -Message "Change to selected subscription and validate resource group exists"
Set-AzContext $Subscription | Out-Null
$ResGroupExist = $null
$ResGroupExist = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
If (@($ResGroupExist).count -gt 1) {
Write-Error "Multiple Resource Groups matched" -ErrorAction Stop
}
If (@($ResGroupExist).count -lt 1) {
Write-Warning "No matching resource group found, creating it now"
New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Tag @{ApplicationName = $AppName }
}
If (@($ResGroupExist).count -eq 1) {
Write-Verbose -Message "Exactly one matching resource group found"
}
$VMName = "vm-$HostName-$EnvShortString"
$VMIPConfigName = "ipconfig-$HostName-$EnvShortString"
$VMNicName = "nic-$HostName-$EnvShortString"
$VMObjectName = "vm-$HostName-$EnvShortString"
$VMOsDiskName = "vm-$HostName-os-$EnvShortString"
Write-Verbose -Message "Verifying Applicable Inputs are Lowercase"
$VMIPConfigName = $VMIPConfigName.ToLower()
$VMNicName = $VMNicName.ToLower()
$VMObjectName = $VMObjectName.ToLower()
$VMOsDiskName = $VMOSDiskName.ToLower()
Write-Verbose -Message "Creating IPConfig and NIC"
$IPConfig = New-AzNetworkInterfaceIpConfig -Name $VMIPConfigName -PrivateIpAddress $IPAddress.IPAddressToString -PrivateIpAddressVersion IPv4 -Subnet $VirtualNetworkSubnet
$VMNIC = New-AzNetworkInterface -IpConfigurationName $IPConfig -Location $Location -Name $VMNICName -ResourceGroupName $ResourceGroupName -Subnet $VirtualNetworkSubnet -Force
$VMNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
$VMNIC.IpConfigurations[0].PrivateIpAddress = $IPAddress.IPAddressToString
Set-AzNetworkInterface -NetworkInterface $VMNIC
Write-Verbose -Message "Build VM Config"
$vmConfigParams = @{
VMName = $VMObjectName
VMSize = $VMSize.Name
}
If ($PSBoundParameters.ContainsKey('AvailabilityZone')) {
Write-Verbose -Message "AvailabilityZone parameter found, adding to vmConfigParams"
$vmConfigParams += @{
Zone = $AvailabilityZone
}
}
$vmConfig = New-AzVMConfig @vmConfigParams
# Security Profile
switch ($SecurityType) {
'TrustedLaunch' {
Write-Verbose -Message "Security Profile is TrustedLaunch"
$vmConfig = Set-AzVMSecurityProfile -VM $vmConfig -SecurityType "TrustedLaunch"
}
'' {
Write-Verbose -Message "Security Profile is empty"
}
Default {
Write-Verbose -Message "Security Profile is null"
}
}
$vmConfig | Set-AzVMOSDisk -Name $VMOSDiskName -CreateOption FromImage
If ($VMOS -eq "Windows") {
$vmConfig | Set-AzVMOperatingSystem -Windows -ComputerName $VMName -Credential $GuestVMLocalCredential
$vmConfig | Set-AzVMSourceImage -PublisherName $Publisher -Offer $Offer -Skus $Sku -Version latest
$vmConfig.OSProfile.ComputerName = $HostName
}
If ($VMOS -eq "Linux") {
$vmConfig | Set-AzVMOperatingSystem -Linux -ComputerName $VMFQDN -Credential $GuestVMLocalCredential
Switch ($VMSubscription) {
"npd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/76297098-764c-43de-8525-c9fda1b237be/resourceGroups/rg-infra-templates-tst-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-103" }
"infra01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/e53aa0c7-824d-40a2-b420-4ab77b1051d2/resourceGroups/rg-infra-templates-prd-001/providers/Microsoft.Compute/images/vm-rhel74template-prd-403" }
"prd01" { $vmConfig | Set-AzVMSourceImage -Id "/subscriptions/437b2bfa-850e-4464-b6c2-38a68cda7c69/resourceGroups/rg-infra-templates-prd-002/providers/Microsoft.Compute/images/vm-rhel74template-prd-003" }
}
}
$vmConfig | Add-AzVMNetworkInterface -Id $VMNIC.ID
Set-AzVMBootDiagnostic -VM $vmConfig -Enable -ResourceGroupName $ResourceGroupName
Write-Verbose "Creating VM"
New-AzVM -VM $vmConfig -ResourceGroupName $ResourceGroupName -Location $Location -DisableBginfoExtension -LicenseType "Windows_Server" #-AsJob
Write-Verbose -Message "New-AzVM completed, waiting 60 seconds before checking for completion"
Start-Sleep -Seconds 60
$VM = Get-AzVM -Name $VMObjectName -ResourceGroupName $ResourceGroupName
If ($PSBoundParameters.ContainsKey("DiskDataGB")) {
$ExistingDisks = @($VM.StorageProfile.DataDisks | Select-Object *, @{n = 'ItdId'; e = { [int]($_.Name -replace "vm-$hostname-app-$VMEnvironment-") } })
$NewDiskItdIdInt = ($ExistingDisks | Sort-Object ItdId -Descending | Select-Object -First 1).ItdId + 1
$NewDiskItdIdStr = $NewDiskItdIdInt.ToString("000")
$NewDiskName = "vm-$Hostname-app-$EnvShortString-$NewDiskItdIdStr" #vm-itduc4p1-app-tst-001
$LunID = ($ExistingDisks | Sort-Object Lun -Descending | Select-Object -First 1).Lun + 1
$count = 0
If ($ExistingDisks) {
while ($Size -match $ExistingDisks.DiskSizeGB) {
$count++
Write-Verbose -Message "DiskDataGB: $DiskDataGB, Count: $count"
If ($count -ge 11) {
Write-Error "Disk size not available" -ErrorAction Stop
}
Else {
$Size = $Size - 5
}
}
}
Write-Verbose -Message "DiskDataGB: $DiskDataGB, Count: $count"
If ($null -ne $DiskDataGB -and $DiskDataGB -gt 0) {
$AzureRmDiskConfigParams = @{
DiskSizeGB = $DiskDataGB
Location = $Location
CreateOption = "Empty"
SkuName = "Premium_LRS"
}
If ($AvailabilityZone) {
Write-Verbose "VM is located in Availability Zone $Zone"
$AzureRmDiskConfigParams += @{Zone = $Zone }
}
#$DiskConfig = New-AzureRmDiskConfig -DiskSizeGB $Size -Location $Location -CreateOption Empty -SkuName Premium_LRS
$DiskConfig = New-AzDiskConfig @AzureRmDiskConfigParams
If (!(Get-AzDisk -ResourceGroupName $ResourceGroupName -DiskName $NewDiskName -ErrorAction SilentlyContinue)) {
$NewDisk = New-AzDisk -DiskName $NewDiskName -Disk $DiskConfig -ResourceGroupName $ResourceGroupName
$VM = Add-AzVMDataDisk -Name $NewDiskName -CreateOption Attach -ManagedDiskId $NewDisk.Id -VM $VM -Lun $LunID -Caching ReadOnly
Update-AzVM -VM $VM -ResourceGroupName $ResourceGroupName -AsJob
}
}
}
Write-Verbose -Message "Add networking tag to VM" -Verbose
$Tags = @{"Network"="StandardWindows"}
New-AzTag -ResourceId $VM.Id -Tag $Tags
}
end {
}
}
@@ -0,0 +1,341 @@
function New-ITDWindowsVMAzureStep2 {
[CmdletBinding()]
param (
[string]
$FQDN,
[string]
$AppName,
[string]
$VMEnvironment,
[PSCredential]
$Credential
)
begin {
$search = @()
#$Hostname = $VMName.split('-')[1]
$HostName = $FQDN.split('.')[0]
###$VMEnvironment = $VMName.split('-')[2] ####
Write-Verbose -Message ("Searching for Az VM name like " + $HostName)
Get-AzSubscription | ForEach-Object {
$Subscription = $_
Set-AzContext $_ | Out-Null
$search += Get-AzVM | Where-Object Name -Like "*$HostName*" | Select-Object *, @{n = 'SubscriptionName'; e = { $Subscription.Name } }
}
}
process {
switch (@($Search).count) {
{ $_ -gt 1 } { Write-Error -Message "More than one virtual machine match found." }
{ $_ -lt 1 } { Write-Error -Message "Less than one virtual machine match found." }
{ $_ -eq 1 } {
Write-Verbose -Message "Prep VM variables"
$VMName = $search.Name
$ResourceGroupName = $search.ResourceGroupName
#$VMEnvironment = $VMName.split('-')[2]
$SubscriptionName = $search.SubscriptionName
Set-AzContext -Subscription $SubscriptionName
$VM = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName
Write-Verbose -Message "Set firewall tag"
$Tags = @{"Network"="StandardWindows"}
New-AzTag -ResourceId $VM.Id -Tag $Tags
Write-Verbose -Message "Wait two minutes before Pre-Firewall Guest OS customization"
#Start-Sleep -Seconds 120
Write-Verbose -Message "Begin Pre-Firewall Guest OS customization"
$InvokeAzVMRunCommandParams = @{
ResourceGroupName = $ResourceGroupName;
Name = $VMName;
CommandId = 'RunPowerShellScript'
}
Write-Verbose -Message "1-Set WMI Tags"
$ScriptBlock = {
try {
Write-Verbose "Create new Class"
$Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);
$Class["__CLASS"] = "ITD";
$Class.Qualifiers.Add("Static", $true)
$Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false)
$Class.Properties["MyKey"].Qualifiers.Add("Key", $true)
$Class.Properties.Add("LastModified", [System.Management.CimType]::String, $false)
$Class.Properties.Add("DTAP", [System.Management.CimType]::String, $false)
$Class.Properties.Add("Baseline", [System.Management.CimType]::String, $false)
$Class.Put()
Write-Verbose "Create single ITD Object"
Set-WmiInstance -Class ITD -Arguments @{LastModified = (Get-Date); DTAP = "Prod"; Baseline = "000" }
}
catch {
Throw $_
Break
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "3-Disk Configuration"
$ScriptBlock = {
try {
# Non-initialized and MBR-initialized disks will have 0 partitions by default, but GPT-initialized disks will have 1 system reserved partition by default
$disks = Get-Disk | Where-Object { $_.NumberOfPartitions -eq 0 -or ( $_.PartitionStyle -eq 'GPT' -and $_.NumberOfPartitions -eq 1 ) } | Sort-Object -Property Number
if ($disks) {
Write-Verbose -Message "Found $(@($disks).Count) unpartitioned disks."
# Prevent the "You must format this partition before using it." popup
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Stop-Service ShellHWDetection -ErrorAction SilentlyContinue }
foreach ($disk in $disks) {
if ($disk.IsOffline) {
Set-Disk $disk.Number -IsOffline $false
Write-Verbose -Message "Brought disk $($disk.Number)($("{0:n0}GB" -f ($disk.Size / 1GB))) online..."
}
if ($disk.IsReadOnly) { Set-Disk $disk.Number -IsReadOnly $false }
if ($disk.PartitionStyle -eq 'RAW') { Initialize-Disk $disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue }
$diskParam = @{
FileSystem = 'NTFS'
Confirm = $false
}
$driveLetter = [Int][Char]'D'
while (Get-Volume -DriveLetter $([Char]$driveLetter) -ErrorAction SilentlyContinue) {
$driveLetter++
}
$diskParam.DriveLetter = [Char]$driveLetter
if (@($disks).IndexOf($disk) -eq 0 -and (-not (Get-Volume -DriveLetter D -ErrorAction SilentlyContinue))) {
$diskParam.NewFileSystemLabel = 'Temporary Storage'
}
elseif (@($disks).IndexOf($disk) -eq 1 -and (-not (Get-Volume -DriveLetter E -ErrorAction SilentlyContinue))) {
$diskParam.NewFileSystemLabel = 'Data'
}
[void](New-Partition -DiskNumber $disk.Number -DriveLetter $diskParam.DriveLetter -UseMaximumSize)
[void](Format-Volume @diskParam)
}
}
else {
Write-Verbose -Message "No unpartitioned disks found, continuing..."
}
}
catch {
Throw $_
Break
}
finally {
if (Get-Service ShellHWDetection -ErrorAction SilentlyContinue) { Start-Service ShellHWDetection -ErrorAction SilentlyContinue }
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "5-Time Zone"
$ScriptBlock = {
if ((Get-TimeZone).Id -ne 'Central Standard Time') {
Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time."
Set-TimeZone -Id 'Central Standard Time'
Write-Verbose -Message "Time zone set to Central Standard Time."
}
else {
Write-Verbose -Message "Time zone is already set to Central Standard Time."
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "6-Enable Performance Counters"
$ScriptBlock = {
# Enable Performance Counters
try {
if (Get-ScheduledTask -TaskName "Server Manager Performance Monitor" | Where-Object State -NE "Running" -ErrorAction SilentlyContinue) {
Enable-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Server Manager Performance Monitor" | Start-ScheduledTask
Write-Verbose -Message "Performance monitors enabled."
}
else {
Write-Verbose -Message "Performance monitors already enabled, continuing..."
}
}
catch {
Throw $_
Break
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
Write-Verbose -Message "7-Disable Windows Firewall"
$ScriptBlock = {
# Disable Windows Firewall
Write-Verbose -Message "Checking for active Windows Firewall..."
if ((Get-NetFirewallProfile).Enabled -contains 'True') {
Write-Verbose -Message "Windows Firewall is still enabled, disabling it..."
Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False
Write-Verbose -Message "Windows Firewall disabled."
}
else {
Write-Verbose -Message "Windows Firewall already disabled, continuing..."
}
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock
# determine Active Directory Forest and OU
Write-Verbose -Message "8a-Determine domain join"
$DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1)
switch ($DomainName) {
'nd.gov' {
$SearchBaseDomain = "dc=nd,dc=gov"
}
'ndcloud.gov' {
$SearchBaseDomain = "dc=ndcloud,dc=gov"
}
}
If ($DomainName -eq "nd.gov") {
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq $AppName }
If ($null -eq $OUAppName) {
Write-Verbose -Message "Dedicated AppName OU for $AppName not found, placing in All-General OU"
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq 'All-General' }
}
Write-Verbose -Message ("OUAppName: " + $OUAppName.distinguishedName) -Verbose
# Determine if the Environment sub-ou is special for the selected AppName
switch ($OUAppName.Name) {
'Shared-PeopleSoft-HigherEd' {
switch ($VMEnvironment) {
'Test' { $EnvString = "Non-Prod" }
'Production' { $EnvString = "Prod" }
}
}
'Shared-PeopleSoft-State' {
switch ($VMEnvironment) {
'Test' { $EnvString = "Non-Prod" }
'Production' { $EnvString = "Prod" }
}
}
Default {
switch ($VMEnvironment) {
'Test' { $EnvString = "Test" }
'Production' { $EnvString = "Prod" }
}
}
}
# Gather all sub OUs for the AppName OU
Write-Verbose -Message "EnvString is $EnvString" -Verbose
$SubOUs = Get-ADOrganizationalUnit -Server $DomainName -SearchBase $OUAppName -Filter *
Write-Verbose -Message ("SubOUs:") -Verbose
$SubOUs | ForEach-Object { Write-Verbose -Message $_.DistinguishedName -Verbose }
$LikeFilter = ("OU=$EnvString,OU=" + $OUAppName.Name + "*")
Write-Verbose -Message "LikeFilter: $LikeFilter"
$OUToUse = $SubOUs | Where-Object { $_.DistinguishedName -like $LikeFilter }
Write-Verbose -Message ("OUToUse: " + $OUToUse.distinguishedName) -Verbose
<#$MatchFilter = ("OU=$EnvString,OU=" + $OUAppName.Name)
Write-Verbose -Message "MatchFiler: $MatchFilter"
$OutToUseMatch = ($SubOUs | Where-Object {$_.DistinguishedName -match "^$MatchFilter"})
Write-Verbose -Message ("OUToUseMatch: " + $OUToUseMatch.distinguishedName) -Verbose
#>
# Determine if AD Computer object already exists
$ExistingADComputer = Get-ADComputer -Filter { Name -eq $HostName }
If ($ExistingADComputer) {
Write-Verbose -Message "AD Object already exists in AD"
# Determine if AD Computer object is in the correct OU, or a sub-OU of the correct OU
If ($ExistingADComputer.DistinguishedName -like ("*" + $OUToUse.DistinguishedName)) {
Write-Verbose -Message "AD Object is in the correct OU, will attempt domain join"
$AttemptDomainJoin = $true
}
Else {
Write-Verbose -Message "AD Object is NOT in the correct OU, domain join will not occur, needs human review"
$AttemptDomainJoin = $false
}
}
Else {
$AttemptDomainJoin = $true
}
If ($AttemptDomainJoin) {
$OuFinal = $OUToUse.DistinguishedName
Write-Verbose -Message "OuFinal: $OuFinal" -Verbose
$FirstScriptBlock = { $DomainJoinCred = New-Object System.Management.Automation.PSCredential('svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force)) }
$SecondScriptText = 'Add-Computer -DomainName <DomainName> -OUPath "<OuPath>" -Credential $DomainJoinCred -Server itddc42.nd.gov -Verbose; Restart-Computer -Force -Verbose;'
$SecondScriptText = $SecondScriptText -replace '<DomainName>', $DomainName
$SecondScriptText = $SecondScriptText -replace '<OuPath>', $OuFinal
#Write-Verbose -Message "[$FQDN]:Invoke-VMScript to AD join"
$InvokeVMScriptFunc = [System.Management.Automation.ScriptBlock]::Create("$FirstScriptBlock ; $SecondScriptText")
}
Write-Verbose -Message $InvokeVMScriptFunc.tostring() -Verbose ###
<# wait for firewall and reconnect
Write-Verbose -Message "8b-Verify if firewall is open before domain join attempt"
$connectivity = $false
while ($connectivity -eq $false) {
If ( (Test-NetConnection -ComputerName $FQDN).PingSuccedded) {
Write-Verbose -Message "Ping successful" -Verbose
$connectivity = $true
}
Else {
Write-Verbose -Message "Ping failed, may need to wait for PA IP sync" -Verbose
Start-Sleep -Seconds 60
}
}
#>
Write-Verbose -Message "8c-Attempt domain join"
switch ($DomainName) {
'nd.gov' {
Write-Verbose -Message "Attempting domain join nd.gov"
Write-Verbose -Message "Domain: $DomainName"
Write-Verbose -Message "OuPath: $OuFinal"
<#Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptPath 'C:\AzWinBuild\8-Domain-ndgov.ps1' -Parameter @{
"DomainName" = 'nd.gov';
"OuPath" = "$OuFinal"
}#>
$ScriptBlock = {
Param(
[string]
$DomainName,
[string]
$OuPath
)
Write-Host $OuPath
Test-NetConnection -ComputerName nd.gov
$DomainJoinCred = New-Object System.Management.Automation.PSCredential('ndgov\svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force))
Add-Computer -DomainName $DomainName -OUPath $OuPath -Credential $DomainJoinCred
Restart-Computer -Force
}
Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $ScriptBlock -Parameter @{
"DomainName" = 'nd.gov';
"OuPath" = "$OuFinal"
}
#Invoke-AzVMRunCommand @InvokeAzVMRunCommandParams -ScriptString $InvokeVMScriptFunc
}
}
}
}
}
}
end {
}
}
@@ -0,0 +1,529 @@
<#
### step 1
gather specs
provision virtual machine on hypervisor
final result:
cpu configured, with hot-add
memory configured, with hot-add
disks 1, 2, and 3 attached with correct sizing
network connected
virtual machine powered on
update SCTASK Step 1 completed
### step 2 - customize Windows Guest OS
wait for SCTASK Step 1 completed message
configure
time zone, mount points, page file, etc
update SCTASK Step 2 completed
### step 3 - administration
wait for SCTASK Step 2 completed message
domain join
nd.gov at a minimum
install sccm
regardless if domain joined
update SCTASK Step 3 completed
### step 4 - validation
confirm all agents are installed
disks are present and formatted
#>
<#
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
$NewITDWindowsVmVMwareParams = @{
ComputerName = 'itdzmtest300.nd.gov';
CPU = 2;
MemoryGB = 8;
DiskOsGB = 50;
DiskSwapGB = 9;
DiskDataGB = 20;
Subnet = '10.11.12.0/23';
OS = 'Windows Server 2022 Datacenter';
Environment = "Test";
Datacenter = "Bismarck";
AppName = "ITD-POC-zmeier";
StartupPriority = 4;
LicensingRestrictions = "No Licensing Restrictions";
Credential = $PrvCred;
}
New-ITDWindowsVmVMware @NewITDWindowsVmVMwareParams
#>
function New-ITDWindowsVmVMwareStep1 {
[CmdletBinding()]
param (
[string]
$FQDN,
[int]
$CPU,
[int]
$MemoryGB,
[int]
$DiskOsGB,
[int]
$DiskSwapGB,
[int]
$DiskDataGB,
[string]
$Subnet,
[string]
$OS,
[string]
$VMEnvironment,
[string]
$Datacenter,
[string]
$AppName,
[int]
$StartupPriority,
[string]
$LicensingRestrictions,
[PSCredential]
$Credential
)
begin {
}
process {
$FQDN = $FQDN.ToLower()
$HostName = $FQDN.split('.')[0]
If ($VMEnvironment -eq "Development") {
$VMEnvironment = "Test"
}
Write-Verbose -Message "Prepare Credentials and Connections"
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
Write-Verbose -Message "Infoblox: Find DNS pre-existing record, or create one"
Write-Verbose -Message "FQDN: $FQDN" -Verbose
Write-Verbose -Message "Hostname: $Hostname" -Verbose
Clear-DnsClientCache
$InfobloxVlanMetadata = Get-ITDIbVlan -CIDR $Subnet -Credential $RadiusCred
$Cidr = ($InfobloxVlanMetadata.AssignedTo | Out-String).TrimEnd()
[Net.IpAddress]$NetworkId = $Cidr.split('/')[0]
#######
####### Remove 10.10.10.10 references when DNS sync is fixed
#######
[Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction SilentlyContinue -Server 10.10.10.10).IPAddress
$SubnetMaskInt = $CIDR.split('/')[1]
$Int64 = ([convert]::ToInt64(('1' * $SubnetMaskInt + '0' * (32 - $SubnetMaskInt)), 2))
[Net.IPAddress]$SubnetMask = '{0}.{1}.{2}.{3}' -f ([math]::Truncate($Int64 / 16777216)).ToString(),
([math]::Truncate(($Int64 % 16777216) / 65536)).ToString(),
([math]::Truncate(($Int64 % 65536) / 256)).ToString(),
([math]::Truncate($Int64 % 256)).ToString()
$IPSplit = $Subnet.Split('.')
[Net.IPAddress]$DefaultGateway = ($IPSplit[0] + '.' + $IPSplit[1] + '.' + $IPSplit[2] + '.' + (($CIDR.split('/')[0].split('.')[-1] -as [int]) + 1) )
If ($null -ne $IpAddress) {
If (($IpAddress.Address -band $SubnetMask.Address) -eq ($NetworkId.Address -band $SubnetMask.Address)) {
Write-Verbose -Message "DNS record already exists, CIDR Block match"
}
Else {
Write-Error "DNS record already exists, but does not match CIDR Block"
Break
}
}
Else {
Write-Verbose -Message "Pre-existing IP address not found, creating new DNS record."
try {
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
Start-Sleep -Seconds 5
Write-Verbose -Message ("FQDN is " + $FQDN)
}
catch {
}
#######
####### Review this code after DNS problems resolved - 2024/09/24 zm
#######
####### [Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction Stop -Server 10.10.10.10).IPAddress
[Net.IPAddress]$IpAddress = (Get-ITDIbDNSRecord -Hostname $FQDN -Credential $RadiusCred).IPv4Address
If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) {
Write-Error "IP Address already in use." -ErrorAction Stop
}
}
Write-Verbose -Message "Passwordstate: If local administrator password does not exist in vault, create it."
If ($FQDN -like "itdcnd*") {
$PasswordStateList = "Peoplesoft Share PW"
}
Else {
$PasswordStateList = "CSRC"
}
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential
If ($null -eq $GuestVMLocalCredential) {
Write-Verbose -Message "Passwordstate: Local admin password record does not exist, creating new credentials"
$GuestVMLocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $Credential
}
Else {
Write-Verbose -Message "Passwordstate: Local admin password record already exists, use those credentials"
}
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
Write-Verbose -Message "Decide VMware Cluster and Licensing"
switch ($LicensingRestrictions) {
"No Licensing Restrictions" { $ClusterRoot = "WINDOWS" }
"Microsoft SharePoint Server" { $ClusterRoot = "WINDOWS" }
"Microsoft SharePoint Server (Academic)" { $ClusterRoot = "WINDOWS" }
"Microsoft SQL Developer" { $ClusterRoot = "WINDOWS" }
"Microsoft SQL MSDN" { $ClusterRoot = "WINDOWS" }
"Microsoft SQL Standard" { $ClusterRoot = "WINDOWS" }
"Microsoft SQL Standard (Academic)" { $ClusterRoot = "WINDOWS" }
"Microsoft SQL Standard (Vendor Provided)" { $ClusterRoot = "WINDOWS" }
"Microsoft SQL Enterprise" { $ClusterRoot = "SQLe" }
"Microsoft SQL Enterprise (Academic)" { $ClusterRoot = "SQLa" }
"IBM Websphere" { $ClusterRoot = "WAS" }
"Powerschool" { $ClusterRoot = "PS" }
"Pexip" { $ClusterRoot = "TEL" }
}
Write-Verbose -Message "Decide Datacenter"
switch ($Datacenter) {
"Bismarck" { $ClusterInt = 1 }
"Mandan" { $ClusterInt = 2 }
}
$Cluster = $ClusterRoot + $ClusterInt
Write-Verbose -Message "VMware Cluster is $Cluster, gathering metadata for $Cluster"
switch ($Cluster) {
"WINDOWS1" {
$ViServer = 'itdvmvc1.nd.gov'
$ComputeCluster = Get-Cluster WINDOWS1
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
If ($LicensingRestrictions -like "*SQL*") {
$DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS1_FS92_SQL"
$DiskStorageFormat = 'EagerZeroedThick'
}
Else {
$DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS1_FS92_Gen"
$DiskStorageFormat = 'Thin'
}
}
"WINDOWS2" {
$ViServer = 'itdvmvc2.nd.gov'
$ComputeCluster = Get-Cluster WINDOWS2
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
$DiskStorageFormat = 'Thin'
If ($LicensingRestrictions -like "*SQL*") {
$DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS2_FS92_SQL"
$DiskStorageFormat = 'EagerZeroedThick'
}
Else {
$DatastoreCluster = Get-DatastoreCluster -Name "WINDOWS2_FS92_Gen"
$DiskStorageFormat = 'Thin'
}
}
"SQLa1" {
$ViServer = 'itdvmvc1.nd.gov'
$ComputeCluster = Get-Cluster SQLa1
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
$DiskStorageFormat = 'EagerZeroedThick'
$DatastoreCluster = Get-DatastoreCluster -Name "SQLa1_FS92_Gen"
}
"SQLa2" {
$ViServer = 'itdvmvc2.nd.gov'
$ComputeCluster = Get-Cluster SQLa2
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
$DiskStorageFormat = 'EagerZeroedThick'
$DatastoreCluster = Get-DatastoreCluster -Name "SQLa2_FS92_Gen"
}
"SQLe1" {
$ViServer = 'itdvmvc1.nd.gov'
$ComputeCluster = Get-Cluster SQLe1
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
$DiskStorageFormat = 'EagerZeroedThick'
$DatastoreCluster = Get-DatastoreCluster -Name "SQLe1_FS92_Gen"
}
"SQLe2" {
$ViServer = 'itdvmvc2.nd.gov'
$ComputeCluster = Get-Cluster SQLe2
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
$DiskStorageFormat = 'EagerZeroedThick'
$DatastoreCluster = Get-DatastoreCluster -Name "SQLe2_FS92_Gen"
}
"WAS1" {
$ViServer = 'itdvmvc1.nd.gov'
$ComputeCluster = Get-Cluster WAS1
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
$DiskStorageFormat = 'Thin'
$DatastoreCluster = Get-DatastoreCluster -Name "WAS1_FS92_Gen"
}
"WAS2" {
$ViServer = 'itdvmvc2.nd.gov'
$ComputeCluster = Get-Cluster WAS2
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
$DiskStorageFormat = 'Thin'
$DatastoreCluster = Get-DatastoreCluster -Name "WAS2_FS92_Gen"
}
"PS1" {
$ViServer = 'itdvmvc1.nd.gov'
$ComputeCluster = Get-Cluster PS1
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server"
$DiskStorageFormat = 'Thin'
$DatastoreCluster = Get-DatastoreCluster -Name "PS1_FS92_Gen"
}
"PS2" {
$ViServer = 'itdvmvc2.nd.gov'
$ComputeCluster = Get-Cluster PS2
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server"
$DiskStorageFormat = 'Thin'
$DatastoreCluster = Get-DatastoreCluster -Name "PS2_FS92_Gen"
}
"TEL1" {
$ViServer = 'itdvmvc1.nd.gov'
$ComputeCluster = Get-Cluster TEL1
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-TEL1-Data"
$DiskStorageFormat = 'Thin'
$DatastoreCluster = Get-DatastoreCluster -Name "TEL1_FS92_Gen"
}
"TEL2" {
$ViServer = 'itdvmvc2.nd.gov'
$ComputeCluster = Get-Cluster TEL2
$VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-TEL2-Data"
$DiskStorageFormat = 'Thin'
$DatastoreCluster = Get-DatastoreCluster -Name "TEL2_FS92_Gen"
}
Default {
Write-Error "Cluster not found" -ErrorAction Stop
}
}
Write-Verbose -Message "Validate entered disk sizes"
Write-Verbose -Message "DiskOsGB is $DiskOsGB. Validating its not a stupid number." -Verbose
switch ($DiskOsGB) {
{ $_ -lt 50 } {
Write-Verbose -Message "DiskOsGB is 0 or below. Since an OS Disk is required, defaulting to 50GB" -Verbose
$DiskOsGB = 50
}
}
Write-Verbose -Message "DiskSwapGB is $DiskSwapGB. Validating its not a stupid number." -Verbose
switch ($DiskSwapGB) {
{ $_ -le 0 } {
Write-Verbose -Message "DiskSwapGB is zero or below. Since an Swap Disk is required, defaulting to `$Memory + 1GB." -Verbose
$DiskSwapGB = ($MemoryGB + 1)
}
}
Write-Verbose -Message "DiskDataGB is $DiskDataGB. Validating its not a stupid number." -Verbose
switch ($DiskDataGB) {
{ $_ -le 0 } {
Write-Verbose -Message "DiskDataGB is 0. Since an Data Disk is optional, data disk will not be created." -Verbose
$DiskDataGB = 0
}
{ $_ -gt 500 } {
Write-Verbose -Message "DiskDataGB is 500GB or more. DiskDataGB will be set to the maximum of 500GB." -Verbose
$DiskDataGB = 500
}
}
Write-Verbose -Message "Determine Datastore / Datastore Cluster"
$DiskTotal = $DiskOsGB + $DiskSwapGB + $DiskDataGB
If ($DatastoreCluster) {
}
Else {
$DatastoreCluster = Get-DatastoreCluster | Where-Object Name -Like ("*" + $ComputeCluster + "*")
}
$ClusterDatastoreWithHighestFreeSpaceGB = ($DatastoreCluster | Get-Datastore | Sort-Object FreeSpaceGB -Descending | Select-Object -First 1)
If ($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB -gt $DiskTotal) {
Write-Warning ("VM DiskTotal " + $DiskTotal + "GB, will fit on " + $ClusterDatastoreWithHighestFreeSpaceGB.Name + " (" + [math]::round($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB, 0) + "GB free)")
}
else {
Write-Warning ("VM DiskTotal " + $DiskTotal + "GB, will not fit on " + $ClusterDatastoreWithHighestFreeSpaceGB.Name + " (" + [math]::round($ClusterDatastoreWithHighestFreeSpaceGB.FreeSpaceGB, 0) + "GB free)")
Write-Error ("New VM " + $FQDN + " needs " + $DiskTotal + "GB of free space on a single datastore in the " + $DatastoreCluster.Name + " datastore cluster.") -ErrorAction Stop
}
$FolderLocation = $ComputeCluster | Get-Datacenter | Get-Folder -Name "_New Builds"
Write-Verbose -Message "Determine VMware VM Template for OS: $OS"
switch ($OS) {
"Windows Server 2012R2 Standard" { $Template = "Windows Server 2012R2 Standard" }
"Windows Server 2016 Standard" { $Template = "Windows Server 2016 Standard" }
"Windows Server 2019" { $Template = "Windows Server 2019 Standard 1809.19" }
"Windows Server 2019 Standard" { $Template = "Windows Server 2019 Standard 1809.19" }
"Windows Server 2019 Datacenter" { $Template = "Windows Server 2019 Standard 1809.19" }
"Windows Server 2022" { $Template = "Windows Server 2022 Standard 2108.21" }
"Windows Server 2022 Datacenter" { $Template = "Windows Server 2022 Standard 2108.21" }
"Windows Server 2025" { $Template = "Windows Server 2025 Standard 24H2.6" }
Default { Write-Error "Invalid template option: $OS" -ErrorAction Stop }
}
Write-Verbose -Message "Determine VMware Port Group"
$PortGroupsAvailable = Get-VDPortgroup -Server $ViServer -VDSwitch $VirtualSwitch
$PortGroup = $PortGroupsAvailable | Where-Object Name -Like ("dvPG_*" + $NetworkId.IPAddressToString + "_" + $SubnetMaskInt)
If (!($PortGroup)) {
Write-Error "Virtual port group not found" -ErrorAction Stop
Stop
}
If (@($PortGroup).count -gt 1) {
Write-Error "Multiple port groups found" -ErrorAction Stop
Stop
}
Write-Verbose -Message "Configure Guest OS Customization Spec"
$NewOSSpecName = ("AutoBuild-$Hostname-" + (Get-Date -UFormat "%Y%m%d%H%M%S"))
Write-Warning "NewOSSpecName = $NewOSSpecName"
Get-OSCustomizationSpec -Name 'Windows (Auto)' -Server $ViServer | New-OSCustomizationSpec -Name $NewOSSpecName -Type Persistent -Server $ViServer
Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer | `
Set-OSCustomizationSpec `
-NamingScheme fixed `
-NamingPrefix $Hostname `
-AdminPassword $GuestCredentialBB.GetNetworkCredential().Password
Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer | `
Get-OSCustomizationNicMapping | `
Set-OSCustomizationNicMapping `
-IpMode UseStaticIP `
-IpAddress $IpAddress.IPAddressToString `
-SubnetMask $SubnetMask.IPAddressToString `
-DefaultGateway $DefaultGateway.IPAddressToString `
-Dns "10.2.7.40", "10.10.10.10"
$OSSpec = Get-OSCustomizationSpec -Name $NewOSSpecName -Server $ViServer
Set-Location C:\Temp
$NewVMParams = @{
Name = $FQDN;
ResourcePool = $ComputeCluster.Name;
Datastore = $DatastoreCluster;
DiskStorageFormat = $DiskStorageFormat;
Location = $FolderLocation;
# Removed when using Content Library
#Template = $Template;
#OSCustomizationSpec = $OSSpec
}
Get-ContentLibraryItem -Name $Template -Server $ViServer | New-VM @NewVMParams
$VM = Get-VM -Name $FQDN
$VM | Set-VM -OSCustomizationSpec $OSSpec -Confirm:$false
Write-Verbose -Message "Set vCenter Tags on VM"
New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category AppName -Name $AppName) -Server $VIServer
New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category DTAP -Name $VMEnvironment) -Server $VIServer
New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category LicensingRestrictions -Name $LicensingRestrictions) -Server $VIServer
New-TagAssignment -Entity (Get-VM $FQDN -Server $VIServer) -Tag (Get-Tag -Server $ViServer -Category StartupPriority -Name $StartupPriority) -Server $VIServer
# Ensure CPU/Memory Hot-Add Enabled
$vmView = $VM | Get-View
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
$vmOptValCPU = New-Object VMware.Vim.OptionValue
$vmOptValMem = New-Object VMware.Vim.OptionValue
$vmOptValCPU.Key = "vcpu.hotadd"
$vmOptValMem.Key = "mem.hotadd"
$vmOptValCPU.Value = "true"
$vmOptValMem.Value = "true"
$vmConfigSpec.ExtraConfig += $vmOptValCPU
$vmConfigSpec.ExtraConfig += $vmOptValMem
$vmView.ReconfigVM($vmConfigSpec)
# Set CPU, Memory, Network
$VM | Set-VM -NumCpu $CPU -MemoryGB $MemoryGB -Confirm:$false
$VM | Get-NetworkAdapter | Set-NetworkAdapter -Portgroup $PortGroup -Confirm:$false
Write-Verbose -Message "Config and Update Disks"
$VMDisk = $VM | Get-HardDisk
$VMDisk1 = $VMDisk | Where-Object Name -EQ "Hard disk 1"
$VMDisk2 = $VMDisk | Where-Object Name -EQ "Hard disk 2"
$VMDisk3 = $VMDisk | Where-Object Name -EQ "Hard disk 3"
Write-Verbose -Message "Update Disk 1"
$VMDisk1 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 1"
If ($VMDisk1.CapacityGB -lt $DiskOsGB) {
Set-HardDisk -HardDisk $VMDisk1 -CapacityGB $DiskOsGB -Confirm:$false
}
Write-Verbose -Message "Config Disk 2"
If (!$VMDisk2) {
$VM | New-HardDisk `
-CapacityGB ($MemoryGB + 1) `
-StorageFormat Thin `
-DiskType Flat `
-Persistence Persistent
}
$VMDisk2 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 2"
If ($VMDisk2.CapacityGB -lt $DiskSwapGB) {
Set-HardDisk -HardDisk $VMDisk2 -CapacityGB $DiskSwapGB -Confirm:$false
}
Write-Verbose -Message "Config Disk 3"
If ($DiskDataGB -gt 0) {
Write-Verbose -Message "$DiskDataGB greater than zero"
If (!$VMDisk3) {
$VM | New-HardDisk `
-CapacityGB $DiskDataGB `
-StorageFormat Thin `
-DiskType Flat `
-Persistence Persistent
}
$VMDisk3 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 3"
If ($VMDisk3.CapacityGB -lt $DiskDataGB) {
Set-HardDisk -HardDisk $VMDisk3 -CapacityGB $DiskDataGB -Confirm:$false
}
}
Write-Verbose -Message "Set VMware Tools Upgrade Policy to UpgradeAtPowerCycle"
$VMView = $VM | Get-View
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
$vmConfigSpec.Tools = New-Object VMware.Vim.ToolsConfigInfo
$vmConfigSpec.Tools.ToolsUpgradePolicy = "UpgradeAtPowerCycle"
$VMView.ReconfigVM($vmConfigSpec)
Write-Verbose -Message "Power On VM"
$VM | Start-VM
Write-Verbose -Message "[$FQDN]:Step 1 End"
}
end {
}
}
@@ -0,0 +1,667 @@
<#
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
$NewITDWindowsVmVMwareParams = @{
ComputerName = 'itdzmtest300.nd.gov';
Environment = "Test";
AppName = "ITD-POC-zmeier";
Credential = $PrvCred;
}
New-ITDWindowsVmVMware @NewITDWindowsVmVMwareParams
#>
function New-ITDWindowsVmVMwareStep2 {
[CmdletBinding()]
param (
[string]
$FQDN,
[string]
$AppName,
[string]
$VMEnvironment,
[PSCredential]
$Credential
)
begin {
}
process {
Write-Verbose -Message "Start New-ITDWindowsVmVMwareStep2"
$FQDN = $FQDN.ToLower()
$HostName = $FQDN.split('.')[0]
$DomainName = $FQDN.Substring($FQDN.IndexOf(".") + 1)
$VM = Get-VM -Name $FQDN
$ViServer = $VM.Uid.split('@')[1].split(':')[0]
$VMTagAssignments = Get-TagAssignment -Entity $VM
#$AppName = $VMTagAssignments | Where-Object { $_.Tag.Category.Name -eq "AppName" }
#$DTAP = $VMTagAssignments | Where-Object { $_.Tag.Category.Name -eq "DTAP" }
Write-Verbose -Message "FQDN: $FQDN"
Write-Verbose -Message "HostName: $HostName"
$GuestVMLocalCredential = Get-ITDPassword -Title $FQDN -UserName itdadmin -Credential $Credential -ErrorAction SilentlyContinue
$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($GuestVMLocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force))
If ($null -eq $GuestVMLocalCredential) {
Write-Error -Message "Credentials not found in Passwordstate" -ErrorAction Stop
}
Else {
Write-Verbose -Message "Credentials found in Passwordstate"
Write-Verbose -Message ("username: + " + $GuestVMLocalCredential.UserName)
}
<# Wait for Customization to finish
$VMStarted = $false
$VMCustomizationStarted = $false
$VMCustomizationResult = $false
While ($VMStarted -eq $false -or $VMCustomizationStarted -eq $false -or $VMCustomizationResult -eq $false) {
Write-Warning ("Customization wait loop started " + (Get-Date))
Write-Verbose "Current Status:"
Write-Verbose ("VMStarted: " + $VMStarted)
Write-Verbose ("VMCustomizationStarted: " + $VMCustomizationStarted)
Write-Verbose ("VMCustomizationResult: " + $VMCustomizationResult)
$GetVIEventRuntime = Measure-Command -Expression { $VMEvents = Get-VIEvent -Entity $VM -Server $ViServer -ErrorAction SilentlyContinue } ## takes a long time to execute
Write-Verbose ("Get-VIEvent last run time: " + $GetVIEventRuntime.TotalSeconds + " seconds")
If ($VMStarted -eq $false) {
If (@($VMEvents | Where-Object { $_.GetType().Name -eq "VMStartingEvent" })) {
$VMStarted = $true
Write-Warning "[$FQDN]:Virtual machine started"
}
}
If ($VMCustomizationStarted -eq $false) {
If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationStartedEvent" })) {
$VMCustomizationStarted = $true
Write-Warning "[$FQDN]:Virtual machine customization started"
}
}
If ($VMCustomizationResult -eq $false) {
If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationFailed" })) {
$VMCustomizationResult = $true
Write-Error "[$FQDN]:Virtual machine customization failed"
Exit
Exit
}
If (@($VMEvents | Where-Object { $_.GetType().Name -eq "CustomizationSucceeded" })) {
$VMCustomizationResult = $true
Write-Warning "[$FQDN]:Virtual machine customization completed"
}
}
} #>
# OS
Write-Verbose -Message "Begin Post-Sysprep OS Guest Customization"
Write-Verbose -Message "Assigning WMI Tag 000-Prod, SCCM will change it later if required"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
# Move DVD Drive Mount
try {
Write-Verbose "Create new Class"
$Class = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);
$Class["__CLASS"] = "ITD";
$Class.Qualifiers.Add("Static", $true)
$Class.Properties.Add("MyKey", [System.Management.CimType]::String, $false)
$Class.Properties["MyKey"].Qualifiers.Add("Key", $true)
$Class.Properties.Add("LastModified", [System.Management.CimType]::String, $false)
$Class.Properties.Add("DTAP", [System.Management.CimType]::String, $false)
$Class.Properties.Add("Baseline", [System.Management.CimType]::String, $false)
$Class.Put()
Write-Verbose "Create single ITD Object"
Set-WmiInstance -Class ITD -Arguments @{LastModified = (Get-Date); DTAP = "Prod"; Baseline = "000" }
}
catch {
Throw $_
Break
}
}
Write-Verbose -Message "Checking for DVD drive, and moving it to Z:\"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
Write-Host "hello1"
# Move DVD Drive Mount
try {
$dvd_letter = 'Z'
$dvd = Get-WmiObject -Class Win32_Volume -Filter "DriveType=5" | Select-Object -First 1
if ($dvd.Name -notmatch $dvd_letter) {
Write-Verbose -Message "Found DVD drive, switching to $dvd_letter`:"
Set-WmiInstance -InputObject $dvd -Arguments @{DriveLetter = "$dvd_letter`:" } | Out-Null
Write-Verbose -Message "DVD drive moved to drive letter $dvd_letter`:"
}
else {
Write-Verbose -Message "No DVD drive changes required, continuing..."
}
}
catch {
Throw $_
Break
}
}
Write-Verbose -Message "Checking for unpartitioned space on C: volume and expanding"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
Write-Host "hello1"
# Expand C: Partition To Maximum Extent
try {
$cSize = ( Get-Partition -DriveLetter C ).Size
$cMaxSize = ( Get-PartitionSupportedSize -DriveLetter C ).SizeMax
if ($cSize -lt $cMaxSize) {
Write-Verbose -Message "Expanding C: from $($csize / 1GB)GB to $($cMaxSize / 1GB)GB..."
Resize-Partition -DriveLetter C -Size $cMaxSize
Write-Verbose -Message "C: expanded to $($cMaxSize / 1GB)GB."
}
else {
Write-Verbose -Message "C: is already at maximum size, continuing..."
}
}
catch {
Throw $_
Break
}
}
Write-Verbose -Message "Start Extra Disk(s) config"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
# Initialize Additional Disks
try {
# Non-initialized and MBR-initialized disks will have 0 partitions by default, but GPT-initialized disks will have 1 system reserved partition by default
$Disks = Get-Disk | Where-Object { $_.NumberOfPartitions -eq 0 -or ( $_.PartitionStyle -eq 'GPT' -and $_.NumberOfPartitions -eq 1 ) } | Sort-Object -Property Number
If ($Disks) {
Write-Verbose -Message "Found $(@($disks).Count) unpartitioned disks."
ForEach ($disk in $disks) {
if ($disk.IsOffline) {
Set-Disk $disk.Number -IsOffline $false
Write-Verbose -Message "Brought disk $($disk.Number)($("{0:n0}GB" -f ($disk.Size / 1GB))) online..."
}
if ($disk.IsReadOnly) { Set-Disk $disk.Number -IsReadOnly $false }
if ($disk.PartitionStyle -eq 'RAW') { Initialize-Disk $disk.Number -PartitionStyle GPT -ErrorAction SilentlyContinue }
$diskParam = @{
FileSystem = 'NTFS'
Confirm = $false
}
$driveLetter = [Int][Char]'D'
while (Get-Volume -DriveLetter $([Char]$driveLetter) -ErrorAction SilentlyContinue) {
$driveLetter++
}
$diskParam.DriveLetter = [Char]$driveLetter
if (@($disks).IndexOf($disk) -eq 0 -and (-not (Get-Volume -DriveLetter D -ErrorAction SilentlyContinue))) {
$diskParam.NewFileSystemLabel = 'Temporary Storage'
}
elseif (@($disks).IndexOf($disk) -eq 1 -and (-not (Get-Volume -DriveLetter E -ErrorAction SilentlyContinue))) {
$diskParam.NewFileSystemLabel = 'Data'
}
[void](New-Partition -DiskNumber $disk.Number -DriveLetter $diskParam.DriveLetter -UseMaximumSize)
[void](Format-Volume @diskParam)
}
}
Else {
Write-Verbose -Message "No unpartitioned disks found, continuing..." -Verbose
}
}
catch {
Throw $_
Break
}
}
Write-Verbose -Message "Start Page File Configuration"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
# Configure Page File
if (Get-Partition -DriveLetter D -ErrorAction SilentlyContinue) {
Write-Verbose -Message "Setting up pagefile.sys on D:..." -Verbose
try {
if (-not [IO.File]::Exists('D:\pagefile.sys')) {
$autoPage = Get-WmiObject -Class Win32_ComputerSystem -EnableAllPrivileges
$autoPage.AutomaticManagedPagefile = $false
[void]$autoPage.Put()
Write-Verbose -Message "Disabled automatic pagefile management." -Verbose
$pageFile = Get-WmiObject -Class Win32_PageFileSetting -EnableAllPrivileges
$pageFile.Delete()
Write-Verbose -Message "Deleted C:\pagefile.sys." -Verbose
Set-WmiInstance -Class Win32_PageFileSetting -Arguments @{ Name = "D:\pagefile.sys"; InitialSize = 0; MaximumSize = 0; } -EnableAllPrivileges | Out-Null
Write-Verbose -Message "System managed page file created on D:\pagefile.sys." -Verbose
}
else {
Write-Verbose -Message "Pagefile already configured on D:, continuing..." -Verbose
}
}
catch {
Throw $_
Break
}
}
else {
Write-Verbose -Message "Page file drive not found, cannot set up page file. Continuing server configuration..." -Verbose
Write-Warning "Page file drive not found, cannot set up page file. Continuing server configuration..."
}
}
Write-Verbose -Message "Enabling Remote Management"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
# Configure Remote Management (RDP/PoSH)
try {
Write-Verbose -Message "Checking WinRM..." -Verbose
if (Test-WSMan -ErrorAction SilentlyContinue) {
Write-Verbose -Message "WinRM is already enabled."
}
else {
Enable-PSRemoting -Force
Write-Verbose -Message "WinRM is now enabled."
}
Write-Verbose -Message "Checking RDP..." -Verbose
$RDP = Get-WmiObject Win32_TerminalServiceSetting -Namespace root\cimv2\TerminalServices
$NLA = Get-WmiObject Win32_TSGeneralSetting -Namespace root\cimv2\TerminalServices -Filter "TerminalName='RDP-tcp'"
if ($RDP.AllowTSConnections -eq 0) {
Write-Verbose -Message "RDP is disabled, enabling..."
$RDP.SetAllowTSConnections(1, 1) | Out-Null
Write-Verbose -Message "RDP is enabled."
}
else {
Write-Verbose -Message "RDP is already enabled, checking NLA security..."
}
if ($NLA.UserAuthenticationRequired -eq 0) {
Write-Verbose -Message "RDP is not NLA secured, enabling..."
$NLA.SetUserAuthenticationRequired(1) | Out-Null
Write-Verbose -Message "RDP is now NLA secured."
}
else {
Write-Verbose -Message "RDP is already NLA secured."
}
}
catch {
Throw $_
Break
}
}
Write-Verbose -Message "Checking current power plan, set to High Performance"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
# Configure Power Plan
try {
$powerPlans = powercfg -l
if ($powerPlans -match '\*$' -notmatch 'High performance') {
$currentPlan = [regex]::Match($powerPlans, '(?<=(\())[^)]+(?=(\)\s\*))').Value
Write-Verbose -Message "Power plan is currently set to $currentPlan, changing to High Performance..." -Verbose
$highPerformance = [regex]::Match($powerPlans, '([\d\w-\S]+)(?=\s+\(High performance\))').Value
[void](powercfg -setactive $highPerformance)
Write-Verbose -Message "Power plan set to High Performance." -Verbose
}
else {
Write-Verbose -Message "Power plan already configured for High Performance." -Verbose
}
[void](& w32tm.exe /resync /nowait)
}
catch {
Throw $_
Break
}
}
$TimeSyncFunc = {
# Configure Time/Date Settings
Write-Verbose -Message "Checking current time/date settings..."
$Domain = "<DomainName>"
try {
if ((Get-TimeZone).Id -ne 'Central Standard Time') {
Write-Verbose -Message "Current time zone set to $((Get-TimeZone).Id), setting to Central Standard Time." -Verbose
Set-TimeZone -Id 'Central Standard Time'
Write-Verbose -Message "Time zone set to Central Standard Time." -Verbose
}
else {
Write-Verbose -Message "Time zone is already set to Central Standard Time." -Verbose
}
}
catch {
Throw $_
Break
}
}
$TimeSyncScriptBlock = $TimeSyncFunc -replace '<DomainName>', $DomainName
$VM | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText $TimeSyncScriptBlock
#>
Write-Verbose -Message "Enable the server manager performance monitors"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
# Enable Performance Counters
try {
if (Get-ScheduledTask -TaskName "Server Manager Performance Monitor" | Where-Object State -NE "Running" -ErrorAction SilentlyContinue) {
Enable-ScheduledTask -TaskPath "\Microsoft\Windows\PLA\" -TaskName "Server Manager Performance Monitor" | Start-ScheduledTask
Write-Verbose -Message "Performance monitors enabled." -Verbose
}
else {
Write-Verbose -Message "Performance monitors already enabled, continuing..." -Verbose
}
}
catch {
Throw $_
Break
}
}
Write-Verbose -Message "Disable Windows Firewall"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText {
# Disable Windows Firewall
Write-Verbose -Message "Checking for active Windows Firewall..." -Verbose
if ((Get-NetFirewallProfile).Enabled -contains 'True') {
Write-Verbose -Message "Windows Firewall is still enabled, disabling it..."
Set-NetFirewallProfile -Profile Domain, Public, Private -Enabled False
Write-Verbose -Message "Windows Firewall disabled." -Verbose
}
else {
Write-Verbose -Message "Windows Firewall already disabled, continuing..." -Verbose
}
}
# Active Directory
Write-Verbose -Message "Join Active Directory"
Write-Verbose -Message "Domain is $DomainName"
Write-Verbose -Message "AppName is $AppName"
switch ($DomainName) {
'nd.gov' {
$SearchBaseDomain = "dc=nd,dc=gov"
}
'ndcloud.gov' {
$SearchBaseDomain = "dc=ndcloud,dc=gov"
}
}
### Shared-PeopleSoft-HigherEd is not found because of the extra space character in the OU name
### commented out section below is the original code, swapped out for new code 2025/01/13
# Determine if the AppName has a dedicated OU, and pick it... or choose the All-General OU
If ($DomainName -eq 'nd.gov') {
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq $AppName }
If ($null -eq $OUAppName) {
Write-Verbose -Message "Dedicated AppName OU not found, placing in All-General OU"
$OUAppName = Get-ADOrganizationalUnit -Server $DomainName -SearchBase ("OU=Windows,OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) -Filter { Name -eq "All-General" }
}
# Determine if the Environment sub-ou is special for the selected AppName
switch ($OUAppName.Name) {
'Shared-PeopleSoft-HigherEd' {
switch ($VMEnvironment) {
'Test' { $EnvString = "Non-Prod" }
'Production' { $EnvString = "Prod" }
}
}
'Shared-PeopleSoft-State' {
switch ($VMEnvironment) {
'Test' { $EnvString = "Non-Prod" }
'Production' { $EnvString = "Prod" }
}
}
Default {
switch ($VMEnvironment) {
'Test' { $EnvString = "Test" }
'Production' { $EnvString = "Prod" }
}
}
}
# Gather all sub OUs for the AppName OU
$SubOUs = Get-ADOrganizationalUnit -Server $DomainName -SearchBase $OUAppName -Filter *
If (@($SubOUs).count -eq 1) {
$OUToUse = $SubOUs
}
Else {
$OUToUse = $SubOUs | Where-Object { $_.DistinguishedName -like ("OU=$EnvString,OU=" + $OUAppName.Name + "*") }
}
# Determine if AD Computer object already exists
$ExistingADComputer = Get-ADComputer -Filter { Name -eq $HostName }
If ($ExistingADComputer) {
Write-Verbose -Message "AD Object already exists in AD"
# Determine if AD Computer object is in the correct OU, or a sub-OU of the correct OU
If ($ExistingADComputer.DistinguishedName -like ("*" + $OUToUse.DistinguishedName)) {
Write-Verbose -Message "AD Object is in the correct OU, will attempt domain join"
$AttemptDomainJoin = $true
}
Else {
Write-Verbose -Message "AD Object is NOT in the correct OU, domain join will not occur, needs human review"
$AttemptDomainJoin = $false
}
}
Else {
$AttemptDomainJoin = $true
}
If ($AttemptDomainJoin) {
$OuFinal = $OUToUse.DistinguishedName
$FirstScriptBlock = { $DomainJoinCred = New-Object System.Management.Automation.PSCredential('svcitdvmdomainjoin', ('hypes-Vgv8h89' | ConvertTo-SecureString -AsPlainText -Force)) }
$SecondScriptText = 'Add-Computer -DomainName <DomainName> -OUPath "<OuPath>" -Credential $DomainJoinCred -Server itddc42.nd.gov'
$SecondScriptText = $SecondScriptText -replace '<DomainName>', $DomainName
$SecondScriptText = $SecondScriptText -replace '<OuPath>', $OuFinal
Write-Verbose -Message "[$FQDN]:Invoke-VMScript to AD join"
$InvokeVMScriptFunc = [System.Management.Automation.ScriptBlock]::Create("$FirstScriptBlock ; $SecondScriptText")
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText $InvokeVMScriptFunc
Write-Warning -Message "[$FQDN]:Restart VMGuest, wait for Tools, then 90 seconds after"
Get-VM -Name $FQDN | Restart-VMGuest -Confirm:$false
Wait-Tools -VM (Get-VM -Name $FQDN)
Start-Sleep -Seconds 90
}
}
else {
$GuestCredentialAB = $GuestCredentialBB
}
<#
Write-Verbose -Message ("[$FQDN]:Copying SCCM client installer to C:\temp... " + (Get-Date))
Copy-VMGuestFile -Source C:\SCCM_Client\ -Destination C:\temp\SCCM_Client -VM (Get-VM -Name $FQDN) -LocalToGuest -GuestCredential $GuestCredentialAB -Force
Write-Verbose -Message ("[$FQDN]:SCCM client copy complete " + (Get-Date))
#E:\AutoBuild\SCCM_Client\
# Check if SCCM automatically installed the SCCM client and registered it
$CcmRegistered = $false
$CcmRegistration = Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
Get-Content C:\windows\ccm\logs\ClientIDManagerStartup.log
}
If ($CcmRegistration.ScriptOutput -match "Client is registered") {
Write-Warning "Client is registered."
$CcmRegistered = $true
}
ElseIf ($CcmRegistration.ScriptOutput -match "Client is already registered") {
Write-Warning "Client is already registered."
$CcmRegistered = $true
}
If ($CcmRegistered -eq $false) {
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
If (Get-Process -Name ccmsetup -ErrorAction SilentlyContinue) {
Write-Warning "CCM client is already installing"
$CcmRegistered = $true
}
ElseIf (Get-Process -Name ccmexec -ErrorAction SilentlyContinue) {
Write-Warning "CCM client is already installed"
$CcmRegistered = $true
}
Else {
Write-Warning -Message "Installing SCCM Client..."
Invoke-Expression -Command "C:\temp\SCCM_Client\ccmsetup.exe SMSSITECODE=ITD SMSMP=itdsccmp2.nd.gov DNSSUFFIX=nd.gov"
}
}
}
Write-Verbose -Message "[$FQDN]:Register SCCM Client"
While ($CcmRegistered -eq $false) {
$CcmRegistration = Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
Get-Content C:\windows\ccm\logs\ClientIDManagerStartup.log
}
If ($CcmRegistration.ScriptOutput -match "Client is registered") {
Write-Warning "Client is registered."
$CcmRegistered = $true
}
ElseIf ($CcmRegistration.ScriptOutput -match "Client is already registered") {
Write-Warning "Client is already registered."
$CcmRegistered = $true
}
ElseIf ($CcmRegistered -eq $false) { Start-Sleep -Seconds 30 }
}
Write-Verbose -Message "[$FQDN]:Approve SCCM Client"
#Start-Sleep -Seconds 30 # ADD LOOP/SMARTS TO WAIT FOR DISCOVERY AND ANOTHER FOR APPROVAL
Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock {
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
$PSDrives = Get-PSDrive
If ($PSDrives | Where-Object Name -EQ "ITD") {
# ITD Drive exists, do nothing
}
else {
New-PSDrive -Name "ITD" -PSProvider AdminUI.PS.Provider\CMSite -Root itdsccmp2.nd.gov
}
Set-Location ITD:\
$Device = Get-CMDevice -Name $args[0]
If ($Device.IsApproved -eq 0) {
Approve-CMDevice -DeviceName $args[0]
}
}
Write-Verbose -Message "Trigger SCCM MachinePolicy First Check-in"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
[void] ([wmiclass] "\\localhost\root\ccm:SMS_Client").TriggerSchedule("{00000000-0000-0000-0000-000000000021}");
}
Write-Verbose -Message "Trigger SCCM MachinePolicy"
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
[void] ([wmiclass] "\\localhost\root\ccm:SMS_Client").TriggerSchedule("{00000000-0000-0000-0000-000000000021}");
}
<#Write-Verbose -Message "Waiting for network connectivity / Then KVM Activation..."
Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialAB -ScriptType PowerShell -ScriptText {
# Pause until network connectivity is available
$KMS = 'kms.nd.gov'
try {
$nwJob = Start-Job -Name 'NetworkCheck' -ScriptBlock {
Param ( [String]$KMS )
do {
$nwStatus = Test-NetConnection -ComputerName $KMS -Port 1688 -InformationLevel Quiet
Start-Sleep -Seconds 10
} until($nwStatus)
} -ArgumentList $KMS
# If after 30 seconds the network connection is not responding continue on
if ((Wait-Job -Job $nwJob -Timeout 30).State -eq 'Completed') {
Write-Verbose -Message 'Network connectivity has been verified.'
}
else {
[void](Stop-Job -Job $nwJob)
Write-Verbose -Message 'Network connectivity could not be verified.'
}
}
catch {
Throw $_
Break
}
Activate via KMS
Write-Verbose -Message "Activating windows against $KMS..."
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit
}
try {
cscript C:\Windows\System32\slmgr.vbs /skms $KMS | Out-Null
cscript C:\Windows\System32\slmgr.vbs /ato | Out-Null
Write-Verbose -Message "Checking activation status..."
$kmsOut = cscript C:\Windows\System32\slmgr.vbs /dli
if (($kmsOut | Select-String -Pattern '^License Status:') -match 'Licensed') {
Write-Verbose -Message "Windows successfully activated."
}
else {
Write-Verbose -Message "Windows failed to activate, run slmgr commands manually. Ensure server time is correct."
Write-Warning -Message "Windows failed to activate, run slmgr commands manually. Ensure server time is correct."
}
}
catch {
Throw $_
Break
}
}
#>
Write-Verbose -Message ("[$FQDN]:Add to Solarwinds")
$Func = {
param($C)
Import-Module -Name ITDSolarwinds -Verbose
Import-SWDiscovery -ComputerName $C -Integration ServiceNow
}
Invoke-Command -ComputerName itdslrwnds.nd.gov -ScriptBlock $Func -ArgumentList $FQDN -Credential $Credential
Write-Verbose -Message "[$FQDN]:End"
}
end {
}
}
@@ -0,0 +1,290 @@
<#
.SYNOPSIS
Decomissions a Windows Server device
.DESCRIPTION
Decomissions a Windows Server device with ITD specifications (DNS/Infoblox, Active Directory, SCCM)
.NOTES
Credential must be an nd.gov account with access to remove objects on all platforms involved: DNS/Infoblox, Active Directory, SCCM. Read access to vCenter in the future.
.EXAMPLE
Remove-ITDWindowsServer -ComputerName itdxxx.nd.gov -SCTaskNum SCTASKxxxxxxxxx -Credential $AdminCredential
#>
function Remove-ITDWindowsServer {
[CmdletBinding()]
param (
[string]
$ComputerName,
[string]
$SCTaskNum,
[Parameter(Mandatory = $true)]
[PSCredential]
$Credential
)
begin {
$RadiusCred = New-Object System.Management.Automation.PSCredential($Credential.username.split('\')[1], ($Credential.Password))
}
process {
$HostName = $ComputerName.split('.')[0]
# get current user, SCTask, Ritm, custom variables
switch ($env:username) {
'svcitdiaasauto' {
$assignTo = Get-ITDServiceNowUser -Username svcvmwareadm
}
Default {
$assignTo = Get-ITDServiceNowUser -Username $Env:username
}
}
$SCTask = Get-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum
$RitmNum = $SCTask.request_item.display_value
Write-Verbose -Message "Gathering $RitmNum"
$Ritm = Get-ITDServiceNowRecord -ItemType 'Request Item' -Number $RitmNum -IncludeVariableSet -ErrorAction Stop
###### the name in the $ComputerName parameter must match a name in the form to continue
Write-Verbose -Message "Gathering VariableSet data from $RitmNum"
$MatchFound = $false
ForEach ($Row in $Ritm.VariableSet) {
$TempCi = Get-ITDServiceNowRecord -Table cmdb_ci -SysId ($Row.host_name_ref) -ErrorAction Stop
If ($ComputerName -eq $TempCi.FQDN.display_value) {
$Ci = $TempCi
$MatchFound = $true
}
}
$FQDN = $Ci.fqdn.display_value
If ($MatchFound -eq $false) {
Write-Error -Message "ComputerName $ComputerName was not found in VariableSet for $RitmNum" -ErrorAction Stop
}
Write-Verbose -Message "Assigning SCTask to current user, maybe"
##### Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{assigned_to = $assignTo.name } REVIEW IF ASSIGNMENT SHOULD BE MADE HERE
$short_description = $SCTask.short_description.display_value
# Gather DNS FQDN and IP
$DNSResolve = Resolve-DnsName -Name $ComputerName -ErrorAction SilentlyContinue
If ($DNSResolve -eq $null) {
$DNSResolve = "DNS object not found"
}
$DNSInfo = $DNSResolve | ConvertTo-Json -WarningAction SilentlyContinue
# AD OU
$ADComputers = $null
$Domain = $ComputerName.Substring($ComputerName.IndexOf('.') + 1)
switch ($Domain) {
'nd.gov' {
try {
$ADComputers = Get-ADComputer -Identity $HostName -Properties * -ErrorAction SilentlyContinue
}
catch {
# empty because erroraction silentlycontinue doesn't work as expected
}
If ($ADComputers) {
$ADInfo = $ADComputers | ConvertTo-Json -WarningAction SilentlyContinue
}
Else {
$ADInfo = "AD object not found"
}
}
'Default' {
Write-Warning -Message "$ComputerName not nd.gov, review other domains manually"
}
}
# SCCM collections
$SCCMResult = Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname -ScriptBlock {
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
Set-Location ITD:\
$CMDevice = Get-CMDevice -Name $args[0]
If ($CMDevice) {
Get-ITDCMDeviceMemberOf -ComputerName $args[0]
}
Else {
"SCCM object not found"
}
}
$SCCMInfo = $SCCMResult | ConvertTo-Json -WarningAction SilentlyContinue
Write-Verbose -Message "Updating SCTask with discovered information"
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("DNS Information: `n " + $DNSInfo) }
If ($vCenterInfo) {
}
If ($AzureInfo) {
}
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("Active Directory Information: `n " + $ADInfo) }
Update-ITDServiceNowRecord -ItemType "Catalog Task" -Number $SCTaskNum -Values @{work_notes = ("SCCM Information: `n " + $SCCMInfo) }
$CommentsForWorkNotes = $null
try {
$Notes = '<div><strong><span style="color: #c00000;">Decommissioned ' + (Get-Date -UFormat "%Y/%m/%d %H:%M:%S") + '</span></strong></div>'
Write-Verbose -Message "All records with Title matching $ComputerName were marked as decommissioned in the Notes field."
Update-ITDPassword -Title $ComputerName -AppendNotes -Notes $Notes -Credential $Credential -Force
$CommentsForWorkNotes += ("`nPasswordstate: All records with Title matching $ComputerName were marked as decommissioned in the Notes field. ")
}
catch {
}
# Power off VM
switch ($Ci.model_id.display_value) {
{ $_ -like "*VMware*" } {
$hardware_platform = "VMware";
$hardware_type = 'Virtual Machine'
$VMs = Get-VM -Name $FQDN -ErrorAction SilentlyContinue | Where-Object { $_.ExtensionData.summary.config.ManagedBy.Type -ne "placeholderVm" }
switch ( @($VMs).count ) {
{ 0 } {
Write-Warning "$FQDN not found in vCenter... is it Azure? Or does it not exist?"
}
{ $_ -gt 1 } {
Write-Verbose -Message "Multiple virtual machines with name $FQDN were found."
Write-Error -Message ("Multiple virtual machines with name $FQDN were found. Are there SRM placeholders? If so, unconfigure SRM and run this again. If there are no placeholders, confirm the virtual machine name.") -ErrorAction Stop
}
1 {
Write-Verbose -Message 'One virtual machine with name $FQDN were found.'
$TagAssignment = Get-TagAssignment -Entity $VMs
$vCenterInfo = $TagAssignment | select Tag, Entity | ConvertTo-Json -Depth 1
If ($VMs.PowerState -eq 'PoweredOn') {
Write-Verbose -Message "Power off VMware VM $FQDN"
$CommentsForWorkNotes += ("`nVMware: VM $FQDN has been powered off. ")
$VMs | Stop-VM -Confirm:$false
}
If ($vCenterInfo) {
# enter work_notes into sctask
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{
work_notes = ("vCenter Information: `n " + $vCenterInfo)
}
}
}
}
}
{ $_ -like "*Microsoft Virtual Machine*" } {
$hardware_platform = "Azure";
$hardware_type = 'Virtual Machine'
}
{ $_ -like "*HP*" } {
$hardware_platform = 'HPE';
$hardware_type = 'Physical'
}
default { $hardware_platform = 'Other' }
}
# SCCM removal
If ($SCCMResult -eq "SCCM object not found") {
# do nothing
}
Else {
Write-Verbose -Message "Attempt SCCM removal"
try {
$Domain = $ComputerName.Substring($ComputerName.IndexOf('.') + 1)
switch ($Domain) {
'nd.gov' { $SCCMDomain = 'NDGOV' }
'itd.nd.gov' { $SCCMDomain = 'NDGOV' }
'k12.nd.us' { $SCCMDomain = 'K12' }
'stg.k12.nd.us' { $SCCMDomain = 'K12STG' }
'ndcloud.gov' { $SCCMDomain = 'NDCLOUD'}
}
Write-Verbose -Message ("$Computername is SCCM Domain $SCCMDomain")
Invoke-Command -ComputerName itdsccmp2.nd.gov -Credential $Credential -ArgumentList $Hostname, $SCCMDomain -ScriptBlock {
Import-Module 'D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
Set-Location ITD:\
$Devices = Get-CMDevice -Name $args[0] | Where-Object Domain -EQ $args[1]
$Devices | select Name, Domain
ForEach ($Device in $Devices) {
If ($Device.Domain -match $SCCMDomain) {
Write-Verbose -Message ("SCCM: Removing " + $Device.Name + " on " + $Device.Domain + " domain")
$Device | Remove-CMDevice -Confirm:$false -Force
}
}
}
$CommentsForWorkNotes += ("`nSCCM: Device named $Hostname was removed. ")
}
catch {
}
}
# Active Directory removal
If ($ADInfo -eq "AD object not found") {
# do nothing
}
Else {
try {
switch ($Domain) {
'nd.gov' {
$ADComputers = Get-ADComputer -Identity $HostName
switch ( @($ADComputers).count ) {
{ 0 } { "AD: Not found" }
{ $_ -gt 1 } { "AD: More than one found" }
{ 1 } {
Write-Verbose -Message "AD: One found, removing"
$ADComputers
$ADComputers | Remove-ADObject -Recursive -Credential $Credential -Confirm:$false
}
}
$CommentsForWorkNotes += ("`nActive Directory: AD computer object with name $Hostname was removed from the nd.gov domain. ")
}
'Default' {
Write-Warning -Message "$ComputerName not nd.gov"
}
}
}
catch {
}
}
# DNS removal
If ($DNSResolve -eq "DNS object not found") {
# do nothing
}
Else {
try {
Write-Verbose "Attempting DNS removal"
Remove-ITDIbDnsRecord -ComputerName $ComputerName -Credential $RadiusCred
$CommentsForWorkNotes += ("`nInfoblox: DNS A Record was removed for $ComputerName. ")
}
catch {
}
}
# Solarwinds removal
try {
Remove-ITDSolarwindsNode -ComputerName $ComputerName -Credential $Credential
$CommentsForWorkNotes += ("`nSolarwinds: Node was removed for $ComputerName. ")
}
catch {
}
$CommentsForWorkNotes += ("`n `nWindows OS Decommission completed. $hardware_platform $hardware_type hardware is ready for removal. ")
$HardwareRemovalDescription = ("$ComputerName $hardware_platform $hardware_type hardware is ready for removal.")
Write-Verbose -Message "Add final comments for work notes into the SCTask, update the short_description for next workflow step"
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{
work_notes = $CommentsForWorkNotes;
short_description = $HardwareRemovalDescription;
}
}
end {
}
}
@@ -0,0 +1,19 @@
# figure out where to put it
$x = Get-ADComputer -Filter * -Server itdk12dc7.k12.nd.us -Credential $K12Cred -Properties CanonicalName
# enter this on the machine itself
$Credential = Get-Credential
$AddComputerParams = @{
DomainName = 'k12.nd.us';
OUPath = 'OU=Prod,OU=All-General,OU=Computers,OU=ITD,DC=k12,DC=nd,DC=us' ;
Credential = $Credential;
}
Add-Computer @AddComputerParams
@@ -0,0 +1,29 @@
$Step1Complete = $null
$SCTaskNum = ''
try {
$FQDN = 'itdcndhh22ywt.nd.gov'
$CIDR = '10.221.10.96/28'
New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred
Write-Verbose -Message "New-ITDWindowsVmVMwareStep1 function completed"
$Step1Complete = $true
}
catch {
#Write-Error -Message $error[0]
$Step1Complete = $false
$ErrorText = ($_.ErrorDetails.message | ConvertFrom-Json).text
If ($ErrorText -match "Cannot find 1 available IP address" ) {
$Msg = "Resolve the issue and resubmit the Server Build Request catalog item. Setting $SCTaskNum State to Closed Incomplete"
Write-Warning -Message $Msg
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTaskNum -Values @{ #>
work_notes = ("VMware build step 1 errored. $Msg. `nPSU Job Id #" + '52928' + " `n" + $ErrorText)
state = 'Closed Incomplete'
close_notes = ("VMware build step 1 errored. $Msg. `nPSU Job Id #" + '52928' + " `n" + $ErrorText)
}
}
throw
}
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.Infra-ActiveDirectory.Object'
variables:
major: 0
minor: 1
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.Infra-ActiveDirectory.Object.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.Infra-ActiveDirectory.Object.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.Infra-ActiveDirectory.Object'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.Infra-ActiveDirectory.Object).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.Infra-ActiveDirectory.Object</id>
<version>$VERSIONHERE$</version>
<authors>Zack Meier</authors>
<description>Functions for Windows Server Active Directory User, Group, and Computer tasks</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>
@@ -0,0 +1,132 @@
#
# Module manifest for module 'ITD.Windows'
#
# Generated by: zmeier
#
# Generated on: 6/14/2022
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'ITD.Infra-ActiveDirectory.Object.psm1'
# Version number of this module.
ModuleVersion = '<ModuleVersion>'
# Supported PSEditions
# CompatiblePSEditions = @()
# ID used to uniquely identify this module
GUID = 'bfe6483c-14b2-42e9-8d98-62482eb4569c'
# Author of this module
Author = 'zmeier'
# Company or vendor of this module
CompanyName = 'State of North Dakota'
# Copyright statement for this module
Copyright = '(c) zmeier. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Functions for Active Directory object (users and computers) administration'
# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# ClrVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(<FunctionsToExport>)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,23 @@
#Get public and private function definition files.
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in @($Public + $Private))
{
Try
{
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
# Here I might...
# Read in or create an initial config file and variable
# Export Public functions ($Public.BaseName) for WIP modules
# Set variables visible to the module and its functions only
Export-ModuleMember -Function $Public.Basename
@@ -0,0 +1,49 @@
function Disable-ITDADUser
{
[CmdletBinding()]
Param
(
[string]
$Identity,
[PSCredential]
$Credential
)
Begin
{
Write-Verbose "Validate credentials, stop script if invalid."
If($Credential -eq "" -or $Credential -eq $null)
{
$Credential = Get-Credential -Message "Enter domain/OU administrator credentials. User name must be entered as a SAMAccountName (DOMAIN\username) or as a User Principal Name (username@domain.com)" -UserName $Credential
If($Credential -eq "" -or $Credential -eq $null)
{
Write-Warning "credentials missing - stopping script"
break
}
If((Test-ADCredential -Credential $Credential -ErrorAction Stop) -eq $false)
{
Write-Warning "Invalid credentials or locked account."
break
}
}
Import-Module ActiveDirectory
}
Process
{
$OUdestination = "OU=DisabledAccounts,OU=USERS,OU=ITD,DC=ND,DC=GOV"
ForEach($username in $Identity)
{
Write-Verbose "[$Username]:Processing"
$object = Get-ADUser -Identity $username
Write-Verbose "[$Username]:Disabling Object"
$object | Set-ADuser -Enabled $false -Credential $Credential
Write-Verbose "[$Username]:Moving Object"
$object | Move-ADObject -TargetPath $OUdestination -Credential $Credential
}
}
End
{
}
}
@@ -0,0 +1,27 @@
function Get-ITDADActiveServer
{
[CmdletBinding()]
Param
(
[int]
$ExpireAgeDays = 30
)
Begin
{
Import-Module ActiveDirectory
$OUsource = "OU=ITD,DC=ND,DC=GOV"
$ExpireDate = (Get-Date).AddDays((-$ExpireAgeDays))
}
Process
{
Get-ADComputer -SearchBase $OUsource -Filter * -Properties Name,CanonicalName,operatingSystem,operatingSystemServicePack,LastLogonDate,Enabled | `
Where-Object operatingSystem -Like "*Server*" | `
Where-Object LastLogonDate -GT $ExpireDate | `
Where-Object Enabled -EQ $true | `
Select-Object Name,operatingSystem,operatingSystemServicePack,LastLogonDate,CanonicalName
}
End
{
}
}
@@ -0,0 +1,30 @@
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function Get-ITDADGroupMember
{
[CmdletBinding()]
Param
(
[string]
$Identity
)
Begin
{
}
Process
{
return (Get-ADUser -Identity $Identity -Properties MemberOf).MemberOf
}
End
{
}
}
@@ -0,0 +1,68 @@
<#
.Synopsis
Creates AD Computer object in ITD OUs
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function New-ITDADComputerServer
{
[CmdletBinding()]
Param
(
[string[]]
$ComputerName,
#[string]
#$AppName,
[PSCredential]
$Credential
)
Begin
{
Write-Verbose "Validate credentials, stop script if invalid."
If($Credential -eq "" -or $Credential -eq $null)
{
$Credential = Get-Credential -Message "Enter domain/OU administrator credentials. User name must be entered as a SAMAccountName (DOMAIN\username) or as a User Principal Name (username@domain.com)" -UserName $Credential
If($Credential -eq "" -or $Credential -eq $null)
{
Write-Warning "credentials missing - stopping script"
break
}
If((Test-ADCredential -Credential $Credential -ErrorAction Stop) -eq $false)
{
Write-Warning "Invalid credentials or locked account."
break
}
}
Import-Module ActiveDirectory
$OUdefault = "OU=Prod,OU=All-General,OU=Windows,OU=SERVERS,OU=COMPUTERS,OU=ITD,DC=ND,DC=GOV"
}
Process
{
ForEach($c in $ComputerName)
{
$Hostname=($c.split(".")[0]).ToUpper()
#If($AppName)
#{
#}
#Else
#{
$OUdestination = $OUdefault
#}
New-ADComputer -Name $Hostname -Path $OUdestination -Credential $Credential
}
}
End
{
}
}
@@ -0,0 +1,93 @@
<#
.Synopsis
Create AD group within ITD GROUPS OU
.DESCRIPTION
Create Active Directory group within the ITD\ITD GROUPS OU, ability to add group members if needed
.EXAMPLE
New-ITDADGroup -SamAccountName ITD-GROUP-1 -Description "Sales group"
.EXAMPLE
New-ITDADGroup -SamAccountName ITD-GROUP-1 -Description "Sales group" -Members username1,username2,username3
#>
function New-ITDADGroup
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[string]
$SamAccountName,
[Parameter(Mandatory=$true)]
[string]
$Description,
[string[]]
$Members,
[PSCredential]
$Credential
)
Begin
{
Write-Verbose "Validate credentials, stop script if invalid."
If($Credential -eq "" -or $Credential -eq $null)
{
$Credential = Get-Credential -Message "Enter domain/OU administrator credentials. User name must be entered as a SAMAccountName (DOMAIN\username) or as a User Principal Name (username@domain.com)" -UserName $Credential
If($Credential -eq "" -or $Credential -eq $null)
{
Write-Warning "credentials missing - stopping script"
break
}
If((Test-ADCredential -Credential $Credential -ErrorAction Stop) -eq $false)
{
Write-Warning "Invalid credentials or locked account."
break
}
}
Import-Module ActiveDirectory
}
Process
{
Write-Verbose "verify group object does not already exist, if it does, stop script"
$groupexists = Get-ADGroup -Filter {sAMAccountName -eq $SamAccountName}
If($groupexists)
{
Write-Warning "$SamAccountName already exists"
break
}
Write-Verbose "fix description if needed"
If($Description -like "*1120*")
{
Write-Verbose "no change to description"
}
Else
{
Write-Verbose "adding '1120 - ' to description"
$Description = "1120 - " + $Description
}
$OUdestination = "OU=ITDGROUPS,OU=GROUPS,OU=ITD,DC=ND,DC=GOV"
Write-Verbose "create group in AD"
New-ADGroup -Name $SamAccountName `
-SamAccountName $SamAccountName `
-Description $Description `
-DisplayName $SamAccountName `
-GroupScope Global `
-GroupCategory Security `
-Path $OUdestination `
-Credential $Credential
Write-Verbose "Adding group members if applicable"
If($Members)
{
Add-ADGroupMember -Identity $SamAccountName -Members $Members -Credential $Credential
}
}
End
{
}
}
@@ -0,0 +1,123 @@
<#
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
A longer description of the function, its purpose, common use cases, etc.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
Test-MyTestFunction
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
#>
function New-ITDADServiceAccount {
[CmdletBinding()]
param (
[string]
$SamAccountName,
[Parameter(Mandatory = $true)]
[string]
$Description,
[Parameter(Mandatory = $true)]
[ValidateSet('Office365', 'VMware_Systems', 'CSRC', 'Shared Linux Password List', 'Peoplesoft Share PW', 'Cohesity', 'VDI')]
[string]
$PasswordstateList,
[Parameter(Mandatory = $true)]
[string]
$PasswordstateTitle,
[string]
$PasswordstateNotes,
[pscredential]
$Credential
)
begin {
}
process {
Write-Verbose -Message "Verify if user object already exists in Active Directory"
try {
If (Get-ADUser -Identity $SamAccountName) {
$ADUserExists = $true
}
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
Write-Verbose -Message "Active Directory user object not found"
$ADUserExists = $false
}
catch {
Write-Error -Message "Unable to validate if samaccountname $SamAccountName is available" -ErrorAction $Stop
}
Write-Verbose -Message "ADUser exists $ADUserExists"
switch ($ADUserExists) {
Default {
Write-Error -Message "Unable to validate if samaccountname $SamAccountName is available"
}
$true {
Write-Error -Message "AD user object with $SamAccountName SamAccountName already exists."
}
$false {
Write-Verbose -Message "Create Passwordstate record"
$NewITDPasswordParams = @{
PasswordList = $PasswordstateList;
Title = $PasswordstateTitle;
Description = $Description;
UserName = ("ndgov\$SamAccountName");
Credential = $Credential;
}
switch ($PSBoundParameters.Keys) {
PasswordStateNotes {
$NewITDPasswordParams.Notes = $PasswordstateNotes
}
}
$NewITDPasswordResult = New-ITDPassword @NewITDPasswordParams -ErrorAction Stop
If ($NewITDPasswordResult) {
Write-Verbose -Message "Create AD account"
$OuDestination = "OU=ITD SERVICE,OU=USERS,OU=ITD,DC=ND,DC=GOV"
$DCtoUse = Get-ADDomainController -DomainName nd.gov -Discover -Site "Default-First-Site-Name"
$NewADUserParams = @{
Name = $SamAccountName;
SamAccountName = $SamAccountName;
UserPrincipalName = "$SamAccountName@nd.gov";
Description = "1120 - $Description";
Surname = "$SamAccountName";
DisplayName = "$SamAccountName";
Path = $OuDestination;
AccountPassword = $NewITDPasswordResult.Password;
PasswordNeverExpires = $true;
Enabled = $true;
Credential = $Credential;
Server = $DCtoUse;
}
#try {
Write-Verbose -Message "Attempt New-ADUser"
New-ADUser @NewADUserParams
#}
#catch {
#Write-Error $error[0]
#}
}
}
}
}
end {
}
}
@@ -0,0 +1,118 @@
<#
.Synopsis
Create new account with random password, save in Passwordstate
.DESCRIPTION
Create new Active Directory user account in the "ITD SERVICE" OU, randomly generate a password, and save it in Passwordstate
.EXAMPLE
New-ITDADServiceAccount -SamAccountName !itdtest01 -Description "app/sql db account" -ComputerName itdtest01.nd.gov -PasswordStateList CSRC -Credential <PSCredential>
#>
function New-ITDADServiceAccountOLD
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[string]
$SamAccountName,
[Parameter(Mandatory=$true)]
[string]
$Description,
[Parameter(Mandatory=$true)]
[string]
$ComputerName,
[Parameter(Mandatory=$true)]
[ValidateSet("CSRC","CND","Linux","Office365","VMware","ZTEST")]
[string]
$PasswordstateList,
[PSCredential]
$Credential
)
Begin
{
Write-Verbose "Validate credentials, stop script if invalid."
If($Credential -eq "" -or $Credential -eq $null)
{
$Credential = Get-Credential -Message "Enter domain/OU administrator credentials. User name must be entered as a SAMAccountName (DOMAIN\username) or as a User Principal Name (username@domain.com)" -UserName $Credential
If($Credential -eq "" -or $Credential -eq $null)
{
Write-Warning "credentials missing - stopping script"
break
}
If((Test-ADCredential -Credential $Credential -ErrorAction Stop) -eq $false)
{
Write-Warning "Invalid credentials or locked account."
break
}
}
Write-Verbose "Confirm Passwordstate connection"
If((Test-NetConnection -ComputerName itdpv.nd.gov).PingSucceeded)
{
}
Else
{
Write-Warning "Passwordstate unavailable"
break
}
Import-Module ActiveDirectory
}
Process
{
Write-Verbose "verify user account does not already exist, if it does, stop script"
$userexists = Get-ADUser -Filter {sAMAccountName -eq $SamAccountName}
If($userexists)
{
Write-Warning "$SamAccountName already exists"
break
}
Write-Verbose "fix description if needed"
If($Description -like "*1120*")
{
Write-Warning "Do not enter '1120' into the description, this will be done for you"
Break
}
Write-Verbose "set OU, get passwordstate passwordlist information, set ADDescription"
$OUdestination = "OU=ITD SERVICE,OU=USERS,OU=ITD,DC=ND,DC=GOV"
$PStateList = Get-ITDPasswordstatePasswordList -Name $PasswordstateList
$ADDescription = "1120 - " + $Description
<# removed 20181228
Write-Verbose "Generate new password"
$PasswordGenerated = New-ITDRandomPassword
$PasswordSecured = $PasswordGenerated | ConvertTo-SecureString -AsPlainText -Force
Write-Verbose "add to passwordstate"
$Date = Get-Date -UFormat "%Y/%m/%d @ %H:%M:%S"
$Notes = "Automatically generated by $env:USERNAME on $Date"
New-PasswordstateRecord -ListID $PStateList.ID -Title $ComputerName -Username "nd.gov\$SamAccountName" -APIkey $PStateList.APIkey -Password $PasswordGenerated -Description $Description -Notes $Notes
#>
New-ITDPasswordstateRecord -Title $ComputerName -Description $ADDescription -PSList $PasswordstateList -Username $SamAccountName -GeneratePassword
Write-Verbose "create account in AD"
New-ADUser -Name $SamAccountName `
-SamAccountName $SamAccountName `
-UserPrincipalName "$SamAccountName@nd.gov" `
-Description $ADDescription `
-DisplayName "$SamAccountName" `
-Path $OUdestination `
-AccountPassword $PasswordSecured `
-PasswordNeverExpires $true `
-Enabled $true `
-Credential $Credential
}
End
{
}
}
@@ -0,0 +1,88 @@
<#
.SYNOPSIS
A short one-line action-based description, e.g. 'Tests if a function is valid'
.DESCRIPTION
Function will submit a ServiceNow Catalog Request of Application Server type with relevant information required for automated AD Service Account creation.
.NOTES
Information or caveats about the function e.g. 'This function is not supported in Linux'
.LINK
Specify a URI to a help page, this will show when Get-Help -Online is used.
.EXAMPLE
Test-MyTestFunction -Verbose
Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
#>
function New-ITDADServiceAccountRitm {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$RequestedForEmail,
[Parameter(Mandatory = $true)]
[string]
$SamAccountName,
[Parameter(Mandatory = $true)]
[ValidateSet('nd.gov')]
[string]
$ADDomain,
[Parameter(Mandatory = $true)]
[string]
$Description,
[Parameter(Mandatory = $true)]
#[ValidateSet('Office365', 'VMware_Systems', 'CSRC', 'Shared Linux Password List', 'Peoplesoft Share PW', 'Cohesity', 'VDI')]
[string]
$PasswordstateList,
[Parameter(Mandatory = $true)]
[string]
$PasswordstateTitle
)
begin {
}
process {
# create Application Server RITM with json
$AdditionalComments = "Please create a new $ADDomain Active Directory service account with the following details, following guidelines found in KB0016867.`n`n"
$obj = [PSCustomObject]@{
RequestedForEmail = $RequestedForEmail
SamAccountName = $SamAccountName;
ADDomain = $ADDomain;
PasswordstateTitle = $PasswordstateTitle;
PasswordstateList = $PasswordstateList;
Description = $Description;
}
$AdditionalComments += ($obj | ConvertTo-Json -Compress)
$NewITDServiceNowServiceCatalogRequest = @{
CategoryItemName = 'Application Server';
RequestedForEmail = $RequestedForEmail;
Values = @{
additional_comments = $AdditionalComments;
request_type = "New";
application_name = "Infra-ActiveDirectory.Object";
environment = "Production";
require_hosting_quote = 'No';
add_change_disaster_recovery = 'No'; #>
vm_work_needed = 'No';
}
}
$ReqResult = New-ITDServiceNowServiceCatalogRequest @NewITDServiceNowServiceCatalogRequest
}
end {
Write-Output $ReqResult
}
}
@@ -0,0 +1,80 @@
<#
.Synopsis
Unlock any Active Directory Account
.DESCRIPTION
Unlock any Active Directory Account, verify information
.EXAMPLE
Unlock-ITDADAccount -Identity username1
.EXAMPLE
Unlock-ITDADAccount -Identity username1, username2, username3
.EXAMPLE
Unlock-ITDADAccount -Identity username1 -Credential $PSCredential
.INPUTS
Inputs to this cmdlet (if any)
.OUTPUTS
Output from this cmdlet (if any)
.NOTES
General notes
.COMPONENT
The component this cmdlet belongs to
.ROLE
The role this cmdlet belongs to
.FUNCTIONALITY
The functionality that best describes this cmdlet
#>
function Unlock-ITDADAccount
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true)]
[string[]]
$Identity,
[PSCredential]
$Credential
)
Begin
{
Write-Verbose "Validate credentials, stop script if invalid."
If($Credential -eq "" -or $Credential -eq $null)
{
$Credential = Get-Credential -Message "Enter domain/OU administrator credentials. User name must be entered as a SAMAccountName (DOMAIN\username) or as a User Principal Name (username@domain.com)" -UserName $Credential
If($Credential -eq "" -or $Credential -eq $null)
{
Write-Warning "credentials missing - stopping script"
break
}
If((Test-ADCredential -Credential $Credential -ErrorAction Stop) -eq $false)
{
Write-Warning "Invalid credentials or locked account."
break
}
}
.3
Import-Module ActiveDirectory
}
Process
{
ForEach ($i in $Identity)
{
$before = Get-ADUser -Identity $i -Properties SamAccountName,PasswordLastSet,lastLogonDate,Enabled,LockedOut | Select-Object SamAccountName,PasswordLastSet,lastLogonDate,Enabled,LockedOut
$SamAccountName = $before.SamAccountName
If($before.LockedOut -eq $false)
{
Write-Warning "[$SamAccountName]:Before:$before"
}
Else
{
Unlock-ADAccount -Identity $i -Credential $Credential
$after = Get-ADUser -Identity $i -Properties SamAccountName,PasswordLastSet,lastLogonDate,Enabled,LockedOut | Select-Object SamAccountName,PasswordLastSet,lastLogonDate,Enabled,LockedOut
Write-Warning "[$SamAccountName]:After:$after"
}
}
}
End
{
}
}
@@ -0,0 +1,20 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
# Build and Test
TODO: Describe and show how to build your code and run the tests.
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
@@ -0,0 +1,147 @@
[CmdletBinding()]
param (
[string]
$SCTaskNum,
[switch]
$Quiet
)
begin {
$StartTime = Get-Date
New-ITDServiceNowSession -Environment Production -Credential $Secret:SNowVMCred
}
process {
$SCTaskSearch = Get-ITDServiceNowRecord -ItemType 'Catalog Task' -Filter ('active=true^short_description=Active Directory Service Account Provisioning') -Verbose
switch ($PSBoundParameters.Keys) {
'SCTaskNum' {
$SCTaskSearch = $SCTaskSearch | Where-Object Number -EQ $SCTaskNum
}
}
Switch (@($SCTaskSearch).count) {
{ $_ -le 0 } {
Write-Verbose -Message "No Active Directory Service Account Provisioning tasks found." -Verbose
}
{ $_ -ge 1 } {
Write-Verbose -Message ("Number of Active Directory Service Account Provisioning tasks found: " + @($SCTaskSearch).count) -Verbose
}
}
ForEach ($SCTask in $SCTaskSearch) {
Clear-Variable -Name RITM, obj, NewITDADServiceAccountParams -ErrorAction SilentlyContinue
Write-Verbose -Message ("Start " + $SCTask.Num)
$Ritm = Get-ITDServiceNowRecord -ItemType 'Request Item' -SysId $SCTask.request_item.value -IncludeCustomVariable
$RitmRequestedFor = Get-ITDServiceNowUser -SysId $Ritm.requested_for.value
$obj = ($Ritm.CustomVariable.additional_comments.Value -split "`n")[2] | ConvertFrom-Json
If ($Obj.ADDomain -ne 'nd.gov') {
Write-Error -Message "Only nd.gov is supported, create account manually" -ErrorAction Stop
}
$NewITDADServiceAccountParams = @{
SamAccountName = $obj.SamAccountName;
Description = $obj.Description;
PasswordstateList = $obj.PasswordstateList;
PasswordstateTitle = $obj.PasswordstateTitle;
PasswordstateNotes = ("Requested via " + $RITM.number)
Credential = $PrvCred; #$Secret:svcitdiaasauto;
}
try {
New-ITDADServiceAccount @NewITDADServiceAccountParams -Verbose -ErrorAction Stop
$Notes = "New Active Directory account created."
$AccountCreated = $true
}
catch [Microsoft.PowerShell.Commands.WriteErrorException] {
Write-Error -Message $error[0]
$AccountCreated = $false
}
$EndTime = Get-Date
If ($PSBoundParameters.ContainsKey('Quiet') -and $Quiet -eq $true) {
Write-Verbose -Message "Quiet mode enabled. No ServiceNow interactions will be done." -Verbose
}
Else {
Write-Verbose -Message "Quiet mode disabled. ServiceNow CHG will be generated." -Verbose
# create std chg and close it
switch ($AccountCreated) {
$true {
Write-Verbose -Message "AccountCreated true" -Verbose
Write-Verbose -Message "Generating SNow CHG" -Verbose
#New-ITDServiceNowSession -Environment Test -Credential $Secret:SNowVMCred
$NewITDServiceNowChangeRequestParams = @{
TemplateName = 'NDIT-SPS-Server Add/Chg/Del'
RequestedByUsername = $RitmRequestedFor.user_name;
Category = 'Systems Platforms - Systems';
Subcategory = 'Windows';
Impact = 3;
ShortDescription = "New nd.gov Active Directory service account created - $UAJobId, " + $RITM.number;
Description = "New nd.gov Active Directory service account created";
Justification = "New nd.gov Active Directory service account required for zero-trust policies, following guidelines found in KB0016867";
Implementation = "PSUniversal execution";
RiskImpactAnalysis = "Low";
BackoutPlan = "Delete the new user account"
TestPlan = "n/a"
WhoIsImpacted = "Windows System Administrators";
StartTime = $StartTime
EndTime = $EndTime;
AssignmentGroup = 'NDIT-Computer Systems Windows';
ChangeManagerUsername = 'khellman';
ChangeCoordinatorUsername = 'gpgolberg';
AssignedToUsername = $RitmRequestedFor.user_name;
}
$CHG = New-ITDServiceNowChangeRequest @NewITDServiceNowChangeRequestParams -Verbose
Update-ITDServiceNowRecord -ItemType "Change Request" -Number $CHG.Number.Value -Values @{
work_notes = $Notes;
}
Write-Verbose -Message ("Completing SNow " + $CHG.Number.value) -Verbose
$CompleteITDServiceNowChangeRequestParams = @{
Number = $CHG.Number.value
CloseCode = "Successful"
CloseNotes = ("New nd.gov Active Directory account " + $obj.ADDomain + "\" + $obj.SamAccountName + " created.")
}
Complete-ITDServiceNowChangeRequest @CompleteITDServiceNowChangeRequestParams -Verbose
New-ITDServiceNowSession -Environment Production -Credential $Secret:SNowVMCred
Write-Verbose -Message ("SCTASK " + $SCTask.Num + " success notes")
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCTask.Number -Values @{
work_notes = $Notes + "`n" + ($Chg.Number.value + " created for the work.");
close_notes = $Notes;
state = "Closed Complete";
}
}
$false {
Write-Verbose -Message "AccountCreated false" -Verbose
Write-Verbose -Message ("SCTASK " + $SCTask.Num + " failure notes")
$Message = "Error during account creation, requires human review. PSU Job Id #$UAJobId"
Write-Warning -Message $Message
Write-Verbose -Message ("Update " + $SCTask.Number)
Update-ITDServiceNowRecord -ItemType 'Catalog Task' -Number $SCtask.Number -Values @{
work_notes = $Message;
short_description = $SCTask.short_description + " - HUMAN REVIEW"
}
}
Default {
Write-Verbose -Message "AccountCreated default" -Verbose
Write-Error -Message "AccountCreated variable is somehow not true or false... not sure how that happened. Great work!"
}
}
}
Write-Verbose -Message ("End " + $SCTask.Num)
}
}
end {
}
@@ -0,0 +1,40 @@
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$RequestedForEmail,
[Parameter(Mandatory = $true)]
[string]
$SamAccountName,
[Parameter(Mandatory = $true)]
[ValidateSet('nd.gov')]
[string]
$ADDomain,
[Parameter(Mandatory = $true)]
[string]
$Description,
[Parameter(Mandatory = $true)]
[ValidateSet('Office365', 'VMware_Systems', 'CSRC', 'Shared Linux Password List', 'Peoplesoft Share PW', 'Cohesity', 'VDI')]
[string]
$PasswordstateList,
[Parameter(Mandatory = $true)]
[string]
$PasswordstateTitle
)
$NewITDADServiceAccountParams = @{
RequestedForEmail = $RequestedForEmail;
SamAccountName = $SamAccountName;
ADDomain = $ADDomain;
Description = $Description;
PasswordstateList = $PasswordstateList;
PasswordstateTitle = $PasswordstateTitle;
}
New-ITDServiceNowSession -Environment Production -Credential $Secret:SNowVMCred -Verbose
New-ITDADServiceAccountRitm @NewITDADServiceAccountParams -Verbose
@@ -0,0 +1,78 @@
[CmdletBinding()]
param (
[string]
$SamAccountName,
[Parameter(Mandatory = $true)]
[string]
$Description,
[Parameter(Mandatory = $true)]
[ValidateSet('VMware_Systems', 'CSRC', 'Shared Linux Password List', 'Peoplesoft Share PW', 'Cohesity', 'VDI', 'Office365')]
[string]
$PasswordstateList,
[Parameter(Mandatory = $true)]
[string]
$PasswordstateTitle,
[switch]
$Quiet
)
$StartTime = Get-Date
$NewITDADServiceAccountParams = @{
SamAccountName = $SamAccountName;
Description = $Description;
PasswordstateList = $PasswordstateList;
PasswordstateTitle = $PasswordstateTitle;
Credential = $Secret:svcitdiaasauto;
}
try {
New-ITDADServiceAccount @NewITDADServiceAccountParams -Verbose
}
catch {
Write-Error -Message $error[0] -ErrorAction Stop
}
$EndTime = Get-Date
If ($PSBoundParameters.ContainsKey('Quiet') -and $Quiet -eq $true) {
Write-Verbose -Message "Quiet mode enabled. No ServiceNow interactions will be done." -Verbose
}
Else {
Write-Verbose -Message "Quiet mode disabled. ServiceNow CHG will be generated." -Verbose
# create std chg and close it
New-ITDServiceNowSession Test -Credential $Secret:SNowVMCred
$NewITDServiceNowChangeRequestParams = @{
TemplateName = 'NDIT-SPS-Server Add/Chg/Del'
RequestedByUsername = 'zmeier';
Category = 'Systems Platforms - Systems';
Subcategory = 'Windows';
Impact = 3;
ShortDescription = "New nd.gov Active Directory service account created - $UAJobId";
Description = "New nd.gov Active Directory service account created";
Justification = "New nd.gov Active Directory service account required for zero-trust policies";
Implementation = "PSUniversal execution";
RiskImpactAnalysis = "Low";
BackoutPlan = "Delete the new user account"
TestPlan = "n/a"
WhoIsImpacted = "Windows System Administrators";
StartTime = $StartTime
EndTime = $EndTime;
AssignmentGroup = 'NDIT-Computer Systems Windows';
ChangeManagerUsername = 'khellman';
ChangeCoordinatorUsername = 'gpgolberg';
AssignedToUsername = 'zmeier';
}
$CHG = New-ITDServiceNowChangeRequest @NewITDServiceNowChangeRequestParams -Verbose
Update-ITDServiceNowRecord -ItemType "Change Request" -Number $CHG.Number.Value -Values @{
work_notes = $Notes;
}
Complete-ITDServiceNowChangeRequest -Number $CHG.Number.value -CloseCode "Successful" -CloseNotes "New nd.gov Active Directory account ndgov\$SamAccountName created." -Verbose
}
@@ -0,0 +1,49 @@
New-UDApp -Title 'SyncVMwareVMtoSharePoint' -Pages @(
New-UDPage -Name "Home" -Content {
New-UDForm -Content {
New-UDTypography -Text 'Enter the information below (all fields required) and click the Submit button'
New-UDRow -Columns {
New-UDColumn -SmallSize 6 -LargeSize 6 -Content {
New-UDTextbox -Label 'RequestedForEmail' -Id RequestedForEmail
New-UDTextbox -Label 'ADDomain' -Id ADDomain
New-UDTextbox -Label 'SamAccountName' -Id SamAccountName
New-UDTextbox -Label 'Description' -Id Description
New-UDTextbox -Label 'PasswordstateList' -Id PasswordstateList
New-UDTextbox -Label 'PasswordstateTitle' -Id PasswordstateTitle
}
}
} -OnValidate {
$FormContent = $EventData
if ($EventData.RequestedForEmail -eq $null -or $EventData.RequestedForEmail -eq '' -or $EventData.ADDomain -eq $null -or $EventData.ADDomain -eq '' -or $EventData.SamAccountName -eq $null -or $EventData.SamAccountName -eq '' -or $EventData.Description -eq $null -or $EventData.Description -eq '' -or $EventData.PasswordstateList -eq $null -or $EventData.PasswordstateList -eq '' -or $EventData.PasswordstateTitle -eq $null -or $EventData.PasswordstateTitle -eq '') {
# ('Office365', 'VMware_Systems', 'CSRC', 'Shared Linux Password List', 'Peoplesoft Share PW', 'Cohesity', 'VDI')
New-UDFormValidationResult -ValidationError "All fields are required."
}
else {
if ($FormContent.ADDomain -ne 'nd.gov') {
New-UDFormValidationResult -ValidationError "Only nd.gov ADDomain is supported at this time."
}
else {
If (@('Office365', 'VMware_Systems', 'CSRC', 'Shared Linux Password List', 'Peoplesoft Share PW', 'Cohesity', 'VDI') -notcontains $EventData.PasswordstateList) {
New-UDFormValidationResult -ValidationError "PasswordstateList must match one of the following: VMware_Systems, Cohesity, VDI, Office365, CSRC, Shared Linux Password List, Peoplesoft Share PW"
}
}
New-UDFormValidationResult -Valid
}
} -OnSubmit {
$InvokePSUScriptParams = @{
Script = 'New-ITDADServiceAccountRitm_script.ps1';
RequestedForEmail = $EventData.RequestedForEmail
ADDomain = $EventData.ADDomain
SamAccountName = $EventData.SamAccountName
Description = $EventData.Description
PasswordstateList = $EventData.PasswordstateList
PasswordstateTitle = $EventData.PasswordstateTitle
}
$InvokePSUScriptResult = Invoke-PSUScript @InvokePSUScriptParams -Wait
Show-UDToast -Message ("ServiceNow Request " + $InvokePSUScriptResult.number + " created on behalf of $User" ) -Duration 100000
}
}
)
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.Infra-Certificate-External.Sectigo'
variables:
major: 0
minor: 1
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.Infra-Certificate-External.Sectigo.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.Infra-Certificate-External.Sectigo.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.Infra-Certificate-External.Sectigo'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.Infra-Certificate-External.Sectigo).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,14 @@
{
"AppConfig": {
"AuthBaseAPIUrl": {
"Description": "API url for Sectigo - Authentication only",
"Value": "https://auth.sso.sectigo.com"
},
"BaseAPIUrl": {
"Description": "API url for Sectigo - Calls",
"Value": "https://admin.hard.sectigo.com"
}
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.Infra-Certificate-External.Sectigo</id>
<version>$VERSIONHERE$</version>
<authors>Freeman Peterson</authors>
<description>Functions for Sectigo certificate enrollment and deployment</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>
@@ -0,0 +1,132 @@
#
# Module manifest for module 'ITD.Infra-Certificate-External.Sectigo'
#
# Generated by: zmeier
#
# Generated on: 11/5/2025
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'ITD.Infra-Certificate-External.Sectigo.psm1'
# Version number of this module.
ModuleVersion = '<ModuleVersion>'
# Supported PSEditions
CompatiblePSEditions = 'Desktop', 'Core'
# ID used to uniquely identify this module
GUID = '5c65f13f-9bde-40d3-97ce-aa9b37883db2'
# Author of this module
Author = 'fjpeterson'
# Company or vendor of this module
CompanyName = 'State of North Dakota'
# Copyright statement for this module
Copyright = '(c) 2026 fjpeterson. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Functions for Sectigo certificate enrollment and deployment'
# Minimum version of the PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# ClrVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(<FunctionsToExport>)
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @()
# DSC resources to export from this module
# DscResourcesToExport = @()
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
# Prerelease string of this module
# Prerelease = ''
# Flag to indicate whether the module requires explicit user acceptance for install/update/save
# RequireLicenseAcceptance = $false
# External dependent modules of this module
# ExternalModuleDependencies = @()
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
@@ -0,0 +1,25 @@
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
#Get public and private function definition files.
$Public = @( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = @( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in @($Public + $Private))
{
Try
{
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
# Here I might...
# Read in or create an initial config file and variable
# Export Public functions ($Public.BaseName) for WIP modules
# Set variables visible to the module and its functions only
Export-ModuleMember -Function $Public.Basename
@@ -0,0 +1,37 @@
# Using configuration file for most of the settings
$ConfigFile="${PSScriptRoot}/../Data/config.json"
$Config=(Get-Content -Path $ConfigFile|ConvertFrom-Json).AppConfig
#Load Configuration File as Variables
$ConfigVars=($Config | Get-Member -Membertype Noteproperty).Name
ForEach ($ConfigVar in $ConfigVars) {
Set-Variable -Name $ConfigVar -Value $Config.${ConfigVar}.Value
}
#Remove temporary variables we don't need to reused thse
Remove-Variable -Name Config,ConfigFile,ConfigVar,ConfigVars
$TimeZone=(timezone).id
# We need to declare the fields for syncing.
$SyncFields = @("Admins", "AgencyName","App","City","Cluster","Environment","SRM_Request","SRM_Status","SupportHours","TeamLead","VLAN","VMHost" )
$SNSyncFields=@("Environment","AgencyName")
#Default before set
$VerbosePreference = "SilentlyContinue"
$InformationPreference = "SilentlyContinue"
$DebugPreference = "SilentlyContinue"
# Sets logging levels for cmdlets.
Switch ($VerboseLevel) {
0 { $VerbosePreference = "SilentlyContinue" }
1 { $VerbosePreference = "Continue" }
2 { $VerbosePreference = "Continue" ; $InformationPreference = "Continue" }
3 { $VerbosePreference = "Continue" ; $InformationPreference = "Continue"; $DebugPreference = "Continue" }
}
# Overrides email SNMPTO
if ($TestMode -eq $True) {
Write-Information "Test Mode - On"
$SNMPTo = $TestModeAdmin
}
@@ -0,0 +1,71 @@
#'x509' - for Certificate (w/ chain), PEM encoded,
#'x509CO' - for Certificate only, PEM encoded,
#'base64' - for PKCS#7, PEM encoded,
#'bin' - for PKCS#7, 'x509IO' - for Root/Intermediate(s) only, PEM encoded,
#'x509IOR' - for Intermediate(s)/Root only, PEM encoded,
#'pem' - for Certificate (w/ chain), PEM encoded,
#'pemco' - for Certificate only, PEM encoded,
#'pemia' - for Certificate (w/ issuer after), PEM encoded,
#'x509R' - for Certificate (w/ chain), PEM encoded.
# base64 is default.
function Download-SectigoCertificate {
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[string]$ApiToken=$env:SectigoToken,
[string]$CertRootPath="c:\certs",
[ValidateSet('x509','x509CO','base64','bin','x509IOR','pem','pemco','pemia','x509R' )]
[string]$Format="x509CO",
[Parameter(Mandatory=$true)]
[string]$OrderId
)
if (-Not $ApiToken) {
$ApiToken=Read-Host "ApiToken:"
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
[string]$CollectUrl = "${BaseAPIUrl}/api/ssl/v1/collect/${OrderId}?format=${format}"
Write-Verbose -Verbose "CollectUrl: $CollectUrl"
$headers = @{
"Authorization" = "Bearer $ApiToken"
"Content-Type" = "application/json" # <-- Cleaned up syntax
}
# --- API Call ---
Write-Verbose "Attempting to retrieve certificate for Order ID: $OrderId"
try {
$response = Invoke-WebRequest -Uri $CollectUrl -Method Get -Headers $headers -UseBasicParsing -ErrorAction Stop
} catch {
Write-Error "API Request Failed: $($_.Exception.Message)"
return $null
}
$OutPath = "${CertRootPath}\cert_${OrderId}.cer"
# --- Response Processing ---
if ($response.StatusCode -eq 200) {
Write-Verbose "Certificate successfully retrieved (Status 200)."
# 1. Get the Hex String
# ASSUMPTION: The API returns the raw certificate Hex string in the response content.
# If the API returns JSON, you must use 'ConvertFrom-Json' first to extract the hex property.
$decimalNumbersString = $response.Content
$numberStrings = $decimalNumbersString -split '\s+|,|\r?\n' | Where-Object { $_ }
try {
[byte[]]$bytes = $numberStrings | ForEach-Object { [int]$_ }
} catch {
Write-Error "Error converting numbers. Ensure all numbers are between 0 and 255."
exit
}
# Write the byte array to the binary file
[System.IO.File]::WriteAllBytes($OutPath , $bytes)
Get-ChildItem $OutPath|select fullname, LastWriteTime
}
}
@@ -0,0 +1,149 @@
Function Enroll-SectigoCertificateRequest {
[CmdletBinding()]
param (
[string]$ApiToken=$env:SectigoToken,
[int]$OrgId=8091, # 8091 friendly label is "Information Technology Department - Windows"
[Parameter(Mandatory=$true)]
[string]$subjAltNames,
[ValidateSet('IIS','IIS_OLD','IBM','LINUX','Apache','Tomcat')]
[string]$Type="IIS",
[string]$comment = "",
[Parameter(Mandatory=$true)]
[string]$dcvEmail,
[Parameter(Mandatory=$true)]
[ValidateSet('ECC',"RSA")]
[string]$KeyType,
[Parameter(Mandatory=$true)]
[string]$Csr, # Replace with your Sectigo Organization ID
[switch]$Test
)
if (-Not $ApiToken) {
$ApiToken=Read-Host "ApiToken:"
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
[string]$RequestUrl= $BaseAPIUrl + "/api/ssl/v1/enroll"
Write-Verbose -Verbose "RequestUrl: $RequestUrl"
#$CertType=2369
#If ($subjAltNames) {
$CertType=2375
#}
$term=365
# 7: IBM HTTP Server
# 14: Microsoft IIS 5 or 6
switch ($ServerType.ToLower()) {
"iis" {
$ServerTypeCode = 35
}
"iis_old" {
$ServerTypeCode = 14
}
"ibm" {
$ServerTypeCode = 7
}
"linux" {
$ServerTypeCode = 'Linux'
}
"apache" {
$ServerTypeCode = 2
}
"tomcat" {
$ServerTypeCode = 12
}
default {
Write-Warning "Unsupported server type: $ServerType. Please provide specific instructions for manual installation."
}
}
#ignorded for now
# keySize = 2048,
# keyParam = 2048,
# algorithm = $KeyType
# keyGenerationMethod = PK_AGENT
$body = @{
orgId = $OrgId
subjAltNames = $subjAltNames
certType = $CertType
term = $term
serverType = $ServerTypeCode
comments = $comment
csr = $csr
externalRequester = $dcvEmail
}
#$b2= @{
# subjAltNames = $subjAltNames
#}
#
#if ($subjAltNames) {
# $body = $body + $b2
#}
$b3=@{
commonName = $commonName
keySize = 2048
keyParam = "2048"
algorithm = "RSA"
keyGenerationMethod = "PK_AGENT"
}
$b4=@{
commonName = $commonName
keyParam = "secp256r1"
algorithm = "ESS"
keyGenerationMethod = "PK_AGENT"
}
# $body = $body + $b2
#If ($KeyType -eq "rsa") {
# $body = $body + $b3
#} else {
# $body = $body + $b4
#}
If ($test) {
Return
}
# Convert the body to JSON
$jsonBody = $body | ConvertTo-Json
Write-Host $jsonBody
# --- Set up Authentication Headers ---
$headers = @{
"Authorization" = "Bearer $ApiToken"
"Content-Type" = "application/json"
}
# --- Send the Request ---
try {
$response=Invoke-RestMethod -Uri $RequestUrl -Method POST -Headers $headers -Body $jsonBody -ContentType "application/json"
return $response
}
catch {
Write-Error "Error during certificate enrollment: $($_.Exception.Message)"
if ($_.Exception.Response) {
$errorResponse = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorResponse)
$responseBody = $reader.ReadToEnd()
Write-Error "Sectigo API Error Response: $responseBody"
}
}
}
@@ -0,0 +1,46 @@
Function Get-SectigoApiToken {
#[CmdletBinding()]
. $PSScriptRoot\..\Private\Set-Onload.ps1
[string]$tokenEndpoint = $AuthBaseAPIUrl + "/auth/realms/apiclients/protocol/openid-connect/token"
$clientId=$env:Sectigoclientid
$clientSecret=$env:SectigoclientSecret
if (-Not $clientid) {
$clientid=Read-Host "Please enter your clientid"
}
if (-Not $clientSecret) {
$clientSecret=Read-Host "Please enter your clientSecret"
}
Write-Verbose -Verbose "tokenEndpoint: $tokenEndpoint "
# Prepare the body for the token request
$body = @{
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
# Request the access token
try {
$tokenResponse = Invoke-RestMethod -Uri $tokenEndpoint -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"
$accessToken = $tokenResponse.access_token
$env:SectigoToken=$accessToken
if ($accesstoken) { Write-Verbose -Verbose 'Token Set $ENV:SectigoToken'}
}
catch {
Write-Error "Failed to obtain access token: $($_.Exception.Message)"
#exit 1
}
}
@@ -0,0 +1,37 @@
function Get-SectigoCertificate {
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[string]$ApiToken=$env:SectigoToken,
[Parameter(Mandatory=$true)]
[string]$OrderId
)
if (-Not $ApiToken) {
$ApiToken=Read-Host "ApiToken:"
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
# [string]$dcvUrl = "${BaseAPIUrl}/api/ssl/v1/${OrderId}/dcv"
[string]$dcvUrl = "${BaseAPIUrl}/api/ssl/v1/${OrderId}"
$headers = @{
"Authorization" = "Bearer $ApiToken"
"Content-Type" = "application/json" # <-- Cleaned up syntax
}
# --- API Call ---
Write-Verbose "Attempting to retrieve certificate for Order ID: $OrderId"
try {
$response = Invoke-RestMethod -Uri $dcvUrl -Method Get -Headers $headers -UseBasicParsing -ErrorAction Stop
return $response
} catch {
Write-Error "API Request Failed: $($_.Exception.Message)"
return $null
}
$response
}
@@ -0,0 +1,42 @@
function Get-SectigoCertificateTypes {
[CmdletBinding()]
param (
[string]$ApiToken=$env:SectigoToken
)
if (-Not $ApiToken) {
$ApiToken=Read-Host "ApiToken:"
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
[string]$CertificateTypesUrl= $BaseAPIUrl + "/api/ssl/v1/types"
Write-Verbose -Verbose "CertificateTypesUrl: $CertificateTypesUrl"
# 1. Prepare the Authorization Header
# The Sectigo API usually requires the token in a Bearer authorization header.
$headers = @{
"Authorization" = "Bearer $ApiToken"
"Content-Type" = "application/json"
}
# 2. Send the Request (GET is the standard method for listing resources)
$response = Invoke-WebRequest -Uri $CertificateTypesUrl -Method Get -Headers $headers -UseBasicParsing
if ($response.StatusCode -eq 200) {
# 3. Process the Response
$certTypes = $response.Content | ConvertFrom-Json
# ASSUMPTION: The API returns an array of objects,
# each representing a certificate type.
# This function returns the entire list/array.
return $certTypes
} else {
Write-Error "Failed to get Certificate Types. Status code: $($response.StatusCode)."
Write-Error "Response content: $($response.Content)"
return $null
}
}
@@ -0,0 +1,44 @@
# Load Configuration Variables
function Get-SectigoOrg {
[CmdletBinding()]
param (
[string]$ApiToken=$env:SectigoToken
)
if (-Not $ApiToken) {
$ApiToken=Read-Host
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
[string]$OrganizationLookupUrl=$BaseAPIUrl + "/api/organization/v1"
# 1. Prepare the Authorization Header
$headers = @{
"Authorization" = "Bearer $ApiToken"
"Content-Type" = "application/json"
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
try {
# Invoke the web request to the Sectigo API
$Response = Invoke-WebRequest -Uri $OrganizationLookupUrl -Headers $Headers -Method GET
# Check if the request was successful
if ($Response.StatusCode -eq 200) {
# Parse the JSON response
$Organizations = $Response.Content | ConvertFrom-Json
$Organizations|select-object id, name
$Organizations.departments
} else {
Write-Error "Failed to retrieve organizations. Status Code: $($Response.StatusCode)"
Write-Error "Response Content: $($Response.Content)"
}
}
catch {
Write-Error "An error occurred during the API call: $($_.Exception.Message)"
}
}
@@ -0,0 +1,43 @@
# Load Configuration Variables
function Get-SectigoSeverTypes {
[CmdletBinding()]
param (
[string]$ApiToken=$env:SectigoToken
)
if (-Not $ApiToken) {
$ApiToken=Read-Host
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
[string]$OrganizationLookupUrl=$BaseAPIUrl + "/api/v1/servertype"
# 1. Prepare the Authorization Header
$headers = @{
"Authorization" = "Bearer $ApiToken"
"Content-Type" = "application/json"
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
try {
# Invoke the web request to the Sectigo API
$Response = Invoke-WebRequest -Uri $OrganizationLookupUrl -Headers $Headers -Method GET
# Check if the request was successful
if ($Response.StatusCode -eq 200) {
# Parse the JSON response
$Response.Content | ConvertFrom-Json
} else {
Write-Error "Failed to retrieve organizations. Status Code: $($Response.StatusCode)"
Write-Error "Response Content: $($Response.Content)"
}
}
catch {
Write-Error "An error occurred during the API call: $($_.Exception.Message)"
}
}
@@ -0,0 +1,46 @@
function Revoke-SectigoCertificate {
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[string]$ApiToken=$env:SectigoToken,
[int]$reasonCode=4,
[Parameter(Mandatory=$true)]
[string]$reason,
[Parameter(Mandatory=$true)]
[string]$OrderId
)
if (-Not $ApiToken) {
$ApiToken=Read-Host
}
. $PSScriptRoot\..\Private\Set-Onload.ps1
[string]$RevokeUrl = "${BaseAPIUrl}/api/ssl/v1/revoke/${OrderId}"
Write-Verbose -Verbose "RequestUrl: $RevokeUrl"
$headers = @{
"Authorization" = "Bearer $ApiToken"
"Content-Type" = "application/json" # <-- Cleaned up syntax
}
$body = @{
reasonCode = $reasonCode
reason = $reasonCode
}
$jsonBody = $body | ConvertTo-Json
# --- API Call ---
Write-Verbose "Attempting to retrieve certificate for Order ID: $OrderId"
try {
$response=Invoke-RestMethod -Uri $RevokeUrl -Method POST -Headers $headers -Body $jsonBody -ContentType "application/json"
return $response
"Success"
} catch {
Write-Error "API Request Failed: $($_.Exception.Message)"
return $null
}
}
@@ -0,0 +1,39 @@
function Test-SectigoCertificateRequest {
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[string]$FilePath,
[string]$csr
)
if ($FilePath) {
[string]$csr = (Get-Content -Path $FilePath -Raw)
}
[string]$dcvUrl = "https://certificates.nd.gov/api/csr/validate/string"
$headers = @{
"accept" = "application/json"
"Content-Type" = "application/json" # <-- Cleaned up syntax
}
$Body = @{
"csr" = $csr
}
$jsonBody = $body | ConvertTo-Json
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# --- API Call ---
Write-Verbose "Attempting to retrieve certificate for Order ID: $OrderId"
try {
$response = Invoke-RestMethod -Uri $dcvUrl -Method Post -Headers $headers -Body $jsonBody
$response
} catch {
Write-Error "API Request Failed: $($_.Exception.Message)"|convertfrom-json
return $null
}
}
@@ -0,0 +1,122 @@
# ITD.Infra-Certificate-Internal.Sectigo
### Written by: Freeman Peterson fjpeterson@nd.gov
# Description
This module is used to interact Sectigo API
## Prerequisites
None
# Api Documentation
https://www.sectigo.com/knowledge-base/detail/Sectigo-Certificate-Manager-SCM-REST-API/kA01N000000XDkE
# Git repo
https://dev.azure.com/ndgov/NDIT-WindowsServerTeam/_git/ITD.Infra-Certificate-Internal.Sectigo
# Install
### Add Gallary
```
Register-PSRepository -Name ITD_PwshGallery `
-SourceLocation "https://powershell.nd.gov/ITD_PwshGallery/nuget/" `
-PublishLocation "https://powershell.nd.gov/ITD_PwshGallery/nuget/" `
-InstallationPolicy Trusted
```
### Install Module
```
Find-Module -Repository ITD_PwshGallery -Name ITD.Infra-Certificate-Internal.Sectigo|Install-Module -Scope CurrentUser
```
### Validate Module installed
```
Get-Command -Module ITD.Infra-Certificate-Internal.Sectigo
```
### Update Module
```
Find-Module -Repository ITD_PwshGallery -Name ITD.Infra-Certificate-Internal.Sectigo|Update-Module
```
# Examples and Information
### Obtaining CSR From File
```
$csr = (Get-Content -Path "c:\temp\hostname.csr" -Raw)
```
### Get Token Prompt for Creds
```
$env:Sectigoclientid='b16d95fd-405f-4d41-a748-c1035916a359'
$env:SectigoclientSecret=redacted
Get-SectigoApiToken
```
### Test Cert Request
```
Test-SectigoCertificateRequest -FilePath $csrpath
```
### Enroll the CSR
```
$certRequest = Enroll-SectigoCertificateRequest -Csr $csr -dcvEmail 'youremail@nd.gov" #-Comment "app123"
```
### Get Cert Status
```
Get-SectigoCertificate -Orderid $certRequest.sslid
```
### Wait for approval
```
while ($certstatus -ne "Issued") {
$certstatus=(Get-SectigoCertificate -OrderId $certRequest.sslid).status
Sleep 1
}
```
### Download Cert
```
$CertPath = (Download-SectigoCertificate -Orderid $certRequest.sslid -Format "pem").FullName
```
Default Format: Pem
'x509' - for Certificate (w/ chain) PEM encoded
'x509CO' - for Certificate only, PEM encoded
'base64' - for PKCS#7, PEM encoded
'bin' - for PKCS#7, 'x509IO' - for Root/Intermediate(s) only, PEM encoded
'x509IOR' - for Intermediate(s)/Root only, PEM encoded
'pem' - for Certificate (w/ chain), PEM encoded
'pemco' - for Certificate only, PEM encoded
'pemia' - for Certificate (w/ issuer after), PEM encoded
'x509R' - for Certificate (w/ chain), PEM encoded
base64
### Add it to a cert store
As administrator:
```
Import-Certificate -FilePath "C:\Certs\cert_OrderNumber.pem" -CertStoreLocation Cert:\LocalMachine\My
```
# Other functions
### Revoke
```
Revoke-SectigoCertificate -reasonCode 4 -reason "Just a test" -Orderid 11012388
```
No results will be given back to you.
*If you revoke a cert you will no longer see them in Get-SectigoCertificate
Reason code (unspecified (0), keyCompromise (1), affiliationChanged (3), superseded (4), cessationOfOperation (5))
### Org Lookup
```
Get-SectigoOrg
```
### Certificate Types
```
Get-SectigoCertificateTypes
```
File diff suppressed because one or more lines are too long
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.Infra-Networking-Infoblox'
variables:
major: 1
minor: 2
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.Infra-Networking-Infoblox.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.Infra-Networking-Infoblox.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.Infra-Networking-Infoblox'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.Infra-Networking-Infoblox).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.Infra-Networking-Infoblox</id>
<version>$VERSIONHERE$</version>
<authors>Zack Meier</authors>
<description>Functions for Infoblox use</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>
@@ -0,0 +1,10 @@
@{
RootModule = 'ITD.Infra-Networking-Infoblox.psm1'
ModuleVersion = '<ModuleVersion>'
GUID = '32fa3228-dfa3-4cc9-aac8-e14332a46abf'
Author = 'Zack Meier'
CompanyName = 'State of North Dakota'
PowerShellVersion = '5.1'
CompatiblePSEditions = 'Desktop','Core'
FunctionsToExport = @(<FunctionsToExport>)
}
@@ -0,0 +1,337 @@
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function Get-ITDIbDNSRecord {
[CmdletBinding()]
Param
(
[string[]]
$Hostname,
[PSCredential]
$Credential
)
Begin {
If (!($Credential)) {
$Credential = Get-Credential
}
}
Process {
$result = @()
ForEach ($h in $hostname) {
$x = Invoke-RestMethod -Method Get -Uri "https://infoblox-gmv.ns.nd.gov/wapi/v2.7/record:host?name=$h" -ContentType "application/json" -Credential $Credential
If ($x) {
$obj = [PSCustomObject]@{
'HostName' = $x.ipv4addrs.Host;
'IPv4Address' = $x.ipv4addrs.ipv4addr
'DHCP' = $x.ipv4addrs.configure_for_dhcp
}
$result += $obj
}
}
}
End {
Write-Output $result
}
}
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function New-ITDIbDNSRecord {
[CmdletBinding()]
Param
(
[ipaddress]
$IPv4Address,
[string]
$Hostname,
[PSCredential]
$Credential
)
Begin {
If (!($Credential)) {
$Credential = Get-Credential
}
$Uri = "https://infoblox-gmv.ns.nd.gov/wapi/v2.7/record:host"
$IPs = @([PSCustomObject]@{'ipv4addr' = $IPv4Address })
}
Process {
$obj = [PSCustomObject]@{
'ipv4addrs' = $IPs;
'name' = $Hostname;
'view' = "default";
}
$postJson = $obj | ConvertTo-Json
Invoke-RestMethod -Uri $Uri -Method Post -Body $postJson -ContentType "application/json" -Credential $Credential
}
End {
}
}
function Get-IPs {
Param
(
[Parameter(Mandatory = $true)]
[array]
$Subnets
)
foreach ($subnet in $subnets) {
#Split IP and subnet
$IP = ($Subnet -split "\/")[0]
$SubnetBits = ($Subnet -split "\/")[1]
#Convert IP into binary
#Split IP into different octects and for each one, figure out the binary with leading zeros and add to the total
$Octets = $IP -split "\."
$IPInBinary = @()
foreach ($Octet in $Octets) {
#convert to binary
$OctetInBinary = [convert]::ToString($Octet, 2)
#get length of binary string add leading zeros to make octet
$OctetInBinary = ("0" * (8 - ($OctetInBinary).Length) + $OctetInBinary)
$IPInBinary = $IPInBinary + $OctetInBinary
}
$IPInBinary = $IPInBinary -join ""
#Get network ID by subtracting subnet mask
$HostBits = 32 - $SubnetBits
$NetworkIDInBinary = $IPInBinary.Substring(0, $SubnetBits)
#Get host ID and get the first host ID by converting all 1s into 0s
$HostIDInBinary = $IPInBinary.Substring($SubnetBits, $HostBits)
$HostIDInBinary = $HostIDInBinary -replace "1", "0"
#Work out all the host IDs in that subnet by cycling through $i from 1 up to max $HostIDInBinary (i.e. 1s stringed up to $HostBits)
#Work out max $HostIDInBinary
$imax = [convert]::ToInt32(("1" * $HostBits), 2) - 1
$IPs = @()
#Next ID is first network ID converted to decimal plus $i then converted to binary
For ($i = 1 ; $i -le $imax ; $i++) {
#Convert to decimal and add $i
$NextHostIDInDecimal = ([convert]::ToInt32($HostIDInBinary, 2) + $i)
#Convert back to binary
$NextHostIDInBinary = [convert]::ToString($NextHostIDInDecimal, 2)
#Add leading zeros
#Number of zeros to add
$NoOfZerosToAdd = $HostIDInBinary.Length - $NextHostIDInBinary.Length
$NextHostIDInBinary = ("0" * $NoOfZerosToAdd) + $NextHostIDInBinary
#Work out next IP
#Add networkID to hostID
$NextIPInBinary = $NetworkIDInBinary + $NextHostIDInBinary
#Split into octets and separate by . then join
$IP = @()
For ($x = 1 ; $x -le 4 ; $x++) {
#Work out start character position
$StartCharNumber = ($x - 1) * 8
#Get octet in binary
$IPOctetInBinary = $NextIPInBinary.Substring($StartCharNumber, 8)
#Convert octet into decimal
$IPOctetInDecimal = [convert]::ToInt32($IPOctetInBinary, 2)
#Add octet to IP
$IP += $IPOctetInDecimal
}
#Separate by .
$IP = $IP -join "."
$IPs += $IP
}
$IPs
}
}
<#
.SYNOPSIS
Dynamically creates DNS A records
.DESCRIPTION
Dynamically creates DNS A records based on CIDR input
.EXAMPLE
New-ITDIbDNSRecordNextAvailableIP -Hostname itdserver1.nd.gov -CIDR 10.11.12.0/23 -Credential $Credential
.EXAMPLE
New-ITDIbDNSRecordNextAvailableIP -Hostname itdserver2.nd.gov,itdserver3.nd.gov,itdserver4.nd.gov -CIDR 10.11.12.0/23 -Credential $Credential
.INPUTS
Inputs to this cmdlet (if any)
.OUTPUTS
Output from this cmdlet (if any)
.NOTES
General notes
.COMPONENT
The component this cmdlet belongs to
.ROLE
The role this cmdlet belongs to
.FUNCTIONALITY
The functionality that best describes this cmdlet
#>
function New-ITDIbDNSRecordNextAvailableIP {
[CmdletBinding()]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[string[]]
$Hostname,
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidatePattern("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(3[0-2]|[1-2][0-9]|[0-9]))$")]
[string]
$CIDR,
[PSCredential]
$Credential
)
begin {
$NetworkAddress = $CIDR.split('/')[0]
$IPs = Get-IPs -Subnets $CIDR
$ExcludeIPs = $IPs | select -First 1
}
process {
#$ curl -k -u admin:infoblox -X GET https://10.64.41.6/wapi/v1.1/network?ipv4addr=10.144.2.0
try {
$networkobj = Invoke-RestMethod -Method Get -Uri "https://infoblox-gmv.ns.nd.gov/wapi/v2.7/network?ipv4addr=$NetworkAddress" -Credential $Credential -ContentType "application/json" -ErrorAction Stop
$networkobjref = $networkobj._ref
#curl -k -u admin:infoblox -X POST https://10.64.41.6/wapi/v1.1/network/ZG5zLm5ldHdvcmskMTAuMTQ0LjIuMC8yNC8w:10.144.2.0/24/default?_function=next_available_ip -H "Content-Type: application/json" -d '{"exclude": ["10.144.2.8", "10.144.2.10"], "num": 6}'
$bodyjson = @{num = @($Hostname).count; exclude = @($ExcludeIPs) } | ConvertTo-Json
#Invoke-RestMethod -Method Post -Uri ("https://infoblox-gmv.ns.nd.gov/wapi/v2.7/" + $networkobjref + "?_function=next_available_ip") -ContentType "application/json" -Credential $Credential -Body '{"exclude": ["10.11.12.1", "10.11.12.2"], "num": 6}' #WORKS
$IPsAvailable = (Invoke-RestMethod -Method Post -Uri ("https://infoblox-gmv.ns.nd.gov/wapi/v2.7/" + $networkobjref + "?_function=next_available_ip") -ContentType "application/json" -Credential $Credential -Body $bodyjson -ErrorAction Stop).Ips
$IPCount = 0
ForEach ($hn in $hostname) {
if (Get-ITDIbDNSRecord -Hostname $hn -Credential $Credential) {
Write-Warning "DNS record already exists"
}
else {
New-ITDIbDNSRecord -IPv4Address $IPsAvailable[$IPCount] -Hostname $hn -Credential $Credential -ErrorAction Stop
$IPcount++
}
}
}
catch [System.Net.WebException] {
Write-Error "webexception error"
}
}
end {
}
}
function Get-ITDIbVlan {
[CmdletBinding()]
Param(
[Parameter(
ParameterSetName = 'VlanLookup')]
[ValidateRange(0, 4096)]
[int[]]
$Vlan,
[Parameter(
ParameterSetName = 'CIDRLookup')]
[string[]]
$CIDR,
[PSCredential]
$Credential
)
begin {
}
process {
$Output = @()
switch ($PsCmdlet.ParameterSetName) {
"VlanLookup" {
ForEach ($v in $Vlan) {
$InvokeResult = Invoke-RestMethod -Method Get -Uri "https://infoblox-gmv.ns.nd.gov/wapi/v2.11.3/vlan?id=$v&_return_as_object=1&_return_fields=assigned_to,id,name,parent,comment,description" -ContentType "application/json" -Credential $Credential
$InvokePSObject = $InvokeResult.result | Select-Object id, Name, Comment, @{n = "AssignedTo"; e = { $_.Assigned_to.split(':')[1] -replace '/default' } }
$Output += $InvokePSObject
}
}
"CIDRLookup" {
ForEach ($c in $CIDR) {
$InvokeResult = Invoke-RestMethod -Method Get -Uri "https://infoblox-gmv.ns.nd.gov/wapi/v2.11.3/vlan?_return_as_object=1&_max_results=-50000&_return_fields=assigned_to,id,name,parent,comment,description" -ContentType "application/json" -Credential $Credential
$InvokePSObject = $InvokeResult.result | Select-Object id, Name, Comment, @{n = "AssignedTo"; e = { $_.Assigned_to.split(':')[1] -replace '/default' } }
$Output += $InvokePSObject | Where-Object AssignedTo -EQ $c
}
}
"default" {
$InvokeResult = Invoke-RestMethod -Method Get -Uri "https://infoblox-gmv.ns.nd.gov/wapi/v2.11.3/vlan?_return_as_object=1&_max_results=-50000&_return_fields=assigned_to,id,name,parent,comment,description" -ContentType "application/json" -Credential $Credential
$InvokePSObject = $InvokeResult.result | Select-Object id, Name, Comment, @{n = "AssignedTo"; e = { $_.Assigned_to.split(':')[1] -replace '/default' } }
$Output = $InvokePSObject
}
}
}
end {
Write-Output $Output
}
}
function Remove-ITDIbDnsRecord {
[CmdletBinding()]
param (
[string]
$ComputerName,
[PSCredential]
$Credential
)
Begin {
If (!($Credential)) {
$Credential = Get-Credential
}
}
Process {
$DNSRecord = Invoke-RestMethod -Method Get -Uri "https://infoblox-gmv.ns.nd.gov/wapi/v2.7/record:host?name=$ComputerName" -ContentType "application/json" -Credential $Credential
$UriToDelete = ( "https://infoblox-gmv.ns.nd.gov/wapi/v2.7/" + $DNSRecord._ref )
Invoke-RestMethod -Method Delete -Uri $UriToDelete -ContentType "application/json" -Credential $Credential
}
end {
}
}
@@ -0,0 +1,20 @@
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
# Build and Test
TODO: Describe and show how to build your code and run the tests.
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
@@ -0,0 +1,49 @@
trigger:
- main
name: 'ITD.Infra-Passwordstate'
variables:
major: 0
minor: 2
patch: $(Build.BuildID)
buildVer: $(major).$(minor).$(Build.BuildID)
pool: itdwinautop1
stages:
- stage: Build
jobs:
- job: Build
steps:
- task: PowerShell@2
inputs:
filePath: '$(System.DefaultWorkingDirectory)/Build/build.ps1'
- task: NuGetCommand@2
inputs:
command: 'pack'
packagesToPack: '$(System.DefaultWorkingDirectory)/ITD.Infra-Passwordstate.nuspec'
versioningScheme: byEnvVar
versionEnvVar: buildVer
buildProperties: 'VERSIONHERE=$(buildVer)'
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'NuGetPackage'
publishLocation: 'Container'
- stage: Deploy
jobs:
- job: Deploy
steps:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'NuGetPackage'
itemPattern: '**'
targetPath: '$(Pipeline.Workspace)'
- task: NuGetCommand@2
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/ITD.Infra-Passwordstate.$(major).$(minor).$(Build.BuildID).nupkg'
nuGetFeedType: external
publishFeedCredentials: 'ITD_PwshGallery'
@@ -0,0 +1,17 @@
$buildVersion = $env:BUILDVER
$moduleName = 'ITD.Infra-Passwordstate'
$manifestPath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psd1"
$modulePath = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath "$moduleName.psm1"
## Update build version in manifest
$manifestContent = Get-Content -Path $manifestPath -Raw
$manifestContent = $manifestContent -replace '<ModuleVersion>', $buildVersion
## Update functions to export in manifest
Import-Module $modulePath
$funcStrings = (Get-Module ITD.Infra-Passwordstate).ExportedCommands.Values.Name
$funcStrings = "'$($funcStrings -join "','")'"
$manifestContent = $manifestContent -replace "<FunctionsToExport>", $funcStrings
$manifestContent | Set-Content -Path $manifestPath
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>ITD.Infra-Passwordstate</id>
<version>$VERSIONHERE$</version>
<authors>Zack Meier</authors>
<description>Functions for Passwordstate use</description>
</metadata>
<files>
<file src="**" exclude="**\.git\**;**\Build\**" />
</files>
</package>

Some files were not shown because too many files have changed in this diff Show More