vhdbuilder/packer/windows/configure-windows-vhd.ps1 (812 lines of code) (raw):
<#
.SYNOPSIS
Used to produce Windows AKS images.
.DESCRIPTION
This script is used by packer to produce Windows AKS images.
#>
param(
[string]
$windowsSKUParam,
[string]
$provisioningPhaseParam,
[string]
$customizedDiskSizeParam
)
if (![string]::IsNullOrEmpty($windowsSKUParam))
{
Write-Log "Setting Windows SKU to $windowsSKUParam"
$env:WindowsSKU = $windowsSKUParam
}
if (![string]::IsNullOrEmpty($provisioningPhaseParam))
{
Write-Log "Setting Provisioning Phase to $provisioningPhaseParam"
$env:ProvisioningPhase = $provisioningPhaseParam
}
if (![string]::IsNullOrEmpty($customizedDiskSizeParam))
{
Write-Log "Setting Customized Disk Size to $customizedDiskSizeParam"
$env:CustomizedDiskSize = $customizedDiskSizeParam
}
$ErrorActionPreference = "Stop"
filter Timestamp
{
"$( Get-Date -Format o ): $_"
}
function Write-Log($Message)
{
$msg = $message | Timestamp
Write-Output $msg
}
. c:/k/windows-vhd-configuration.ps1
function Log-VHDFreeSize
{
Write-Log "Get Disk info"
$disksInfo = Get-CimInstance -ClassName Win32_LogicalDisk
foreach ($disk in $disksInfo)
{
if ($disk.DeviceID -eq "C:")
{
if ($disk.FreeSpace -lt $global:lowestFreeSpace)
{
Write-Log "Disk C: Free space $( $disk.FreeSpace ) is less than $( $global:lowestFreeSpace )"
}
break
}
# the break above means we'll only print this where there is no error.
Write-Log "Disk $( $disk.DeviceID ) has free space $( $disk.FreeSpace )"
}
}
function Download-File
{
param (
$URL,
$Dest,
$retryCount = 5,
$retryDelay = 0,
[Switch]$redactUrl = $false
)
curl.exe -f --retry $retryCount --retry-delay $retryDelay -L $URL -o $Dest
$curlExitCode = $LASTEXITCODE
if ($curlExitCode)
{
$logURL = $URL
if ($redactUrl)
{
$logURL = $logURL.Split("?")[0]
}
Log-VHDFreeSize
curl.exe --version
if ("$curlExitCode" -eq "23") {
throw "Curl exited with '$curlExitCode' while attempting to download '$logURL' to '$Dest'. This often means VHD out of space."
}
throw "Curl exited with '$curlExitCode' while attempting to download '$logURL' to '$Dest'"
}
dir "$Dest"
}
function Download-FileWithAzCopy
{
param (
$URL,
$Dest
)
if (!(Test-Path -Path $global:aksTempDir))
{
Write-Log "Creating temp dir for tools of building vhd"
New-Item -ItemType Directory $global:aksTempDir -Force
}
if (!(Test-Path -Path "$global:aksTempDir\azcopy.exe"))
{
Write-Log "Downloading azcopy"
Invoke-WebRequest -UseBasicParsing "https://aka.ms/downloadazcopy-v10-windows" -OutFile "$global:aksTempDir\azcopy.zip"
Expand-Archive -Path "$global:aksTempDir\azcopy.zip" -DestinationPath "$global:aksTempDir\tmp" -Force
Move-Item "$global:aksTempDir\tmp\*\azcopy.exe" "$global:aksTempDir\azcopy.exe"
}
pushd "$global:aksTempDir"
$env:AZCOPY_JOB_PLAN_LOCATION = "$global:aksTempDir\azcopy"
$env:AZCOPY_LOG_LOCATION = "$global:aksTempDir\azcopy"
mkdir -Force $env:AZCOPY_LOG_LOCATION
if (Test-Path -Path "$env:AZCOPY_LOG_LOCATION\*.log")
{
rm -Force "$env:AZCOPY_LOG_LOCATION\*.log"
}
Write-Log "Logging in to AzCopy"
# user_assigned_managed_identities has been bound in vhdbuilder/packer/windows/windows-vhd-builder-sig.json
.\azcopy.exe login --login-type=MSI
Write-Log "Copying $URL to $Dest"
.\azcopy.exe copy "$URL" "$Dest"
dir "$Dest"
Write-Log "--- START AzCopy Log"
Get-Content "$env:AZCOPY_LOG_LOCATION\*.log" | Write-Log
Write-Log "--- END AzCopy Log"
popd
}
function Cleanup-TemporaryFiles
{
if (Test-Path -Path $global:aksTempDir)
{
Remove-Item -Path $global:aksTempDir -Force -Recurse
}
}
function Retry-Command
{
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $true)]
[scriptblock]$ScriptBlock,
[Parameter(Position = 1, Mandatory = $true)]
[string]$ErrorMessage,
[Parameter(Position = 2, Mandatory = $false)]
[int]$Maximum = 5,
[Parameter(Position = 3, Mandatory = $false)]
[int]$Delay = 10
)
Begin {
$cnt = 0
}
Process {
do
{
$cnt++
try
{
$ScriptBlock.Invoke()
if ($LASTEXITCODE)
{
throw "Retry $cnt : $ErrorMessage"
}
return
}
catch
{
Write-Log $_.Exception.InnerException.Message
if ( $_.Exception.InnerException.Message.Contains("There is not enough space on the disk."))
{
Write-Error "Exit retry since there is not enough space on the disk"
break
}
if ( $_.Exception.InnerException.Message.Contains("The device is not connected.: unknown."))
{
Write-Error "Exit retry since drive disconnected (usually means that disk is out of space)"
break
}
Write-Log "Retry $cnt : $ScriptBlock"
Start-Sleep -Seconds $Delay
}
} while ($cnt -lt $Maximum)
# Throw an error after $Maximum unsuccessful invocations. Doesn't need
# a condition, since the function returns upon successful invocation.
throw 'All retries failed. $ErrorMessage'
}
}
function Invoke-Executable
{
Param(
[string]
$Executable,
[string[]]
$ArgList,
[int]
$Retries = 0,
[int]
$RetryDelaySeconds = 1
)
for ($i = 0; $i -le $Retries; $i++) {
Write-Log "$i - Running $Executable $ArgList ..."
& $Executable $ArgList
if ($LASTEXITCODE)
{
Write-Log "$Executable returned unsuccessfully with exit code $LASTEXITCODE"
Start-Sleep -Seconds $RetryDelaySeconds
continue
}
else
{
Write-Log "$Executable returned successfully"
return
}
}
Write-Log "Exhausted retries for $Executable $ArgList"
throw "Exhausted retries for $Executable $ArgList"
}
function Expand-OS-Partition
{
$customizedDiskSize = $env:CustomizedDiskSize
if ( [string]::IsNullOrEmpty($customizedDiskSize))
{
Write-Log "No need to expand the OS partition size"
return
}
Write-Log "Customized OS disk size is $customizedDiskSize GB"
[Int32]$osPartitionSize = 0
if ( [Int32]::TryParse($customizedDiskSize, [ref]$osPartitionSize))
{
# The supportedMaxSize less than the customizedDiskSize because some system usages will occupy disks (about 500M).
$supportedMaxSize = (Get-PartitionSupportedSize -DriveLetter C).sizeMax
$currentSize = (Get-Partition -DriveLetter C).Size
if ($supportedMaxSize -gt $currentSize)
{
Write-Log "Resizing the OS partition size from $currentSize to $supportedMaxSize"
Resize-Partition -DriveLetter C -Size $supportedMaxSize
Get-Disk
Get-Partition
}
else
{
Write-Log "The current size is the max size $currentSize"
}
}
else
{
Throw "$customizedDiskSize is not a valid customized OS disk size"
}
}
function Disable-WindowsUpdates
{
# See https://docs.microsoft.com/en-us/windows/deployment/update/waas-wu-settings
# for additional information on WU related registry settings
Write-Log "Disabling automatic windows upates"
$WindowsUpdatePath = "HKLM:SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
$AutoUpdatePath = "HKLM:SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
if (Test-Path -Path $WindowsUpdatePath)
{
Remove-Item -Path $WindowsUpdatePath -Recurse
}
New-Item -Path $WindowsUpdatePath | Out-Null
New-Item -Path $AutoUpdatePath | Out-Null
Set-ItemProperty -Path $AutoUpdatePath -Name NoAutoUpdate -Value 1 | Out-Null
}
function Get-ContainerImages
{
Write-Log "Pulling images for windows server $windowsSKU" # The variable $windowsSKU will be "2019-containerd", "2022-containerd", ...
foreach ($image in $imagesToPull)
{
Write-Output "* $image"
}
foreach ($image in $imagesToPull)
{
$imagePrefix = $image.Split(":")[0]
if (($imagePrefix -eq "mcr.microsoft.com/windows/servercore" -and ![string]::IsNullOrEmpty($env:WindowsServerCoreImageURL)) -or
($imagePrefix -eq "mcr.microsoft.com/windows/nanoserver" -and ![string]::IsNullOrEmpty($env:WindowsNanoServerImageURL)))
{
$url = ""
if ( $image.Contains("mcr.microsoft.com/windows/servercore"))
{
$url = $env:WindowsServerCoreImageURL
}
elseif ($image.Contains("mcr.microsoft.com/windows/nanoserver"))
{
$url = $env:WindowsNanoServerImageURL
}
$fileName = [IO.Path]::GetFileName($url.Split("?")[0])
$tmpDest = [IO.Path]::Combine([System.IO.Path]::GetTempPath(), $fileName)
Write-Log "Downloading image $image to $tmpDest"
Download-FileWithAzCopy -URL $url -Dest $tmpDest
Write-Log "Loading image $image from $tmpDest"
Retry-Command -ScriptBlock {
& ctr -n k8s.io images import $tmpDest
} -ErrorMessage "Failed to load image $image from $tmpDest"
Write-Log "Removing tmp tar file $tmpDest"
Remove-Item -Path $tmpDest
}
else
{
Write-Log "Pulling image $image"
Retry-Command -ScriptBlock {
& crictl.exe pull $image
} -ErrorMessage "Failed to pull image $image"
}
}
# before stopping containerd, let's echo the cached images and their sizes.
crictl images show
Stop-Job -Name containerd
Remove-Job -Name containerd
}
function Get-FilesToCacheOnVHD
{
Write-Log "Caching misc files on VHD"
foreach ($dir in $map.Keys)
{
New-Item -ItemType Directory $dir -Force | Out-Null
foreach ($URL in $map[$dir])
{
$fileName = [IO.Path]::GetFileName($URL)
$dest = [IO.Path]::Combine($dir, $fileName)
Write-Log "Downloading $URL to $dest"
Download-File -URL $URL -Dest $dest
}
}
foreach ($dir in $map.Keys)
{
LogFilesInDirectory "$dir"
}
}
function LogFilesInDirectory
{
Param(
[string]
$Directory
)
Get-ChildItem -Path "$Directory" | ForEach-Object {
$sizeKB = [math]::Round($_.Length / 1KB, 2)
Write-Output "$( $_.Name ) - $sizeKB KB"
}
}
function Get-ToolsToVHD
{
if (!(Test-Path -Path $global:aksToolsDir))
{
New-Item -ItemType Directory -Path $global:aksToolsDir -Force | Out-Null
}
Write-Log "Getting DU (Windows Disk Usage)"
Download-File -URL "https://download.sysinternals.com/files/DU.zip" -Dest "$global:aksToolsDir\DU.zip"
Expand-Archive -Path "$global:aksToolsDir\DU.zip" -DestinationPath "$global:aksToolsDir\DU" -Force
Remove-Item -Path "$global:aksToolsDir\DU.zip" -Force
LogFilesInDirectory "$global:aksToolsDir\DU"
}
function Register-ExpandVolumeTask
{
if (!(Test-Path -Path $global:aksToolsDir))
{
New-Item -ItemType Directory -Path $global:aksToolsDir -Force | Out-Null
}
# Leverage existing folder 'c:\aks-tools' to store the task scripts
$taskScript = @'
$osDrive = ((Get-WmiObject Win32_OperatingSystem -ErrorAction Stop).SystemDrive).TrimEnd(":")
$diskpartScriptPath = "c:\aks-tools\diskpart.script"
[String]::Format("select volume {0}`nextend`nexit", $osDrive) | Out-File -Encoding "UTF8" $diskpartScriptPath -Force
Start-Process -FilePath diskpart.exe -ArgumentList "/s $diskpartScriptPath" -Wait
# Run once and remove the task. Sequence: taks invokes ps1, ps1 invokes diskpart.
Unregister-ScheduledTask -TaskName "aks-expand-volume" -Confirm:$false
Remove-Item -Path "c:\aks-tools\expand-volume.ps1" -Force
Remove-Item -Path $diskpartScriptPath -Force
'@
$taskScriptPath = Join-Path $global:aksToolsDir "expand-volume.ps1"
$taskScript | Set-Content -Path $taskScriptPath -Force
# It sometimes failed with below error
# New-ScheduledTask : Cannot validate argument on parameter 'Action'. The argument is null or empty. Provide an argument
# that is not null or empty, and then try the command again.
# Add below logs and retry logic to test it
$scriptContent = Get-Content -Path $taskScriptPath
Write-Log "Task script content: $scriptContent"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File `"$taskScriptPath`""
if (-not $action)
{
Write-Log "action is null or empty. taskScriptPath: $taskScriptPath. Recreating it"
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File `"$taskScriptPath`""
if (-not $action)
{
Write-Log "action is still null"
exit 1
}
}
$principal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest
$trigger = New-JobTrigger -AtStartup
$definition = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger -Description "aks-expand-volume"
Register-ScheduledTask -TaskName "aks-expand-volume" -InputObject $definition
Write-Log "Registered ScheduledTask aks-expand-volume"
}
function Get-PrivatePackagesToCacheOnVHD
{
if (![string]::IsNullOrEmpty($env:WindowsPrivatePackagesURL))
{
Write-Log "Caching private packages on VHD"
$dir = "c:\akse-cache\private-packages"
New-Item -ItemType Directory $dir -Force | Out-Null
$mappingFile = "c:\akse-cache\private-packages\mapping.json"
$content = @{ }
$urls = $env:WindowsPrivatePackagesURL.Split(",")
foreach ($url in $urls)
{
$fileName = [IO.Path]::GetFileName($url.Split("?")[0])
$dest = [IO.Path]::Combine($dir, $fileName)
Write-Log "Downloading a private package to $dest"
Download-FileWithAzCopy -URL $URL -Dest $dest
# Example: v1.29.2-hotfix.2024101-1int.zip
$version = $fileName.Split('-')[0].SubString(1)
Write-Log "Adding $version to $mappingFile"
$content[$version] = $url
}
Write-Log "Writing mapping file to $mappingFile"
$content | ConvertTo-Json -Depth 10 | Out-File -FilePath $mappingFile
}
}
function Install-ContainerD
{
# installing containerd during VHD building is to cache container images into the VHD,
# and the containerd to managed customer containers after provisioning the vm is not necessary
# the one used here, considering containerd version/package is configurable, and the first one
# is expected to override the later one
Write-Log "Getting containerD binaries from $global:defaultContainerdPackageUrl"
$installDir = "c:\program files\containerd"
Write-Log "Installing containerd to $installDir"
New-Item -ItemType Directory $installDir -Force | Out-Null
$containerdFilename = [IO.Path]::GetFileName($global:defaultContainerdPackageUrl)
$containerdTmpDest = [IO.Path]::Combine($installDir, $containerdFilename)
Download-File -URL $global:defaultContainerdPackageUrl -Dest $containerdTmpDest
# The released containerd package format is either zip or tar.gz
if ( $containerdFilename.endswith(".zip"))
{
Expand-Archive -path $containerdTmpDest -DestinationPath $installDir -Force
}
else
{
tar -xzf $containerdTmpDest -C $installDir
if ($LASTEXITCODE -ne 0) {
throw "Failed to extract the '$containerdTmpDest' archive."
}
mv -Force $installDir\bin\* $installDir
Remove-Item -Path $installDir\bin -Force -Recurse
}
Remove-Item -Path $containerdTmpDest | Out-Null
$newPaths = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine) + ";$installDir"
[Environment]::SetEnvironmentVariable("Path", $newPaths, [EnvironmentVariableTarget]::Machine)
$env:Path += ";$installDir"
$containerdConfigPath = [Io.Path]::Combine($installDir, "config.toml")
# enabling discard_unpacked_layers allows GC to remove layers from the content store after
# successfully unpacking these layers to the snapshotter to reduce the disk space caching Windows containerd images
(containerd config default) | %{ $_ -replace "discard_unpacked_layers = false", "discard_unpacked_layers = true" } | Out-File -FilePath $containerdConfigPath -Encoding ascii
Get-Content $containerdConfigPath
# start containerd to pre-pull the images to disk on VHD
# CSE will configure and register containerd as a service at deployment time
Start-Job -Name containerd -ScriptBlock { containerd.exe }
}
function Install-OpenSSH
{
if ($env:INSTALL_OPEN_SSH_SERVER -eq 'False')
{
Write-Log "Not installing Windows OpenSSH Server as this is disabled in the pipeline"
return
}
Write-Log "Installing OpenSSH Server"
# Somehow openssh client got added to Windows 2019 base image.
if ($env:WindowsSKU -Like '2019*')
{
Remove-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
}
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# It’s by design that files within the C:\Windows\System32\ folder are not modifiable.
# When the OpenSSH Server starts, it copies C:\windows\system32\openssh\sshd_config_default to C:\programdata\ssh\sshd_config, if the file does not already exist.
$OriginalConfigPath = "C:\windows\system32\OpenSSH\sshd_config_default"
$ConfigDirectory = "C:\programdata\ssh"
New-Item -ItemType Directory -Force -Path $ConfigDirectory
$ConfigPath = $ConfigDirectory + "\sshd_config"
Write-Log "Updating $ConfigPath for CVE-2023-48795"
$ModifiedConfigContents = Get-Content $OriginalConfigPath `
| %{ $_ -replace "#RekeyLimit default none", "$&`r`n# Disable cipher to mitigate CVE-2023-48795`r`nCiphers -chacha20-poly1305@openssh.com`r`nMacs -*-etm@openssh.com`r`n" }
Write-Log "Updating $ConfigPath for CVE-2006-5051"
$ModifiedConfigContents = $ModifiedConfigContents.Replace("#LoginGraceTime 2m", "LoginGraceTime 0")
Stop-Service sshd
Out-File -FilePath $ConfigPath -InputObject $ModifiedConfigContents -Encoding UTF8
Start-Service sshd
Write-Log "Updated $ConfigPath for CVEs"
}
function Install-WindowsPatches
{
Write-Log "Installing Windows patches"
Write-Log "The length of patchUrls is $( $patchUrls.Length )"
foreach ($patchUrl in $patchUrls)
{
$pathOnly = $patchUrl.Split("?")[0]
$fileName = Split-Path $pathOnly -Leaf
$fileExtension = [IO.Path]::GetExtension($fileName)
$fullPath = [IO.Path]::Combine($env:TEMP, $fileName)
switch ($fileExtension)
{
".msu" {
Write-Log "Downloading windows patch from $pathOnly to $fullPath"
Download-File -URL $patchUrl -Dest $fullPath -redactUrl
Write-Log "Starting install of $fileName"
$proc = Start-Process -Passthru -FilePath wusa.exe -ArgumentList "$fullPath /quiet /norestart"
Wait-Process -InputObject $proc
switch ($proc.ExitCode)
{
0 {
Write-Log "Finished install of $fileName"
}
3010 {
WRite-Log "Finished install of $fileName. Reboot required"
}
2359302 {
# https://learn.microsoft.com/en-gb/windows/win32/wua_sdk/wua-success-and-error-codes-?redirectedfrom=MSDN
# this number is 0x00240006 and means already installed
Write-Log "The update was already installed. Ignoring $fileName"
}
default {
Write-Log "Error during install of $fileName. ExitCode: $( $proc.ExitCode )"
throw "Error during install of $fileName. ExitCode: $( $proc.ExitCode )"
}
}
}
default {
Write-Log "Installing patches with extension $fileExtension is not currently supported."
throw "Installing patches with extension $fileExtension is not currently supported."
}
}
}
}
function Set-WinRmServiceAutoStart
{
Write-Log "Setting WinRM service start to auto"
sc.exe config winrm start=auto
}
function Set-WinRmServiceDelayedStart
{
# Hyper-V messes with networking components on startup after the feature is enabled
# causing issues with communication over winrm and setting winrm to delayed start
# gives Hyper-V enough time to finish configuration before having packer continue.
Write-Log "Setting WinRM service start to delayed-auto"
sc.exe config winrm start=delayed-auto
}
# Best effort to update defender signatures
# This can fail if there is already a signature
# update running which means we will get them anyways
# Also at the time the VM is provisioned Defender will trigger any required updates
function Update-DefenderSignatures
{
Write-Log "Updating windows defender signatures."
$service = Get-Service "Windefend"
$service.WaitForStatus("Running", "00:5:00")
Update-MpSignature
}
function Update-WindowsFeatures
{
$featuresToEnable = @(
"Containers",
"Hyper-V",
"Hyper-V-PowerShell")
foreach ($feature in $featuresToEnable)
{
Write-Log "Enabling Windows feature: $feature"
Install-WindowsFeature $feature
}
}
function Enable-WindowsFixInPath
{
Param(
[Parameter(Mandatory = $true)][string]
$Path,
[Parameter(Mandatory = $true)][string]
$Name,
[Parameter(Mandatory = $false)][string]
$Value = "1",
[Parameter(Mandatory = $false)][string]
$Type = "DWORD"
)
$regPath = (Get-Item -Path $Path -ErrorAction Ignore)
if (!$regPath)
{
Write-Log "Creating $Path"
# assigning to a variable stops logs of logging of successful creations.
$newRegDir = New-Item -Force -Path $Path
}
$currentValue = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction Ignore)
if (![string]::IsNullOrEmpty($currentValue))
{
Write-Log "The current value of $Name in $Path is $currentValue"
}
Set-ItemProperty -Path $Path -Name $Name $Value -Type $Type
}
function Update-Registry
{
foreach ($key in $global:keysToSet)
{
$keyPath = $key.Path
$keyName = $key.Name
$keyValue = $key.Value
$keyType = $key.Type
$keyComment = $key.Comment
$keyOperation = $key.Operation
Write-Log "$keyPath\$keyName = $keyValue : $keyComment"
if ($keyOperation -eq "bor")
{
$currentValue = (Get-ItemProperty -Path $keyPath -Name $keyName -ErrorAction Ignore)
if (![string]::IsNullOrEmpty($currentValue))
{
Write-Log "The current value of $keyName is $currentValue"
$keyValue = ([int]$currentValue.$keyName -bor $keyValue)
}
Enable-WindowsFixInPath -Path $keyPath -Name $keyName -Value $keyValue -Type $keyType
}
else
{
Enable-WindowsFixInPath -Path $keyPath -Name $keyName -Value $keyValue -Type $keyType
}
}
}
function Clear-TempFolder
{
$tempFolders = @()
$tempFolders += [System.Environment]::GetFolderPath('LocalApplicationData') + '\Temp'
$tempFolders += [System.Environment]::GetFolderPath('InternetCache')
$tempFolders += [System.Environment]::GetFolderPath('Windows') + '\Temp'
# Iterate over each temporary folder
foreach ($folder in $tempFolders)
{
# Check if the folder exists
if (-not (Test-Path -Path $folder -PathType Container))
{
Write-Host "The folder '$folder' does not exist."
continue
}
# Get all files in the temporary folder
$tempFiles = Get-ChildItem -Path $folder -File -Force
# Delete each file in the temporary folder
foreach ($file in $tempFiles)
{
# skip file if the file name contains "packer"
if ($file.Name -like "*packer*")
{
continue
}
try
{
Remove-Item -Path $file.FullName -Force
}
catch
{
Write-Host "Failed to remove file: $( $file.FullName )"
continue
}
}
# Confirm completion for each folder
Write-Host "Temporary files in '$folder' cleaned up successfully."
}
# Give the system some time to release the file handles
Start-Sleep -Seconds 1
}
function Get-SystemDriveDiskInfo
{
Clear-TempFolder
Write-Log "Get Disk info"
$disksInfo = Get-CimInstance -ClassName Win32_LogicalDisk
foreach ($disk in $disksInfo)
{
if ($disk.DeviceID -eq "C:")
{
Write-Log "Disk C: Free space: $( $disk.FreeSpace ), Total size: $( $disk.Size )"
}
}
}
function Get-DefenderPreferenceInfo
{
Write-Log "Get preferences for the Windows Defender scans and updates"
Write-Log(Get-MpPreference | Format-List | Out-String)
}
function Exclude-ReservedUDPSourcePort()
{
# https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#what-protocols-can-i-use-within-vnets
# Default UDP Dynamic Port Range in Windows server: Start Port: 49152, Number of Ports : 16384. Range: [49152, 65535]
# Exclude UDP source port 65330. This only excludes the port in AKS Windows nodes but will not impact Windows containers.
# Reference: https://github.com/Azure/AKS/issues/2988
# List command: netsh int ipv4 show excludedportrange udp
Invoke-Executable -Executable "netsh.exe" -ArgList @("int", "ipv4", "add", "excludedportrange", "udp", "65330", "1", "persistent")
}
function Get-LatestWindowsDefenderPlatformUpdate
{
$downloadFilePath = [IO.Path]::Combine([System.IO.Path]::GetTempPath(), "Mpupdate.exe")
$currentDefenderProductVersion = (Get-MpComputerStatus).AMProductVersion
$doc = New-Object xml
$doc.Load("$global:defenderUpdateInfoUrl")
$latestDefenderProductVersion = $doc.versions.platform
if ($latestDefenderProductVersion -gt $currentDefenderProductVersion)
{
Write-Log "Update started. Current MPVersion: $currentDefenderProductVersion, Expected Version: $latestDefenderProductVersion"
Download-File -URL $global:defenderUpdateUrl -Dest $downloadFilePath
$proc = Start-Process -PassThru -FilePath $downloadFilePath -Wait
Start-Sleep -Seconds 10
switch ($proc.ExitCode)
{
0 {
Write-Log "Finished update of $downloadFilePath"
}
default {
Write-Log "Error during update of $downloadFilePath. ExitCode: $( $proc.ExitCode )"
throw "Error during update of $downloadFilePath. ExitCode: $( $proc.ExitCode )"
}
}
$currentDefenderProductVersion = (Get-MpComputerStatus).AMProductVersion
if ($latestDefenderProductVersion -gt $currentDefenderProductVersion)
{
throw "Update failed. Current MPVersion: $currentDefenderProductVersion, Expected Version: $latestDefenderProductVersion"
}
else
{
Write-Log "Update succeeded. Current MPVersion: $currentDefenderProductVersion, Expected Version: $latestDefenderProductVersion"
}
}
else
{
Write-Log "Update not required. Current MPVersion: $currentDefenderProductVersion, Expected Version: $latestDefenderProductVersion"
}
}
function Log-ReofferUpdate
{
try
{
$result = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Update\TargetingInfo\Installed\Server.OS.amd64" -Name ReofferUpdate)
if ($result)
{
Write-Log "ReofferUpdate is $( $result.ReofferUpdate )"
}
}
catch
{
Write-Log "ReofferUpdate registry setting does not exist"
}
}
function Test-AzureExtensions
{
if ($env:SKIP_EXTENSION_CHECK -eq "True")
{
Write-Log "Skipping extension check because SKIP_EXTENSION_CHECK is set to True"
return
}
# Expect the Windows VHD without any other extensions
if (Test-Path "C:\Packages\Plugins")
{
$actualExtensions = (Get-ChildItem "C:\Packages\Plugins").Name
if ($actualExtensions.Length -gt 0)
{
Write-Log "Azure extensions are not expected and skip extension checks was $env:SKIP_EXTENSION_CHECK. Details:"
foreach ($extension in $actualExtensions)
{
Write-Log "* $extension"
}
exit 1
}
}
Write-Log "Azure extensions are not found"
}
# Disable progress writers for this session to greatly speed up operations such as Invoke-WebRequest
$ProgressPreference = 'SilentlyContinue'
try
{
switch ($env:ProvisioningPhase)
{
"1" {
Write-Log "Performing actions for provisioning phase 1"
Expand-OS-Partition
Exclude-ReservedUDPSourcePort
Get-LatestWindowsDefenderPlatformUpdate
Disable-WindowsUpdates
Set-WinRmServiceDelayedStart
Update-DefenderSignatures
Log-ReofferUpdate
Install-OpenSSH
Log-ReofferUpdate
Install-WindowsPatches
Update-WindowsFeatures
}
"2" {
Write-Log "Performing actions for provisioning phase 2"
Log-ReofferUpdate
Set-WinRmServiceAutoStart
Install-ContainerD
Update-Registry
Get-ContainerImages
Get-FilesToCacheOnVHD
Get-ToolsToVHD
Get-PrivatePackagesToCacheOnVHD
Log-ReofferUpdate
}
"3" {
Register-ExpandVolumeTask
Cleanup-TemporaryFiles
(New-Guid).Guid | Out-File -FilePath 'c:\vhd-id.txt'
Clear-TempFolder
Log-VHDFreeSize
Test-AzureExtensions
Write-Output "creating test.txt file"
Write-Output "this is a test file" > "c:\k\test.txt"
}
default {
Write-Log "Unable to determine provisiong phase... exiting"
throw "Unable to determine provisiong phase... exiting"
}
}
}
finally
{
Get-SystemDriveDiskInfo
Get-DefenderPreferenceInfo
}