UnattendResources/Logon.ps1

$ErrorActionPreference = "Stop"
$resourcesDir = "$ENV:SystemDrive\UnattendResources"
$configIniPath = "$resourcesDir\config.ini"
$customScriptsDir = "$resourcesDir\CustomScripts"
$logFile = "$resourcesDir\image-generation-log.txt"

function Set-PersistDrivers {
    Param(
    [parameter(Mandatory=$true)]
    [string]$Path,
    [switch]$Persist
    )
    if (!(Test-Path $Path)) {
        return $false
    }
    try {
        $xml = [xml](Get-Content $Path)
    } catch {
        Write-Error "Failed to load $Path"
        return $false
    }
    if (!$xml.unattend.settings) {
        return $false
    }
    foreach ($i in $xml.unattend.settings) {
        if ($i.pass -eq "generalize") {
            $index = [array]::IndexOf($xml.unattend.settings, $i)
            if ($xml.unattend.settings[$index].component -and $xml.unattend.settings[$index].component.PersistAllDeviceInstalls -ne $Persist.ToString()) {
                $xml.unattend.settings[$index].component.PersistAllDeviceInstalls = $Persist.ToString()
            }
        }
    }
    $xml.Save($Path)
    Write-Log "Drivers" "PersistDrivers was set to ${Persist} in the unattend.xml"
}

function Set-UnattendEnableSwap {
    Param(
    [parameter(Mandatory=$true)]
    [string]$Path
    )
    if (!(Test-Path $Path)) {
        return $false
    } try {
        $xml = [xml](Get-Content $Path)
    } catch {
        Write-Error "Failed to load $Path"
        return $false
    }
    if (!$xml.unattend.settings) {
        return $false
    }
    foreach ($i in $xml.unattend.settings) {
        if ($i.pass -eq "specialize") {
            $index = [array]::IndexOf($xml.unattend.settings, $i)
            if ($xml.unattend.settings[$index].component.RunSynchronous.RunSynchronousCommand.Order) {
                $xml.unattend.settings[$index].component.RunSynchronous.RunSynchronousCommand.Order = "2"
            }
            [xml]$RunSynchronousCommandXml = @"
        <RunSynchronousCommand xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
          <Order>1</Order>
          <Path>"C:\Windows\System32\reg.exe" ADD "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v "PagingFiles" /d "?:\pagefile.sys" /f</Path>
          <Description>Set page file to be automatically managed by the system</Description>
          <WillReboot>Never</WillReboot>
        </RunSynchronousCommand>
"@

          $xml.unattend.settings[$index].component.RunSynchronous.AppendChild($xml.ImportNode($RunSynchronousCommandXml.RunSynchronousCommand, $true))
        }
    }
    $xml.Save($Path)
    Write-Log "Swap(1)" "Was enabled in the unattend.xml"
}

function Optimize-SparseImage {
    $zapfree = "$resourcesDir\zapfree.exe"
    if ( Test-Path $zapfree ) {
        Write-Host "Optimizing for sparse image..."
        & $zapfree -z $ENV:SystemDrive
        Write-Log "ZapFree" "Image was zeroed successfully"
    } else {
        Write-Debug "No zapfree. Image not optimized."
    }
}

function Clean-UpdateResources {
    $HOST.UI.RawUI.WindowTitle = "Running update resources cleanup"
    # We're done, disable AutoLogon
    Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name Unattend*
    Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name AutoLogonCount -ErrorAction SilentlyContinue

    # Cleanup
    Remove-Item -Recurse -Force $resourcesDir
    Remove-Item -Force "$ENV:SystemDrive\Unattend.xml"
    Write-Log "Cleanup(1)" "Image was cleaned up successfully"

}

