#Requires -Modules 'ADDSDeployment' [CmdletBinding()] Param( # No parameters ) $NewEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' ErrorAction = 'SilentlyContinue' } New-EventLog @NewEventLogSplat $WriteEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' EntryType = 'Information' EventID = 1 Message = 'OVF-Properties sequence initiated' } Write-EventLog @WriteEventLogSplat $VMwareToolsExecutable = "C:\Program Files\VMware\VMware Tools\vmtoolsd.exe" [xml]$ovfEnv = & $VMwareToolsExecutable --cmd "info-get guestinfo.ovfEnv" | Out-String $ovfProperties = $ovfEnv.ChildNodes.NextSibling.PropertySection.Property $ovfPropertyValues = @{} foreach ($ovfProperty in $ovfProperties) { $ovfPropertyValues[$ovfProperty.key] = $ovfProperty.Value } # Check for mandatory values Switch ($ovfPropertyValues['deployment.type']) { 'primary' { $MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.netbiosname', 'addsconfig.administratorpw', 'addsconfig.safemodepw', 'addsconfig.ntpserver'), @() } 'secondary' { $MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.dnsserver', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.netbiosname', 'addsconfig.administratorpw', 'addsconfig.safemodepw', 'dhcpconfig.startip', 'dhcpconfig.endip', 'dhcpconfig.subnetmask', 'dhcpconfig.gateway', 'dhcpconfig.leaseduration'), @() } 'standalone' { $MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.netbiosname', 'addsconfig.administratorpw', 'addsconfig.safemodepw', 'addsconfig.ntpserver', 'dhcpconfig.startip', 'dhcpconfig.endip', 'dhcpconfig.subnetmask', 'dhcpconfig.gateway', 'dhcpconfig.leaseduration'), @() } default { # Mandatory values missing, cannot provision. $WriteEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' EntryType = 'Error' EventID = 66 Message = "Unexpected or no value set for property 'deployment.type', cannot provision." } Write-EventLog @WriteEventLogSplat & schtasks.exe /Change /TN 'OVF-Properties' /DISABLE Stop-Computer -Force Exit } } ForEach ($Property in $MandatoryProperties) { If (!$ovfPropertyValues[$Property]) { $MissingProperties += $Property } } If ($MissingProperties.Length -gt 0) { # Mandatory values missing, cannot provision. $WriteEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' EntryType = 'Error' EventID = 66 Message = "Missing values for mandatory properties $(($MissingProperties | ForEach-Object {'{0}' -f $_}) -join ', '), cannot provision." } Write-EventLog @WriteEventLogSplat & schtasks.exe /Change /TN 'OVF-Properties' /DISABLE Stop-Computer -Force Exit } # Set hostname and description If ($Env:ComputerName -ne $ovfPropertyValues['guestinfo.hostname']) { $NewToastSplat = @{ Title = 'OVF Properties' Text = 'Configuring hostname and description...' } New-ToastNotification @NewToastSplat $RenameComputerSplat = @{ NewName = $ovfPropertyValues['guestinfo.hostname'] Force = $True Confirm = $False } Rename-Computer @RenameComputerSplat $SetCimInstanceSplat = @{ InputObject = (Get-CimInstance -ClassName 'Win32_OperatingSystem') Property = @{ Description = $ovfPropertyValues['guestinfo.hostname'] } } Set-CimInstance @SetCimInstanceSplat # Restart the computer to apply changes Restart-Computer -Force Exit } # Configure network interface If ((Get-WmiObject -Class 'Win32_NetworkAdapterConfiguration').IPAddress -NotContains $ovfPropertyValues['guestinfo.ipaddress']) { $NewToastSplat = @{ Title = 'OVF Properties' Text = 'Configuring network...' } New-ToastNotification @NewToastSplat $NewNetIPAddressSplat = @{ InterfaceAlias = (Get-NetAdapter).Name AddressFamily = 'IPv4' IPAddress = $ovfPropertyValues['guestinfo.ipaddress'] PrefixLength = $ovfPropertyValues['guestinfo.prefixlength'] DefaultGateway = $ovfPropertyValues['guestinfo.gateway'] } New-NetIPAddress @NewNetIPAddressSplat # Wait for network connection to become available $Timestamp, $TimeoutMinutes = (Get-Date), 5 Do { If ($Timestamp.AddMinutes($TimeoutMinutes) -lt (Get-Date)) { $WriteEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' EntryType = 'Warning' EventID = 13 Message = "Timeout after $($TimeoutMinutes) minutes waiting for network connection to become available." } Write-EventLog @WriteEventLogSplat Break } Start-Sleep -Milliseconds 250 $GetNetIPAddressSplat = @{ InterfaceAlias = (Get-NetAdapter).Name AddressFamily = 'IPv4' ErrorAction = 'SilentlyContinue' } } Until ((Get-NetIPAddress @GetNetIPAddressSplat).AddressState -eq 'Preferred') $OldErrorActionPreference, $ErrorActionPreference = $ErrorActionPreference, 'SilentlyContinue' $TestNetConnectionSplat = @{ ComputerName = ([IPAddress]$ovfPropertyValues['guestinfo.dnsserver']).IPAddressToString InformationLevel = 'Quiet' } $SetDnsClientServerAddressSplat = @{ InterfaceAlias = (Get-NetAdapter).Name ServerAddresses = If ( [boolean]($ovfPropertyValues['guestinfo.dnsserver'] -as [IPaddress]) -and (Test-NetConnection @TestNetConnectionSplat)) { ($ovfPropertyValues['guestinfo.dnsserver']) } else { ('127.0.0.1') } Validate = $False } Set-DnsClientServerAddress @SetDnsClientServerAddressSplat $ErrorActionPreference, $OldErrorActionPreference = $OldErrorActionPreference, $NULL } # Promote to Domain Controller If ((4,5) -NotContains (Get-WmiObject -Class 'Win32_ComputerSystem').DomainRole) { $NewToastSplat = @{ Title = 'OVF Properties' Text = 'Configuring local administrator password...' } New-ToastNotification @NewToastSplat # Change password of built-in Administrator $BuiltinAdministrator = (Get-LocalUser | Where-Object {$_.SID -match '-500'}) $ConvertToSecureStringSplat = @{ String = $ovfPropertyValues['addsconfig.administratorpw'] AsPlainText = $True Force = $True } $SetLocalUserSplat = @{ InputObject = $BuiltinAdministrator Password = ConvertTo-SecureString @ConvertToSecureStringSplat PasswordNeverExpires = $True AccountNeverExpires = $True ### This setting is not allowed on the last administrator #UserMayChangePassword = $False Confirm = $False } Set-LocalUser @SetLocalUserSplat $NewToastSplat = @{ Title = 'OVF Properties' Text = 'Promoting to Domain Controller...' } New-ToastNotification @NewToastSplat $ResolveDNSNameSplat = @{ Name = "_ldap._tcp.dc._msdcs.$($ovfPropertyValues['addsconfig.domainname'])" ErrorAction = 'SilentlyContinue' } $DNSRecord = Resolve-DnsName @ResolveDNSNameSplat If ([boolean]$DNSRecord.PrimaryServer -eq $False) { # No Primary Domain Controller found, installing as primary $InstallADDSForestSplat = @{ DomainName = $ovfPropertyValues['addsconfig.domainname'] DomainNetbiosName = $ovfPropertyValues['addsconfig.netbiosname'] SafeModeAdministratorPassword = ConvertTo-SecureString $ovfPropertyValues['addsconfig.safemodepw'] -AsPlainText -Force InstallDns = $True DomainMode = 'WinThreshold' ForestMode = 'WinThreshold' Confirm = $False Force = $True ErrorAction = 'Stop' } Try { Install-ADDSForest @InstallADDSForestSplat # Previous cmdlet performs a reboot on completion; so these are commented out # Restart-Computer -Force # Exit } Catch { & schtasks.exe /Change /TN 'OVF-Properties' /DISABLE Stop-Computer -Force Exit } } Else { # Primary Domain Controller is present, installing as secondary $InstallADDSDomainControllerSplat = @{ DomainName = $ovfPropertyValues['addsconfig.domainname'] Credential = New-Object System.Management.Automation.PSCredential("$($ovfPropertyValues['addsconfig.netbiosname'])\$($BuiltinAdministrator.Name)", (ConvertTo-SecureString @ConvertToSecureStringSplat)) SafeModeAdministratorPassword = ConvertTo-SecureString $ovfPropertyValues['addsconfig.safemodepw'] -AsPlainText -Force InstallDns = $True Confirm = $False Force = $True ErrorAction = 'Stop' } Try { Install-ADDSDomainController @InstallADDSDomainControllerSplat # Previous cmdlet performs a reboot on completion; so these are commented out # Restart-Computer -Force # Exit } Catch { & schtasks.exe /Change /TN 'OVF-Properties' /DISABLE Stop-Computer -Force Exit } } } # Wait for Active Directory to become available $NewToastSplat = @{ Title = 'OVF Properties' Text = 'Waiting for Active Directory services...' } New-ToastNotification @NewToastSplat $Timestamp, $TimeoutMinutes = (Get-Date), 15 Do { If ($Timestamp.AddMinutes($TimeoutMinutes) -lt (Get-Date)) { $WriteEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' EntryType = 'Warning' EventID = 13 Message = "Timeout after $($TimeoutMinutes) minutes waiting for Active Directory to become available." } Write-EventLog @WriteEventLogSplat Break } Start-Sleep -Seconds 30 $GetADComputerSplat = @{ Identity = $Env:ComputerName ErrorAction = 'SilentlyContinue' } Get-ADComputer @GetADComputerSplat | Out-Null } Until ($?) # Iterate through and invoke all payload scripts #! TODO: add registry values to determine which scripts have already been invoked (in case of intermediate reboots) $GetItemSplat = @{ Path = "$($PSScriptRoot)\Scripts\*.ps1" } Get-Item @GetItemSplat | ForEach-Object { Try { $NewToastSplat = @{ Title = 'OVF Properties' Text = "Running script: '$($_.FullName)'" } New-ToastNotification @NewToastSplat $WriteEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' EntryType = 'Information' EventID = 4 Message = "Running script: '$($_.FullName)'" } Write-EventLog @WriteEventLogSplat & $_.FullName -Parameter $ovfPropertyValues } Catch { $WriteEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' EntryType = 'Error' EventID = 66 Message = $_.Exception.Message } Write-EventLog @WriteEventLogSplat } } $NewToastSplat = @{ Title = 'OVF Properties' Text = "Sequence finished; ready for use!" } New-ToastNotification @NewToastSplat $WriteEventLogSplat = @{ LogName = 'Application' Source = 'OVF-Properties' EntryType = 'Information' EventID = 42 Message = 'OVF-Properties sequence applied and finished' } Write-EventLog @WriteEventLogSplat & schtasks.exe /Change /TN 'OVF-Properties' /DISABLE Function New-ToastNotification { Param( [Parameter] [string]$Stream = 'OVF Properties', [Parameter(Mandatory)] [string]$Title, [Parameter(Mandatory)] [string]$Text ) $Template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02) $XML = [xml]$Template.GetXml() $XML.SelectSingleNode("toast/visual/binding/text[@id='1']").InnerText = $Title $XML.SelectSingleNode("toast/visual/binding/text[@id='2']").InnerText = $Text $SerializedXml = New-Object Windows.Data.Xml.Dom.XmlDocument $SerializedXml.LoadXml($XML.OuterXml) $Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($Stream) $Notifier.Show([Windows.UI.Notifications.ToastNotification]::new($SerializedXml)) }