#https://96bdfe01-af80-4575-8f23-e7057184c8f6.webhook.cus.azure-automation.net/webhooks?token=4cWZz%2fq97AqMdlNak6qv2lUurLPFsWmPJmovLmAE%2fNg%3d [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [object] $WebhookData ) function New-ITDVMwareWindowsVMAA { [CmdLetBinding()] Param( [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)] [string] $VlanId,#> [Parameter(Mandatory = $true)] [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 ) #$IaasAuto = Get-Credential -UserName ndgov\svcitdiaasauto #$StdCred = $IaasAuto #$PrvCred = $IaasAuto $ComputerName = $ComputerName.ToLower() $FQDN = $ComputerName If ($Credential) { $IaasAuto = $Credential } Write-Warning "[$FQDN]:Start" $SPCred = $IaasAuto $ADCred = $IaasAuto $PSCred = $IaasAuto $VMCred = $IaasAuto $CcmCred = $IaasAuto If($Environment -eq "Development"){ $Environment = "Test" } #$RadiusCred = $RadiusCred $RadiusCred = New-Object System.Management.Automation.PSCredential($IaasAuto.username.split('\')[1], ($IaaSAuto.Password)) Clear-DnsClientCache $HostName = $FQDN.split('.')[0] # Infoblox $InfobloxVlanMetadata = Get-ITDIbVlan -CIDR $Subnet -Credential $RadiusCred $Cidr = ($InfobloxVlanMetadata.AssignedTo | Out-String).TrimEnd() [Net.IpAddress]$NetworkId = $Cidr.split('/')[0] [Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -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 ($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 { New-ITDIbDNSRecordNextAvailableIP -Hostname $FQDN -CIDR $CIDR -Credential $RadiusCred Start-Sleep -Seconds 5 [Net.IPAddress]$IpAddress = (Resolve-DnsName -Name $FQDN -ErrorAction SilentlyContinue).IPAddress If ((Test-NetConnection -ComputerName $IpAddress.IPAddressToString).PingSucceeded) { Write-Error "IP Address already in use." -ErrorAction Stop } } # Passwordstate BB before baseline, AB after baseline Write-Warning "Start Passwordstate" If ($FQDN -like "itdcnd*") { $PasswordStateList = "Peoplesoft Share PW" } Else { $PasswordStateList = "CSRC" } $LocalCredential = New-ITDPassword -Title $FQDN -UserName itdadmin -Description 'Local Administrator' -PasswordList $PasswordStateList -Credential $PSCred #$GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($LocalCredential.Password | ConvertTo-SecureString -AsPlainText -Force)) #$GuestCredentialAB = New-Object System.Management.Automation.PSCredential ($LocalCredential.UserName, ($LocalCredential.Password | ConvertTo-SecureString -AsPlainText -Force)) $GuestCredentialAB = New-Object System.Management.Automation.PSCredential ('itdadmin', ($LocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force)) $GuestCredentialBB = New-Object System.Management.Automation.PSCredential ('Administrator', ($LocalCredential.GetNetworkCredential().Password | ConvertTo-SecureString -AsPlainText -Force)) #$GuestCredential = $GuestCredentialBB # VMware Write-Warning "Verify VM doesn't exist" If (Get-VM -Name $FQDN -ErrorAction SilentlyContinue) { Write-Error "Virtual machine with the name $FQDN already exists." -ErrorAction Stop Exit } Write-Warning "Decide 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 = "SQL" } "Microsoft SQL Enterprise (Academic)" { $ClusterRoot = "WINDOWS" } "IBM Websphere" { $ClusterRoot = "WAS" } "Powerschool" { $ClusterRoot = "PS" } "Pexip" { $ClusterRoot = "TEL" } } Write-Warning "Decide Datacenter" switch ($Datacenter) { "Bismarck" { $ClusterInt = 1 } "Mandan" { $ClusterInt = 2 } } $Cluster = $ClusterRoot + $ClusterInt Write-Warning "$Cluster decided, getting details" 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' } } "SQL1" { $ViServer = 'itdvmvc1.nd.gov' $ComputeCluster = Get-Cluster SQL1 $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-PDC-Data-Server" $DiskStorageFormat = 'EagerZeroedThick' $DatastoreCluster = Get-DatastoreCluster -Name "SQL1_FS92_Gen" } "SQL2" { $ViServer = 'itdvmvc2.nd.gov' $ComputeCluster = Get-Cluster SQL2 $VirtualSwitch = Get-VDSwitch -Name "dvSwitch-SDC-Data-Server" $DiskStorageFormat = 'EagerZeroedThick' $DatastoreCluster = Get-DatastoreCluster -Name "SQL2_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 } } # verify disk swap is sized at or above minimum, if diskdata should be populated, and that it all will fit datastore If($DiskSwap -lt ($MemoryGB + 1)){ [int]$DiskSwap = $MemoryGB + 1 } If($ComputerName -like "itdcnd*"){ $DiskData = 0 } Else{ $DiskData = 20 } $DiskTotal = $DiskOS + $DiskSwap + $DiskData 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" switch ($OS) { "Windows Server 2012R2 Standard" { $Template = "Windows Server 2012R2 Standard" } "Windows Server 2016 Standard" { $Template = "Windows Server 2016 Standard" } "Windows Server 2019 Standard" { $Template = "Windows Server 2019 Standard" } "Windows Server 2019 Datacenter" { $Template = "Windows Server 2019 Datacenter 1809.18" } "Windows Server 2022 Datacenter" { $Template = "Windows Server 2022 Datacenter 2108.13" } Default { Write-Error "Invalid template" -ErrorAction Stop } } $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-Warning ("ViServer = $ViServer") $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 <# $NewVMParams = @{ Name = $FQDN; ResourcePool = $SPItem.Cluster; Datastore = $DatastoreCluster; DiskStorageFormat = $DiskStorageFormat; Template = $Template; Location = $FolderLocation; OSCustomizationSpec = $OSSpec; } $NewVMParams New-VM @NewVMParams #> Set-Location C:\Temp # required to make New-VM work, https://communities.vmware.com/thread/591294 # seems fixed on 2022/07/01 -- is not fixed 2022/10/28 try { Write-Warning $FQDN Write-Warning $ComputeCluster.Name Write-Warning $DatastoreCluster Write-Warning $DiskStorageFormat Write-Warning $Template Write-Warning $FolderLocation Write-Warning $OSSpec New-VM -Name $FQDN -ResourcePool $ComputeCluster.Name -Datastore $DatastoreCluster -DiskStorageFormat $DiskStorageFormat -Template $Template -Location $FolderLocation -OSCustomizationSpec $OSSpec } catch { Write-Error $error[0] Exit } #If (!($BuildError)) { $VM = Get-VM -Name $FQDN # 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 # Power On VM $VM | Start-VM # 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 } 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" } } } # Delete OS Customization Spec # Get-OSCustomizationSpec -Name $NewOSSpecName | Remove-OSCustomizationSpec -Confirm:$false # Add/Expand Disks Write-Warning -Message "[$FQDN]:Modify disks 1,2,3" $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" $VMDisk1 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 1" If ($VMDisk1.CapacityGB -lt $DiskOS) { Set-HardDisk -HardDisk $VMDisk1 -CapacityGB $DiskOS -Confirm:$false } 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 $DiskSwap) { Set-HardDisk -HardDisk $VMDisk2 -CapacityGB $DiskSwap -Confirm:$false } If ($DiskData -gt 0) { If (!$VMDisk3) { $VM | New-HardDisk ` -CapacityGB $DiskData ` -StorageFormat Thin ` -DiskType Flat ` -Persistence Persistent } $VMDisk3 = $VM | Get-HardDisk | Where-Object Name -EQ "Hard disk 3" If ($VMDisk3.CapacityGB -lt $DiskData) { Set-HardDisk -HardDisk $VMDisk3 -CapacityGB $DiskData -Confirm:$false } } 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 $Environment) -Server $VIServer # skipping tag assignment for now # Set-ITDVMwareVMTagFromSharePoint -ComputerName $FQDN -SharePointCredential $SPCred -Verbose # Run Guest OS code # Enable RDP with NLA - can be done with GPO # Enable WinRM - ITD WinRM enable # Configure Power Plan - ITD-PowerPlan-HighPerformance # Activate Windows, KMS on-premise, Azure to Azure # DVD Drive to Z: # Expand C: # Partition/Format the rest, GPT/NTFS # Configure Page File Disk # Enabling server manager performance monitors # Configure Time Zone and NTP - no GPO available # Disable Windows Firewall # Join AD #Initialize-ITDServer -Credential $ADCred Write-Warning -Message "[$FQDN]: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-Warning -Message "[$FQDN]:Checking for DVD drive..." Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText { # 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-Warning -Message "[$FQDN]:Checking for unpartitioned space on C: disk..." Get-VM -Name $FQDN | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText { # 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-Warning "[$FQDN]: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." # 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 } } } Write-Warning "[$FQDN]: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:..." 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." $pageFile = Get-WmiObject -Class Win32_PageFileSetting -EnableAllPrivileges $pageFile.Delete() Write-Verbose -Message "Deleted C:\pagefile.sys." 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." } else { Write-Verbose -Message "Pagefile already configured on D:, continuing..." } } catch { Throw $_ Break } } else { Write-Verbose -Message "Page file drive not found, cannot set up page file. Continuing server configuration..." Write-Warning "Page file drive not found, cannot set up page file. Continuing server configuration..." } } Write-Warning -Message "[$FQDN]: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..." 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..." $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-Warning -Message "[$FQDN]:Checking current power plan..." 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..." $highPerformance = [regex]::Match($powerPlans, '([\d\w-\S]+)(?=\s+\(High performance\))').Value [void](powercfg -setactive $highPerformance) Write-Verbose -Message "Power plan set to High Performance." } else { Write-Verbose -Message "Power plan already configured for High Performance." } [void](& w32tm.exe /resync /nowait) } catch { Throw $_ Break } } $TimeSyncFunc = { # Configure Time/Date Settings Write-Verbose -Message "Checking current time/date settings..." $Domain = "" 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." 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." } <# Write-Verbose -Message "Beginning a time synchronization..." if ((Get-Service W32Time).Status -eq 'Stopped') { Start-Service W32Time } [void](& w32tm.exe /config /manualpeerlist:$Domain /syncfromflags:all /update) [void](& w32tm.exe /resync /nowait) # Start background job to check time service $tmJob = Start-Job -Name 'ManualTimeSync' -ScriptBlock { do { $tmOut = & w32tm.exe /query /status /verbose Start-Sleep -Seconds 10 } until($tmOut -match '^Last Sync Error: 0\D+$') } # If after three minutes time still has not synched, move on. This usually causes no problems. if ((Wait-Job -Job $tmJob -Timeout 180).State -eq 'Completed') { Write-Warning -Message "Time synchronization with $($Domain.ToLower()) successful." } else { [void](Stop-Job -Job $tmJob) Write-Warning -Message "Time synchronization with $($Domain.ToLower()) failed." } #> } catch { Throw $_ Break } } $TimeSyncScriptBlock = $TimeSyncFunc -replace '', $DomainName $VM | Invoke-VMScript -GuestCredential $GuestCredentialBB -ScriptType PowerShell -ScriptText $TimeSyncScriptBlock #> Write-Warning -Message "[$FQDN]:Enabling 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." } else { Write-Verbose -Message "Performance monitors already enabled, continuing..." } } catch { Throw $_ Break } } Write-Warning -Message "[$FQDN]: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..." 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..." } } # Active Directory Write-Warning "[$FQDN]:Join Active Directory (if required)" $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 '^.+?(? -OUPath "" -Credential $DomainJoinCred' $SecondScriptText = $SecondScriptText -replace '', $DomainName $SecondScriptText = $SecondScriptText -replace '', $OuFinal $SecondScriptText = $SecondScriptText -replace '', ("OU=SERVERS,ou=COMPUTERS,ou=ITD," + $SearchBaseDomain) Write-Warning -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-Warning ("[$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-Warning ("[$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-Warning "[$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-Warning -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 $CcmCred -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] } } # vcenter tags #AppName Tag Write-Warning "[$FQDN]: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-Warning "[$FQDN]: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}"); } #Start-Sleep -Seconds 180 Write-Warning -Message '[$FQDN]: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-Warning "[$FQDN]:End" } If ($WebhookData) { If ($WebhookData.RequestHeader.ITD -eq 'mXJU74ABYyDHcVY6iJihPDk8LidJ2ibBA2sA3RAwKaBHS6Gw7Rr2Zz5JZAhPm6wMuvY7X54ZzJxAXaM7ig3PHG4MKvtkBf8X7q3jGNcePgUqg9WCwCSJ3JWG7AA6M39x4vpihKeZV') { Write-Verbose "Header has required data" Write-Host $ } Else { Write-Error "Header missing required data" exit; } $InputParams = (ConvertFrom-Json -InputObject $WebhookData.RequestBody) $IaasAutoCred = Get-AutomationPSCredential -Name 'ITD Iaas Automation' Set-PowerCLIConfiguration -DefaultVIServerMode Multiple -ParticipateInCeip $false -Confirm:$false -InvalidCertificateAction Ignore $Body = $InputParams <# $Body = " ComputerName=$ComputerName CPU=$CPU MemoryGB=$MemoryGB Subnet=$Subnet OS=$OS Environment=$Environment Datacenter=$Datacenter LicensingRestrictions=$LicensingRestrictions AppName=$AppName RunningOn=$env:computername RunningAs=$env:username Credential=$Credential.UserName "#> $ComputerName = $InputParams.target_hostname.ToLower() $TimeStamp = Get-Date -UFormat "%Y%m%d%H%M%S" $InputParams | Export-Csv "E:\AutoBuildInputFiles\$ComputerName-$TimeStamp.csv" -NoTypeInformation Send-MailMessage -From "zmauto@nd.gov" -To "zmeier@nd.gov" -SmtpServer apprelay1.nd.gov -Body $Body -Subject ("AutoBuildAA-$ComputerName-" + $InputParams.target_platform) <#switch ($InputParams.target_platform) { 'azure' { } 'vmware' { Connect-VIServer -Server itdvmvc1.nd.gov, itdvmvc2.nd.gov -Credential $IaasAutoCred New-ITDVMwareWindowsVMAA -ComputerName $InputParams.target_hostname ` -CPU $InputParams.target_cpus ` -MemoryGB $InputParams.target_memory ` -DiskOS $InputParams.target_osdisk ` -DiskSwap $InputParams.target_datadisk ` -Subnet $InputParams.target_subnet ` -OS $InputParams.target_os ` -Environment $InputParams.target_environment ` -Datacenter $InputParams.target_datacenter ` -AppName $InputParams.target_vm_app_name ` -LicensingRestrictions $InputParams.target_licensingrestrictions ` -Credential $IaasAutoCred Disconnect-VIServer -Server * -Confirm:$false -ErrorAction SilentlyContinue } }#> $postParams = [PSCustomObject]@{ AutomationName = "Infra-VMware"; Action = 'Provisioning'; Units = 180; Platform = 'PowerShell-VMware-VMWindows'; } Invoke-RestMethod -Uri http://itdnettools.nd.gov/services/automation-tracking.py -Method POST -Body ($postParams | ConvertTo-Json) } Else { Write-Error "Runbook must be started from webhook" }