function Clean-WindowsUpdates {
    Param(
        $PurgeUpdates
    )
    $HOST.UI.RawUI.WindowTitle = "Running Dism cleanup..."
    if (([System.Environment]::OSVersion.Version.Major -gt 6) -or ([System.Environment]::OSVersion.Version.Minor -ge 2))
    {
        if (!$PurgeUpdates) {
            Dism.exe /Online /Cleanup-Image /StartComponentCleanup
        } else {
            Dism.exe /Online /Cleanup-Image /StartComponentCleanup /ResetBase
        }
        if ($LASTEXITCODE) {
            throw "Dism.exe clean failed"
        }
        Write-Log "Cleanup" "Updates were cleaned up successfully"
    }
}

function Run-Defragment {
    $HOST.UI.RawUI.WindowTitle = "Running Defrag..."
    #Defragmenting all drives at normal priority
    defrag.exe /C /H /V
    if ($LASTEXITCODE) {
        throw "Defrag.exe failed"
    }
    Write-Log "Defragment" "Image was defragemented successfully"
}

function Release-IP {
    $HOST.UI.RawUI.WindowTitle = "Releasing IP..."
    ipconfig.exe /release
    if ($LASTEXITCODE) {
        throw "IPconfig release failed"
    }
    Write-Log "Ipconfig" "IPs were released successfully"
}

function Install-WindowsUpdates {
    Import-Module "$resourcesDir\WindowsUpdates\WindowsUpdates"
    $BaseOSKernelVersion = [System.Environment]::OSVersion.Version
    $OSKernelVersion = ($BaseOSKernelVersion.Major.ToString() + "." + $BaseOSKernelVersion.Minor.ToString())

    #Note (cgalan): Some updates are black-listed as they are either failing to install or superseded by the newer updates.
    $KBIdsBlacklist = @{
        "6.3" = @("KB2887595")
    }
    $excludedUpdates = $KBIdsBlacklist[$OSKernelVersion]
    $updates = ExecRetry {
        Get-WindowsUpdate -Verbose -ExcludeKBId $excludedUpdates
    } -maxRetryCount 30 -retryInterval 1
    $maximumUpdates = 100
    if (!$updates.Count) {
        $updates = [array]$updates
    }
    if ($updates) {
        $availableUpdatesNumber = $updates.Count
        Write-Host "Found $availableUpdatesNumber updates. Installing..."
        try {
            #Note (cgalan): In case the update fails, we need to reboot the instance in order for the updates
            # to be retrieved on a changed system state and be applied correctly.
            Install-WindowsUpdate -Updates $updates[0..$maximumUpdates]
         } finally {
            Write-Log "Updates(${availableUpdatesNumber})" "Available updates were installed successfully. Rebooting..."
            Restart-Computer -Force
            exit 0
         }
    } elseif ((Get-RebootRequired)) {
        Write-Log "Updates(reboot)" "No updates available, but a reboot is required. Rebooting..."
        Restart-Computer -Force
        exit 0
    }
    Write-Log "Updates" "All available updates were installed successfully"
}

function ExecRetry($command, $maxRetryCount=4, $retryInterval=4) {
    $currErrorActionPreference = $ErrorActionPreference
    $ErrorActionPreference = "Continue"

    $retryCount = 0
    while ($true) {
        try {
            $res = Invoke-Command -ScriptBlock $command
            $ErrorActionPreference = $currErrorActionPreference
            return $res
        } catch [System.Exception] {
            $retryCount++
            if ($retryCount -ge $maxRetryCount) {
                $ErrorActionPreference = $currErrorActionPreference
                throw
            } else {
                if($_) {
                    Write-Warning $_
                }
                Start-Sleep $retryInterval
            }
        }
    }
}

function Disable-Swap {
    $computerSystem = Get-WmiObject Win32_ComputerSystem
    if ($computerSystem.AutomaticManagedPagefile) {
        $computerSystem.AutomaticManagedPagefile = $False
        $computerSystem.Put()
    }
    $pageFileSetting = Get-WmiObject Win32_PageFileSetting
    if ($pageFileSetting) {
        $pageFileSetting.Delete()
    }
    Write-Log "Swap" "Swap was disabled successfully"
}

