[CmdletBinding()] Param( # No parameters ) $SetLocationSplat = @{ Path = $PSScriptRoot } Set-Location @SetLocationSplat $NewEventLogSplat = @{ LogName = 'Application' Source = 'FirstBoot' ErrorAction = 'SilentlyContinue' } New-EventLog @NewEventLogSplat $WriteEventLogSplat = @{ LogName = 'Application' Source = 'FirstBoot' EntryType = 'Information' EventID = 1 Message = "FirstBoot sequence initiated [working directory: '$PWD']" } 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']) { 'domainmember' { $MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'addsconfig.domainname', 'addsconfig.username', 'addsconfig.password'), @() } 'standalone' { $MandatoryProperties, $MissingProperties = @('guestinfo.hostname', 'guestinfo.ipaddress', 'guestinfo.prefixlength', 'guestinfo.gateway', 'guestinfo.administratorpw', 'guestinfo.ntpserver'), @() } default { # Mandatory values missing, cannot provision. $WriteEventLogSplat = @{ LogName = 'Application' Source = 'FirstBoot' EntryType = 'Error' EventID = 66 Message = "Unexpected or no value set for property 'deployment.type', cannot provision." } Write-EventLog @WriteEventLogSplat & schtasks.exe /Change /TN 'FirstBoot' /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 = 'FirstBoot' 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 'FirstBoot' /DISABLE Stop-Computer -Force Exit } # Set hostname and description If ($Env:ComputerName -ne $ovfPropertyValues['guestinfo.hostname']) { $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']) { $NewNetIPAddressSplat = @{ InterfaceAlias = (Get-NetAdapter).Name AddressFamily = 'IPv4' IPAddress = $ovfPropertyValues['guestinfo.ipaddress'] PrefixLength = $ovfPropertyValues['guestinfo.prefixlength'] DefaultGateway = $ovfPropertyValues['guestinfo.gateway'] } $IPAddress = 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 = 'FirstBoot' 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 = @{ IPAddress = $ovfPropertyValues['guestinfo.ipaddress'] InterfaceIndex = $IPAddress.InterfaceIndex 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 } Switch ($ovfPropertyValues['deployment.type']) { 'domainmember' { # Join Active Directory domain as member If (!(Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain) { $AddComputerSplat = @{ DomainName = $ovfPropertyValues['addsconfig.domainname'] Credential = New-Object System.Management.Automation.PSCredential( $ovfPropertyValues['addsconfig.username'], (ConvertTo-SecureString $ovfPropertyValues['addsconfig.password'] -AsPlainText -Force) ) # OUPath = $ovfPropertyValues['addsconfig.organizationalunit'] Restart = $True Force = $True Confirm = $False } Add-Computer @AddComputerSplat # Previous cmdlet performs a reboot on completion; so these are commented out # Restart-Computer -Force # Exit } } 'standalone' { # Change password of built-in Administrator $BuiltinAdministrator = (Get-LocalUser | Where-Object {$_.SID -match '-500'}) $ConvertToSecureStringSplat = @{ String = $ovfPropertyValues['guestinfo.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 } } # 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" } ForEach ($Script in (Get-Item @GetItemSplat)) { Try { $WriteEventLogSplat = @{ LogName = 'Application' Source = 'FirstBoot' EntryType = 'Information' EventID = 4 Message = "Running script: '$($Script.FullName)'" } Write-EventLog @WriteEventLogSplat & $Script.FullName -Parameter $ovfPropertyValues } Catch { $WriteEventLogSplat = @{ LogName = 'Application' Source = 'FirstBoot' EntryType = 'Error' EventID = 66 Message = @" Error occurred while executing script '$($Script.Name)': $($_.Exception.Message) "@ } Write-EventLog @WriteEventLogSplat } } $WriteEventLogSplat = @{ LogName = 'Application' Source = 'FirstBoot' EntryType = 'Information' EventID = 42 Message = 'FirstBoot sequence applied and finished' } Write-EventLog @WriteEventLogSplat & schtasks.exe /Change /TN 'FirstBoot' /DISABLE