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,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 {
}
}