function License-Windows {
    Param(
         [parameter(Mandatory=$true)]
         [string]$ProductKey
    )
    $licenseWindows = $false
    $slmgrOutput = cscript.exe "$env:windir\system32\slmgr.vbs" /dli
    if ($lastExitCode) {
        throw "Windows license details could not be retrieved."
    }

    if ($ProductKey -eq "default_kms_key") {
        if (!([System.Environment]::OSVersion.Version.Major -gt 6 `
            -or [System.Environment]::OSVersion.Version.Minor -ge 2)) {
            Write-Log "License" 'KMS trial licensing reset not required. Running on Windows lte Windows 2008 R2'
            return
        }
        if ($slmgrOutput -like "*VOLUME_KMSCLIENT*") {
            $licensingOutput = cscript.exe "$env:windir\system32\slmgr.vbs" /upk
            if ($LASTEXITCODE) {
                Write-Log "License" "Error: KMS trial licensing could not be reset"
                throw $licensingOutput
            }
            Write-Log "License" "KMS trial licensing was reset"
        }
        return
    }

    if ($slmgrOutput -like "*License Status: Licensed*") {
       $partialKey = ($slmgrOutput -like "Partial Product Key*").Replace("Partial Product Key:","").Trim()
       Write-Host "Windows is already licensed with partial key: $partialKey"
       if (!(($ProductKey -split "-") -contains $partialKey)) {
           $licenseWindows = $true
       }
    } else {
        $licenseWindows = $true
    }
    if ($licenseWindows) {
       $licensingOutput = cscript.exe "$env:windir\system32\slmgr.vbs" /ipk $ProductKey
       if ($lastExitCode) {
           Write-Log "License" "Error: Windows could not be licensed"
           throw $licensingOutput
       } else {
           Write-Host "Windows has been successfully licensed."
       }
        Write-Log "License" "Windows was licensed successfully"
    } else {
       Write-Host "Windows will not be licensed."
    }
}

function Get-AdministratorAccount {
    <#
    .SYNOPSIS
    Helper function to return the local Administrator account name.
    This works with internationalized versions of Windows.
    #>

    PROCESS {
        $version = $PSVersionTable.PSVersion.Major
        if ($version -lt 4) {
            # Get-CimInstance is not supported on powershell versions earlier then 4
            New-Alias -Name Get-ManagementObject -Value Get-WmiObject
        } else {
            New-Alias -Name Get-ManagementObject -Value Get-CimInstance
        }
        $SID = "S-1-5-21-%-500"
        $modifier = " LIKE "
        $query = ("SID{0}'{1}'" -f @($modifier, $SID))
        $s = Get-ManagementObject -Class Win32_UserAccount -Filter $query
        if (!$s) {
            throw "SID not found: $SID"
        }
        return $s.Name
    }
}

function Enable-AdministratorAccount {
    [string]$username = Get-AdministratorAccount
    $setupCompletePath = "$env:windir\Setup\Scripts\SetupComplete.cmd"
    $activate = "powershell -c net user {0} /active:yes" -f $username
    $expiration = 'wmic path Win32_UserAccount WHERE Name="{0}" set PasswordExpires=true' -f $username
    $logonReset = "net.exe user {0} /logonpasswordchg:yes" -f $username
    Add-Content -Encoding Ascii -Value $activate -Path $setupCompletePath
    Add-Content -Encoding Ascii -Value $expiration -Path $setupCompletePath
    Add-Content -Encoding Ascii -Value $logonReset -Path $setupCompletePath
    & cmd.exe /c "net.exe user $username """
    # Note(atira): net.exe can set an empty password only if it is run from cmd.exe
    if ($LASTEXITCODE) {
        Write-Log "Administrator" "Error: Account could not be enabled"
        throw "Resetting $username password failed."
    }
    Write-Log "Administrator" "Account was enabled successfully"
}

function Is-WindowsClient {
        $Path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\\'
    try {
        if ((Get-ItemProperty -Path $Path -Name InstallationType).InstallationType -eq "Client") {
            return $true
        }
    } catch { }
    return $false
}

function Run-CustomScript {
    Param($ScriptFileName)
    $fullScriptFilePath = Join-Path $customScriptsDir $ScriptFileName
    if (Test-Path $fullScriptFilePath) {
        Write-Host "Executing script $fullScriptFilePath"
        & $fullScriptFilePath
        if ($LastExitCode -eq 1004) {
            Write-Log "CustomScripts(${ScriptFileName})" "Required to exit"
            exit 0
        }
        if ($LastExitCode -eq 1005) {
            # exit this script and reboot
            Write-Log "CustomScripts(${ScriptFileName})" "Required to reboot. Rebooting..."
            shutdown -r -t 0 -f
            exit 0
        }
        if ($LastExitCode -eq 1006) {
            # exit this script and shutdown
            Write-Log "CustomScripts(${ScriptFileName})" "Required to shut down. Shutting down..."
            shutdown -s -t 0 -f
            exit 0
        }
        if ($LastExitCode -eq 1) {
            Write-Log "CustomScripts(${ScriptFileName})" "${ScriptFileName} failed to run"
            throw "Script $ScriptFileName executed unsuccessfully"
        }
        Write-Log "CustomScripts(${ScriptFileName})" "${ScriptFileName} executed successfully"
    }
}

function Install-VMwareTools {
    $Host.UI.RawUI.WindowTitle = "Installing VMware tools..."
    $vmwareToolsInstallArgs = "/s /v /qn REBOOT=R /l $ENV:Temp\vmware_tools_install.log"
    if (Test-Path $resourcesDir) {
        $vmwareToolsPath = Join-Path $resourcesDir "\VMware-tools.exe"
    }
    $p = Start-Process -FilePath $vmwareToolsPath -ArgumentList $vmwareToolsInstallArgs -Wait -verb runAS
    if ($p.ExitCode) {
        Write-Log "VMwareTools" "Error: Tools could not be installed"
        throw "VMware tools setup failed" 
    }
    Write-Log "VMwareTools" "Tools installed successfully"
}

function Write-HostLog {
    <#
    .SYNOPSIS
     Uses KVP to communicate to the Hyper-V host the status of the various stages
     of the imaging generation. This feature works only if the VM where this script
     runs is spawned on Hyper-V and the 'Data Exchange' (aka Key Value Pair Exchange)
     is enabled for the instance. On KVM / ESXi / baremetal, this method is NOOP.
    #>

    Param($Stage = "Default",
          $StageLog
    )

    $KVPOutgoingRegistryKey = "HKLM://SOFTWARE/Microsoft/Virtual Machine/Auto"
    if ($Stage -and $StageLog -and (Test-Path $KVPOutgoingRegistryKey)) {
        Set-ItemProperty $KVPOutgoingRegistryKey -Name "ImageGenerationLog-${Stage}" `
            -Value $StageLog -ErrorAction SilentlyContinue
    }
}

function Write-Log {
    <#
    .SYNOPSIS
     Writes timestamped logs to the console, to the log file and via KVP if on Hyper-V platform.
    #>

    Param($Stage = "Default",
          $StageLog
    )

    $logMessage = "{0} - {1}: {2}" -f @((Get-Date), $Stage, $StageLog)
    Write-Host $logMessage
    Add-Content -Value $logMessage -Path $logFile -Force -Encoding Ascii -ErrorAction SilentlyContinue
    Write-HostLog $Stage $StageLog
}

function Disable-FirstLogonAnimation {
    if (([System.Environment]::OSVersion.Version.Major -gt 6) -or ([System.Environment]::OSVersion.Version.Minor -ge 2)) {
        New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" `
            -Name "EnableFirstLogonAnimation" -Value 0 -Type DWORD -Force
    }
    Write-Log "FirstLogonAnimation" "First logon animation was disabled"
}

function Enable-AlwaysActiveMode {
    # This mode is the High Performance plus some tweaks to keep
    # the screen always on and to not sleep

    # The user should not automatically log off or the screen to become black
    New-ItemProperty `
        -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System" `
        -Name "InactivityTimeoutSecs" `
        -PropertyType "DWord" `
        -Value "0" -Force

    # This is changing the settings from the machine (power mode) perspective
    powercfg /setactive "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c" # High performance mode
    powercfg -change -monitor-timeout-ac 0
    powercfg -change -monitor-timeout-dc 0
    powercfg -change -standby-timeout-ac 0
    powercfg -change -standby-timeout-dc 0
    powercfg -hibernate OFF
    Write-Log "AlwaysActive" "Always active mode was set."
}

function Set-CustomTimezone {
    Param(
        [parameter(Mandatory=$true)]
        [String]$CustomTimezone
    )

    tzutil.exe /s "${CustomTimezone}"
    if ($LastExitCode) {
        throw "Failed to set custom timezone: ${CustomTimezone}"
    }
    Write-Log "Customization(1)" "Set timezone: ${CustomTimezone}"
}

function Set-CustomNtpServers {
    Param(
        [parameter(Mandatory=$true)]
        [String]$CustomNtpServers
    )

    w32tm.exe /config /syncfromflags:manual /manualpeerlist:"${CustomNtpServers}"
    if ($LastExitCode) {
        throw "Failed to set custom ntp servers: ${CustomNtpServers}"
    }
    Set-Service "W32time" -StartupType Automatic
    Write-Log "Customization(2)" "Set ntp servers: ${CustomNtpServers}"
}

function Enable-PingFirewallRules {
    netsh.exe advfirewall firewall add rule name="Allow IPv4 ping requests" protocol="icmpv4:8,any" dir=in action=allow
    if ($LASTEXITCODE) {
        throw "Failed to enable IPv4 ping firewall rules"
    }
    netsh.exe advfirewall firewall add rule name="Allow IPv6 ping requests" protocol="icmpv6:8,any" dir=in action=allow
    if ($LASTEXITCODE) {
        throw "Failed to enable IPv6 ping firewall rules"
    }
    Write-Log "Ping" "Enabled ping for IPv4 and IPv6"
}

function Disable-IPv6TemporaryAddress {
    Set-NetIPv6Protocol -RandomizeIdentifiers Disabled
    Set-NetIPv6Protocol -UseTemporaryAddresses Disabled
    Write-Log "IPv6" "RandomizeIdentifiers and UseTemporaryAddresses were disabled"
}

function Enable-ShutdownWithoutLogon {
    Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\" `
       -Name shutdownwithoutlogon -Value 1 -Type DWord
    Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\" `
       -Name ShutdownWarningDialogTimeout -Value 1 -Type DWord
    Write-Log "ShutdownWithoutLogon" "Shutdown without logon was enabled"
}

try {
    Write-Log "StatusInitial" "Automated instance configuration started..."
    $psVersion = "PS version {0}." -f $PSVersionTable.PSVersion.ToString()
    $windowsVersion = "Windows version {0}." -f [System.Environment]::OSVersion.Version.ToString()
    Write-Log "WindowsInfo" "${windowsVersion} ${psVersion}"

    Import-Module "$resourcesDir\ini.psm1"

    $installUpdates = Get-IniFileValue -Path $configIniPath -Section "updates" -Key "install_updates" -Default $false -AsBoolean
    $persistDrivers = Get-IniFileValue -Path $configIniPath -Section "sysprep" -Key "persist_drivers_install" -Default $true -AsBoolean
    $purgeUpdates = Get-IniFileValue -Path $configIniPath -Section "updates" -Key "purge_updates" -Default $false -AsBoolean
    $disableSwap = Get-IniFileValue -Path $configIniPath -Section "sysprep" -Key "disable_swap" -Default $false -AsBoolean
    $enableAdministrator = Get-IniFileValue -Path $configIniPath -Section "DEFAULT" `
                                            -Key "enable_administrator_account" -Default $false -AsBoolean
    $goldImage = Get-IniFileValue -Path $configIniPath -Section "DEFAULT" -Key "gold_image" -Default $false -AsBoolean
    try {
        $vmwareToolsPath = Get-IniFileValue -Path $configIniPath -Section "DEFAULT" -Key "vmware_tools_path"
    } catch {}
    try {
        $productKey = Get-IniFileValue -Path $configIniPath -Section "DEFAULT" -Key "product_key"
    } catch {}
    $serialPortName = Get-IniFileValue -Path $configIniPath -Section "cloudbase_init" -Key "serial_logging_port"
    try {
        $runCloudbaseInitUnderLocalSystem = Get-IniFileValue -Path $configIniPath -Section "cloudbase_init" `
            -Key "cloudbase_init_use_local_system" -Default $false -AsBoolean
    } catch {}
    try {
        $enableShutdownWithoutLogon = Get-IniFileValue -Path $configIniPath -Key "enable_shutdown_without_logon" `
            -Default $false -AsBoolean
    } catch {}
    try {
        $enablePing = Get-IniFileValue -Path $configIniPath -Key "enable_ping_requests" `
            -Default $false -AsBoolean
    } catch {}
    try {
        $useIpv6EUI64 = Get-IniFileValue -Path $configIniPath -Key "enable_ipv6_eui64" `
            -Default $false -AsBoolean
    } catch {}
    try {
        $disableFirstLogonAnimation = Get-IniFileValue -Path $configIniPath -Section "DEFAULT" -Key "disable_first_logon_animation" `
            -Default $false -AsBoolean
    } catch{}
    try {
        $enableAlwaysActiveMode = Get-IniFileValue -Path $configIniPath -Section "DEFAULT" -Key "enable_active_mode" `
            -Default $false -AsBoolean
    } catch{}
    try {
        $cleanUpdatesOnline = Get-IniFileValue -Path $configIniPath -Section "updates" -Key "clean_updates_online" `
            -Default $true -AsBoolean
    } catch{}
    try {
        $customTimezone = Get-IniFileValue -Path $configIniPath -Section "custom" -Key "time_zone"
    } catch{}
    try {
        $customNtpServers = Get-IniFileValue -Path $configIniPath -Section "custom" -Key "ntp_servers"
    } catch{}

    if ($productKey) {
        License-Windows $productKey
    }

    Run-CustomScript "RunBeforeWindowsUpdates.ps1"
    if ($installUpdates) {
        Install-WindowsUpdates
    }

    if ($cleanUpdatesOnline) {
        try {
            ExecRetry {
                Clean-WindowsUpdates -PurgeUpdates $purgeUpdates
            }
        } catch {
            Write-Log "DISM" "Failed to cleanup updates. Rebooting..."
            Restart-Computer -Force
            exit 0
        }
    }

    Run-CustomScript "RunAfterWindowsUpdates.ps1"

    if ($goldImage) {
        # Cleanup and shutting down the instance
        Remove-Item -Recurse -Force $resourcesDir
        shutdown -s -t 0 -f
    }

    if ($vmwareToolsPath) {
        Install-VMwareTools
    }

    Run-CustomScript "RunBeforeCloudbaseInitInstall.ps1"
    $Host.UI.RawUI.WindowTitle = "Installing Cloudbase-Init..."

    $cloudbaseInitInstallDir = Join-Path $ENV:ProgramFiles "Cloudbase Solutions\Cloudbase-Init"
    $CloudbaseInitMsiPath = "$resourcesDir\CloudbaseInit.msi"
    $CloudbaseInitConfigPath = "$resourcesDir\cloudbase-init.conf"
    $CloudbaseInitUnattendedConfigPath = "$resourcesDir\cloudbase-init-unattend.conf"
    $CloudbaseInitMsiLog = "$resourcesDir\CloudbaseInit.log"

    if (!$serialPortName) {
        $serialPorts = Get-WmiObject Win32_SerialPort
        if ($serialPorts) {
            $serialPortName = $serialPorts[0].DeviceID
        }
    }

    $msiexecArgumentList = "/i $CloudbaseInitMsiPath /qn /l*v $CloudbaseInitMsiLog"
    if ($serialPortName) {
        $msiexecArgumentList += " LOGGINGSERIALPORTNAME=$serialPortName"
    }

    $cloudbaseInitUser = 'cloudbase-init'
    if ($runCloudbaseInitUnderLocalSystem) {
        $msiexecArgumentList += " RUN_SERVICE_AS_LOCAL_SYSTEM=1"
        $cloudbaseInitUser = "LocalSystem"
    }

    $p = Start-Process -Wait -PassThru -FilePath msiexec -ArgumentList $msiexecArgumentList
    if ($p.ExitCode -ne 0) {
        Write-Log "Cloudbase-Init" "Failed to install cloudbase-init"
        throw "Installing $CloudbaseInitMsiPath failed. Log: $CloudbaseInitMsiLog"
    }

    if (Test-Path $CloudbaseInitConfigPath) {
        Copy-Item -Force $CloudbaseInitConfigPath "${cloudbaseInitInstallDir}\conf\cloudbase-init.conf"
        Write-Log "CustomCloudbaseInitConfig" $CloudbaseInitConfigPath
    }
    if (Test-Path $CloudbaseInitUnattendedConfigPath) {
        Copy-Item -Force $CloudbaseInitUnattendedConfigPath "${cloudbaseInitInstallDir}\conf\cloudbase-init-unattend.conf"
        Write-Log "CustomCloudbaseInitUnattendConfig" $CloudbaseInitUnattendedConfigPath
    }

    $Host.UI.RawUI.WindowTitle = "Running SetSetupComplete..."
    & "${cloudbaseInitInstallDir}\bin\SetSetupComplete.cmd"
    Write-Log "Cloudbase-Init" "Service installed successfully under user ${cloudbaseInitUser}"
    Run-CustomScript "RunAfterCloudbaseInitInstall.ps1"

    Run-Defragment

    Release-IP

    $windowsClient = Is-WindowsClient

    if ($enableShutdownWithoutLogon) {
        Enable-ShutdownWithoutLogon
    }

    if ($windowsClient -and $disableFirstLogonAnimation) {
        Disable-FirstLogonAnimation
    }

    if ($enablePing) {
        Enable-PingFirewallRules
    }

    if ($useIpv6EUI64) {
        Disable-IPv6TemporaryAddress
    }

    if ($windowsClient -and $enableAdministrator) {
        Enable-AdministratorAccount
    }

    if ($enableAlwaysActiveMode) {
        Enable-AlwaysActiveMode
    }

    if ($customTimezone) {
        Set-CustomTimezone $customTimezone
    }

    if ($customNtpServers) {
        Set-CustomNtpServers $customNtpServers
    }

    $Host.UI.RawUI.WindowTitle = "Running Sysprep..."
    $unattendedXmlPath = "${cloudbaseInitInstallDir}\conf\Unattend.xml"
    Set-PersistDrivers -Path $unattendedXmlPath -Persist:$persistDrivers

    if ($disableSwap) {
        ExecRetry {
            Disable-Swap
        }
        Set-UnattendEnableSwap -Path $unattendedXmlPath
    }

    Run-CustomScript "RunBeforeSysprep.ps1"
    Optimize-SparseImage
    & "$ENV:SystemRoot\System32\Sysprep\Sysprep.exe" `/generalize `/oobe `/shutdown `/unattend:"$unattendedXmlPath"
    Write-Log "Sysprep" "Sysprep initiated successfully"
    Run-CustomScript "RunAfterSysprep.ps1"
    Clean-UpdateResources
    Write-Log "StatusFinal" "Waiting for sysprep to stop machine..."
} catch {
    Write-Log "ERROR" $_.Exception.ToString()
    $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
    throw
}