From e904ca26a10abc94086d2739bf756b6ecaff7357 Mon Sep 17 00:00:00 2001 From: Tu Dinh Date: Fri, 6 Dec 2024 11:46:17 +0100 Subject: [PATCH] scripts: Add Windows guest preparation, cleanup and cloning scripts Signed-off-by: Tu Dinh --- .gitignore | 1 + scripts/guests/windows/install-autotest.ps1 | 78 +++++++++++++++++++++ scripts/guests/windows/install-drivers.ps1 | 52 ++++++++++++++ scripts/guests/windows/netreport.ps1 | 21 ++++++ scripts/guests/windows/unattend.xml | 29 ++++++++ scripts/guests/windows/win-diskclone.sh | 46 ++++++++++++ 6 files changed, 227 insertions(+) create mode 100644 scripts/guests/windows/install-autotest.ps1 create mode 100644 scripts/guests/windows/install-drivers.ps1 create mode 100644 scripts/guests/windows/netreport.ps1 create mode 100644 scripts/guests/windows/unattend.xml create mode 100755 scripts/guests/windows/win-diskclone.sh diff --git a/.gitignore b/.gitignore index 8cd7e13a0..8aa60aada 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__ */__pycache__ data.py vm_data.py +/scripts/guests/windows/id_rsa.pub diff --git a/scripts/guests/windows/install-autotest.ps1 b/scripts/guests/windows/install-autotest.ps1 new file mode 100644 index 000000000..bd623d461 --- /dev/null +++ b/scripts/guests/windows/install-autotest.ps1 @@ -0,0 +1,78 @@ +#Requires -RunAsAdministrator + +[CmdletBinding()] +param ( + [Parameter()] + [switch]$NoNetReporting, + [Parameter()] + [switch]$Cleanup +) + +$ErrorActionPreference = "Stop" + +if (!(Test-Path "$PSScriptRoot\id_rsa.pub")) { + throw "Cannot find id_rsa.pub for SSH configuration" +} + +# Sometimes enabling updates will disrupt installation and rebooting. +# This is a temporary measure at most, but Microsoft makes disabling updates really difficult... +Write-Output "Disabling updates" +Set-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name AUOptions -Type DWord -Value 2 -Force +Stop-Service wuauserv +Set-Service wuauserv -StartupType Disabled + +Write-Output "Installing SSH" +$SSHDownloadPath = "$env:TEMP\OpenSSH-Win64-v9.8.1.0.msi" +Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/PowerShell/Win32-OpenSSH/releases/download/v9.8.1.0p1-Preview/OpenSSH-Win64-v9.8.1.0.msi" -OutFile $SSHDownloadPath +$exitCode = (Start-Process -Wait msiexec.exe -ArgumentList "/i", $SSHDownloadPath, "/passive", "/norestart" -PassThru).ExitCode +if ($exitCode -ne 0) { + throw +} +Remove-Item -Force $SSHDownloadPath -ErrorAction SilentlyContinue +Copy-Item "$PSScriptRoot\id_rsa.pub" "$env:ProgramData\ssh\administrators_authorized_keys" -Force +icacls.exe "$env:ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" +if ($LASTEXITCODE -ne 0) { + throw +} +New-NetFirewallRule -Action Allow -Program "$env:ProgramFiles\OpenSSH\sshd.exe" -Direction Inbound -Protocol TCP -LocalPort 22 -DisplayName sshd + +Write-Output "Installing Git Bash" +$GitDownloadPath = "$env:TEMP\Git-2.47.1-64-bit.exe" +Invoke-WebRequest -UseBasicParsing -Uri "https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.1/Git-2.47.1-64-bit.exe" -OutFile $GitDownloadPath +$exitCode = (Start-Process -Wait $GitDownloadPath -ArgumentList "/silent" -PassThru).ExitCode +if ($exitCode -ne 0) { + throw +} +Remove-Item -Force $GitDownloadPath -ErrorAction SilentlyContinue +Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Type String -Value "$env:ProgramFiles\Git\bin\bash.exe" -Force + +if (!$NoNetReporting) { + Write-Output "Installing network reporting script" + Copy-Item "$PSScriptRoot\netreport.ps1" "$env:SystemDrive\" -Force + $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-executionpolicy bypass $env:SystemDrive\netreport.ps1" + $trigger = New-ScheduledTaskTrigger -AtStartup + $principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -RunLevel Highest + $task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal + Register-ScheduledTask -InputObject $task -TaskName "XCP-ng Test Network Report" +} + +if ($Cleanup) { + Read-Host -Prompt "Unplug Internet, run Disk Cleanup and continue" + + Write-Output "Cleaning up component store" + dism.exe /Online /Cleanup-Image /StartComponentCleanup /ResetBase + + Write-Output "Cleaning up SoftwareDistribution" + Stop-Service wuauserv, BITS -ErrorAction SilentlyContinue + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue "$env:windir\SoftwareDistribution\Download\*" + + Write-Output "Cleaning up Defender signatures" + & "$env:ProgramFiles\Windows Defender\MpCmdRun.exe" -RemoveDefinitions -All + + Write-Output "Optimizing system drive" + defrag.exe $env:SystemDrive /O +} + +Write-Output "Resealing" +Stop-Process -Name sysprep -ErrorAction SilentlyContinue +& "$env:windir\System32\Sysprep\sysprep.exe" /generalize /oobe /shutdown /unattend:$PSScriptRoot\unattend.xml diff --git a/scripts/guests/windows/install-drivers.ps1 b/scripts/guests/windows/install-drivers.ps1 new file mode 100644 index 000000000..dc2974450 --- /dev/null +++ b/scripts/guests/windows/install-drivers.ps1 @@ -0,0 +1,52 @@ +[CmdletBinding()] +param ( + [Parameter(Mandatory, ParameterSetName = "Drivers")] + [string]$DriverPath, + [Parameter(Mandatory, ParameterSetName = "Msi")] + [string]$MsiPath, + [Parameter()] + [switch]$Shutdown +) + +$ErrorActionPreference = "Stop" + +$signature = @' +[DllImport("Cfgmgr32.dll")] +public static extern uint CMP_WaitNoPendingInstallEvents(uint dwTimeout); +public const uint INFINITE = 0xFFFFFFFF; +public const uint WAIT_OBJECT_0 = 0; +public const uint WAIT_TIMEOUT = 258; +public const uint WAIT_FAILED = 0xFFFFFFFF; +'@ + +$nativeMethods = Add-Type -MemberDefinition $signature -Name NativeMethods -Namespace XenTools -PassThru + +if ($DriverPath) { + foreach ($driver in @("xenbus", "xeniface", "xenvbd", "xenvif", "xennet")) { + $infPath = (Resolve-Path "$DriverPath\$driver\x64\$driver.inf").Path + Write-Output "Attempting install $infPath" + pnputil.exe /add-driver $infPath /install + if ($LASTEXITCODE -ne 0) { + throw "pnputil.exe $LASTEXITCODE" + } + } +} +elseif ($MsiPath) { + $resolvedMsiPath = (Resolve-Path $MsiPath).Path + Write-Output "Attempting install $resolvedMsiPath" + $msiexecProcess = Start-Process -Wait -PassThru msiexec.exe -ArgumentList "/i", "$resolvedMsiPath", "/l*", "C:\other-install.log", "/passive", "/norestart" + if ($msiexecProcess.ExitCode -ne 0 -and $msiexecProcess.ExitCode -ne 1641 -and $msiexecProcess.ExitCode -ne 3010) { + throw "msiexec.exe $($msiexecProcess.ExitCode)" + } + # Some installers like XCP-ng 8.2 don't install their drivers using MSI but through their own service (XenInstall). + # Leave some time for the installation service to do its thing. + Start-Sleep -Seconds 15 +} +Write-Output "Waiting for install events" +$nativeMethods::CMP_WaitNoPendingInstallEvents($nativeMethods::INFINITE) + +if ($Shutdown) { + Write-Output "Shutting down" + Start-Sleep -Seconds 5 + Stop-Computer -Force +} diff --git a/scripts/guests/windows/netreport.ps1 b/scripts/guests/windows/netreport.ps1 new file mode 100644 index 000000000..1be116824 --- /dev/null +++ b/scripts/guests/windows/netreport.ps1 @@ -0,0 +1,21 @@ +do { + Start-Sleep -Seconds 2 + $Adapter = Get-NetAdapter -Physical | Where-Object Status -eq Up | Select-Object -First 1 +} while (!$Adapter) + +$Address = Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $Adapter.InterfaceIndex +# write the full `r`n sequence so that grep could catch it as a full line +$ReportString = "~xcp-ng-tests~$($Adapter.MacAddress)=$($Address.IPv4Address)~end~`r`n" + +$Port = [System.IO.Ports.SerialPort]::new("COM1") +try { + $Port.Open() + for ($i = 0; $i -lt 300; $i++) { + $Port.Write($ReportString) + Start-Sleep -Seconds 1 + } + $Port.Close() +} +finally { + $Port.Dispose() +} diff --git a/scripts/guests/windows/unattend.xml b/scripts/guests/windows/unattend.xml new file mode 100644 index 000000000..d93801243 --- /dev/null +++ b/scripts/guests/windows/unattend.xml @@ -0,0 +1,29 @@ + + + + + 0409:00000409 + en-US + en-US + en-US + + + + + + Administrators + root + + + + + true + true + true + true + true + 3 + + + + diff --git a/scripts/guests/windows/win-diskclone.sh b/scripts/guests/windows/win-diskclone.sh new file mode 100755 index 000000000..dd7616da0 --- /dev/null +++ b/scripts/guests/windows/win-diskclone.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -eu + +src=$1 +dst=$2 + +if [ "$(blockdev --getsz $src)" != "$(blockdev --getsz $dst)" ] +then + echo "Disks are not the same size!" + exit 1 +fi + +if (sfdisk -d $dst > /dev/null) +then + echo "Destination contains partition table! Stopping" + exit 1 +fi + +echo "Cloning partition table" +sfdisk -d $src | sfdisk $dst + +echo "Cloning non-data partitions" +for srcpart in $(sfdisk -d $src | + grep start= | + grep -v type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 | + cut -d ':' -f 1) +do + dstpart=${srcpart/"$src"/"$dst"} + echo "Cloning $srcpart to $dstpart" + pv $srcpart > $dstpart +done + +echo "Cloning data partitions" +for srcpart in $(sfdisk -d $src | + grep type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 | + cut -d ':' -f 1) +do + dstpart=${srcpart/"$src"/"$dst"} + echo "Deleting pagefiles" + mkdir -p /mnt/$srcpart && + mount $srcpart /mnt/$srcpart && + find /mnt/$srcpart -maxdepth 1 -iname pagefile.sys -or -iname hiberfil.sys -or -iname swapfile.sys -delete + umount /mnt/$srcpart + echo "Cloning $srcpart to $dstpart" + ntfsclone -O $dstpart $srcpart +done