[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

        $EnableLocalUserSplat = @{
            InputObject = $BuiltinAdministrator
            Confirm     = $False
        }
        Enable-LocalUser @EnableLocalUserSplat
    }
}

# 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
