From: Ionut Balutoiu Date: Fri, 29 Apr 2022 10:08:43 +0000 (+0300) Subject: [ceph-windows-image-build] Fix Windows image build X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=94293330b7eafee85901e0b5668f73251bd88c81;p=ceph-build.git [ceph-windows-image-build] Fix Windows image build The `${VIRTIO_WIN_PATH}\guest-agent\qemu-ga-x86_64.msi` unattended installation fails with latest virtio. Refactor the setup scripts to only install the required drivers (`NetKVM`, `viostor`, `vioserial`), and use a single script (`first-logon.ps1`) for provisioning the Windows OS. Also, add a separate script `utils.ps1` with common PowerShell functions. --- diff --git a/ceph-windows-image-build/build/autounattend.xml b/ceph-windows-image-build/build/autounattend.xml index 31cedeb75..93b075256 100644 --- a/ceph-windows-image-build/build/autounattend.xml +++ b/ceph-windows-image-build/build/autounattend.xml @@ -84,9 +84,12 @@ E:\NetKVM\2k19\amd64\ - + E:\viostor\2k19\amd64\ + + E:\vioserial\2k19\amd64\ + @@ -132,15 +135,10 @@ - %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell -NoLogo -NonInteractive -ExecutionPolicy RemoteSigned -File A:\install-virtio-guest-tools.ps1 + %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NonInteractive -ExecutionPolicy RemoteSigned -File A:\first-logon.ps1 1 - - %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell -NoLogo -NonInteractive -ExecutionPolicy RemoteSigned -File A:\install-openssh-server.ps1 - 2 - - diff --git a/ceph-windows-image-build/build/build b/ceph-windows-image-build/build/build index d33e7bdcb..6f17c5102 100755 --- a/ceph-windows-image-build/build/build +++ b/ceph-windows-image-build/build/build @@ -74,8 +74,8 @@ sudo mount ${BUILD_DIR}/floppy.img ${BUILD_DIR}/floppy ssh-keygen -y -f $SSH_PRIVATE_KEY > ${BUILD_DIR}/id_rsa.pub sudo cp \ ${BUILD_DIR}/autounattend.xml \ - ${BUILD_DIR}/install-virtio-guest-tools.ps1 \ - ${BUILD_DIR}/install-openssh-server.ps1 \ + ${BUILD_DIR}/first-logon.ps1 \ + ${BUILD_DIR}/utils.ps1 \ ${BUILD_DIR}/id_rsa.pub \ ${BUILD_DIR}/floppy/ sudo umount ${BUILD_DIR}/floppy @@ -139,9 +139,10 @@ ssh_exec powershell.exe Remove-Item -Force /install-windows-updates.ps1 restart_windows_vm 1800 +scp_upload ${BUILD_DIR}/utils.ps1 /utils.ps1 scp_upload ${BUILD_DIR}/setup.ps1 /setup.ps1 SSH_TIMEOUT=1h ssh_exec powershell.exe -File /setup.ps1 -ssh_exec powershell.exe Remove-Item -Force /setup.ps1 +ssh_exec powershell.exe Remove-Item -Force /setup.ps1, /utils.ps1 restart_windows_vm diff --git a/ceph-windows-image-build/build/first-logon.ps1 b/ceph-windows-image-build/build/first-logon.ps1 new file mode 100644 index 000000000..7360d714a --- /dev/null +++ b/ceph-windows-image-build/build/first-logon.ps1 @@ -0,0 +1,38 @@ +$ErrorActionPreference = "Stop" + +. "${PSScriptRoot}\utils.ps1" + +$VIRTIO_WIN_PATH = "E:\" + +# Install QEMU quest agent +Write-Output "Installing QEMU guest agent" +$p = Start-Process -FilePath "msiexec.exe" -ArgumentList @("/i", "${VIRTIO_WIN_PATH}\guest-agent\qemu-ga-x86_64.msi", "/qn") -NoNewWindow -PassThru -Wait +if($p.ExitCode) { + Throw "The QEMU guest agent installation failed. Exit code: $($p.ExitCode)" +} +Write-Output "Successfully installed QEMU guest agent" + +# Install OpenSSH server +Start-ExecuteWithRetry { + Get-WindowsCapability -Online -Name OpenSSH* | Add-WindowsCapability -Online +} +Set-Service -Name "sshd" -StartupType Automatic +Stop-Service -Name "sshd" + +# Create SSH firewall rule +New-NetFirewallRule -Name "sshd" -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 + +# Authorize the SSH key +$authorizedKeysFile = Join-Path $env:ProgramData "ssh\administrators_authorized_keys" +New-Item -ItemType File -Path $authorizedKeysFile -Force +Set-Content -Path $authorizedKeysFile -Value (Get-Content "${PSScriptRoot}\id_rsa.pub") -Encoding ascii +$acl = Get-Acl $authorizedKeysFile +$acl.SetAccessRuleProtection($true, $false) +$administratorsRule = New-Object system.security.accesscontrol.filesystemaccessrule("Administrators", "FullControl", "Allow") +$systemRule = New-Object system.security.accesscontrol.filesystemaccessrule("SYSTEM", "FullControl", "Allow") +$acl.SetAccessRule($administratorsRule) +$acl.SetAccessRule($systemRule) +$acl | Set-Acl + +# Reboot the machine to complete first logon process +Restart-Computer -Force -Confirm:$false diff --git a/ceph-windows-image-build/build/install-openssh-server.ps1 b/ceph-windows-image-build/build/install-openssh-server.ps1 deleted file mode 100644 index aa96ea0d5..000000000 --- a/ceph-windows-image-build/build/install-openssh-server.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -$ErrorActionPreference = "Stop" - -Get-WindowsCapability -Online -Name OpenSSH* | Add-WindowsCapability -Online - -Set-Service -Name "sshd" -StartupType Automatic -Start-Service -Name "sshd" - -New-NetFirewallRule -Name "sshd" -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 - -# Authorize the SSH key -$authorizedKeysFile = Join-Path $env:ProgramData "ssh\administrators_authorized_keys" -Set-Content -Path $authorizedKeysFile -Value (Get-Content "${PSScriptRoot}\id_rsa.pub") -Encoding ascii -$acl = Get-Acl $authorizedKeysFile -$acl.SetAccessRuleProtection($true, $false) -$administratorsRule = New-Object system.security.accesscontrol.filesystemaccessrule("Administrators", "FullControl", "Allow") -$systemRule = New-Object system.security.accesscontrol.filesystemaccessrule("SYSTEM", "FullControl", "Allow") -$acl.SetAccessRule($administratorsRule) -$acl.SetAccessRule($systemRule) -$acl | Set-Acl diff --git a/ceph-windows-image-build/build/install-virtio-guest-tools.ps1 b/ceph-windows-image-build/build/install-virtio-guest-tools.ps1 deleted file mode 100644 index a8d437900..000000000 --- a/ceph-windows-image-build/build/install-virtio-guest-tools.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -$ErrorActionPreference = "Stop" - -$VIRTIO_WIN_PATH = "E:\" - -Write-Output "Installing virtio-win guest tools" - -# Trust driver certs -$certStore = Get-Item "cert:\LocalMachine\TrustedPublisher" -$certStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) -$driverPath = Get-Item "${VIRTIO_WIN_PATH}\*\2k19\amd64" -Get-ChildItem -Recurse -Path $driverPath -Filter "*.cat" | ForEach-Object { - $cert = (Get-AuthenticodeSignature $_.FullName).SignerCertificate - $certStore.Add($cert) -} -$certStore.Close() - -# Install QEMU quest tools -$p = Start-Process -FilePath "${VIRTIO_WIN_PATH}\virtio-win-guest-tools.exe" -ArgumentList @("/install", "/quiet", "/norestart") -NoNewWindow -PassThru -Wait -if($p.ExitCode) { - Throw "The virtio-win guest tools installation failed. Exit code: $($p.ExitCode)" -} - -Write-Output "Successfully installed virtio-win guest tools" diff --git a/ceph-windows-image-build/build/setup.ps1 b/ceph-windows-image-build/build/setup.ps1 index 75414fc2b..08ae67366 100644 --- a/ceph-windows-image-build/build/setup.ps1 +++ b/ceph-windows-image-build/build/setup.ps1 @@ -1,6 +1,8 @@ $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" +. "${PSScriptRoot}\utils.ps1" + $VS_2019_BUILD_TOOLS_URL = "https://aka.ms/vs/16/release/vs_buildtools.exe" $WDK_URL = "https://download.microsoft.com/download/7/d/6/7d602355-8ae9-414c-ae36-109ece2aade6/wdk/wdksetup.exe" # Windows 11 WDK (22000.1). It can be used to develop drivers for previous OS releases. $PYTHON3_URL = "https://www.python.org/ftp/python/3.10.1/python-3.10.1-amd64.exe" @@ -17,126 +19,6 @@ function Get-WindowsBuildInfo { return $table } -function Invoke-CommandLine { - Param( - [Parameter(Mandatory=$true)] - [String]$Command, - [String]$Arguments, - [Int[]]$AllowedExitCodes=@(0) - ) - & $Command $Arguments.Split(" ") - if($LASTEXITCODE -notin $AllowedExitCodes) { - Throw "$Command $Arguments returned a non zero exit code ${LASTEXITCODE}." - } -} - -function Start-ExecuteWithRetry { - Param( - [Parameter(Mandatory=$true)] - [ScriptBlock]$ScriptBlock, - [Int]$MaxRetryCount=10, - [Int]$RetryInterval=3, - [String]$RetryMessage, - [Array]$ArgumentList=@() - ) - $currentErrorActionPreference = $ErrorActionPreference - $ErrorActionPreference = "Continue" - $retryCount = 0 - while ($true) { - try { - $res = Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList - $ErrorActionPreference = $currentErrorActionPreference - return $res - } catch [System.Exception] { - $retryCount++ - if ($retryCount -gt $MaxRetryCount) { - $ErrorActionPreference = $currentErrorActionPreference - Throw $_ - } else { - $prefixMsg = "Retry(${retryCount}/${MaxRetryCount})" - if($RetryMessage) { - Write-Host "${prefixMsg} - $RetryMessage" - } elseif($_) { - Write-Host "${prefixMsg} - $($_.ToString())" - } - Start-Sleep $RetryInterval - } - } - } -} - -function Start-FileDownload { - Param( - [Parameter(Mandatory=$true)] - [String]$URL, - [Parameter(Mandatory=$true)] - [String]$Destination, - [Int]$RetryCount=10 - ) - Write-Output "Downloading $URL to $Destination" - Start-ExecuteWithRetry ` - -ScriptBlock { Invoke-CommandLine -Command "curl.exe" -Arguments "-L -s -o $Destination $URL" } ` - -MaxRetryCount $RetryCount ` - -RetryMessage "Failed to download '${URL}'. Retrying" - Write-Output "Successfully downloaded." -} - -function Add-ToPathEnvVar { - Param( - [Parameter(Mandatory=$true)] - [String[]]$Path, - [Parameter(Mandatory=$false)] - [ValidateSet([System.EnvironmentVariableTarget]::User, [System.EnvironmentVariableTarget]::Machine)] - [System.EnvironmentVariableTarget]$Target=[System.EnvironmentVariableTarget]::Machine - ) - $pathEnvVar = [Environment]::GetEnvironmentVariable("PATH", $Target).Split(';') - $currentSessionPath = $env:PATH.Split(';') - foreach($p in $Path) { - if($p -notin $pathEnvVar) { - $pathEnvVar += $p - } - if($p -notin $currentSessionPath) { - $currentSessionPath += $p - } - } - $env:PATH = $currentSessionPath -join ';' - $newPathEnvVar = $pathEnvVar -join ';' - [Environment]::SetEnvironmentVariable("PATH", $newPathEnvVar, $Target) -} - -function Install-Tool { - [CmdletBinding(DefaultParameterSetName = "URL")] - Param( - [Parameter(Mandatory=$true, ParameterSetName = "URL")] - [String]$URL, - [Parameter(Mandatory=$true, ParameterSetName = "LocalPath")] - [String]$LocalPath, - [Parameter(ParameterSetName = "URL")] - [Parameter(ParameterSetName = "LocalPath")] - [String[]]$Params=@(), - [Parameter(ParameterSetName = "URL")] - [Parameter(ParameterSetName = "LocalPath")] - [Int[]]$AllowedExitCodes=@(0) - ) - PROCESS { - $installerPath = $LocalPath - if($PSCmdlet.ParameterSetName -eq "URL") { - $installerPath = Join-Path $env:TEMP $URL.Split('/')[-1] - Start-FileDownload -URL $URL -Destination $installerPath - } - Write-Output "Installing ${installerPath}" - $p = Start-Process -FilePath $installerPath -ArgumentList $Params -NoNewWindow -PassThru -Wait - if($p.ExitCode -notin $AllowedExitCodes) { - Throw "Installation failed. Exit code: $($p.ExitCode)" - } - if($PSCmdlet.ParameterSetName -eq "URL") { - Start-ExecuteWithRetry ` - -ScriptBlock { Remove-Item -Force -Path $installerPath -ErrorAction Stop } ` - -RetryMessage "Failed to remove ${installerPath}. Retrying" - } - } -} - function Get-GitHubReleaseAssets { Param( [Parameter(Mandatory=$true)] diff --git a/ceph-windows-image-build/build/utils.ps1 b/ceph-windows-image-build/build/utils.ps1 new file mode 100644 index 000000000..f29ab79f5 --- /dev/null +++ b/ceph-windows-image-build/build/utils.ps1 @@ -0,0 +1,130 @@ +function Invoke-CommandLine { + Param( + [Parameter(Mandatory=$true)] + [String]$Command, + [String]$Arguments, + [Int[]]$AllowedExitCodes=@(0) + ) + & $Command $Arguments.Split(" ") + if($LASTEXITCODE -notin $AllowedExitCodes) { + Throw "$Command $Arguments returned a non zero exit code ${LASTEXITCODE}." + } +} + +function Start-ExecuteWithRetry { + Param( + [Parameter(Mandatory=$true)] + [ScriptBlock]$ScriptBlock, + [Int]$MaxRetryCount=10, + [Int]$RetryInterval=3, + [String]$RetryMessage, + [Array]$ArgumentList=@() + ) + $currentErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + $retryCount = 0 + while ($true) { + try { + $res = Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList + $ErrorActionPreference = $currentErrorActionPreference + return $res + } catch [System.Exception] { + $retryCount++ + if ($retryCount -gt $MaxRetryCount) { + $ErrorActionPreference = $currentErrorActionPreference + Throw $_ + } else { + $prefixMsg = "Retry(${retryCount}/${MaxRetryCount})" + if($RetryMessage) { + Write-Host "${prefixMsg} - $RetryMessage" + } elseif($_) { + Write-Host "${prefixMsg} - $($_.ToString())" + } + Start-Sleep $RetryInterval + } + } + } +} + +function Start-FileDownload { + Param( + [Parameter(Mandatory=$true)] + [String]$URL, + [Parameter(Mandatory=$true)] + [String]$Destination, + [Int]$RetryCount=10 + ) + Write-Output "Downloading $URL to $Destination" + Start-ExecuteWithRetry ` + -ScriptBlock { Invoke-CommandLine -Command "curl.exe" -Arguments "-L -s -o $Destination $URL" } ` + -MaxRetryCount $RetryCount ` + -RetryMessage "Failed to download '${URL}'. Retrying" + Write-Output "Successfully downloaded." +} + +function Add-ToPathEnvVar { + Param( + [Parameter(Mandatory=$true)] + [String[]]$Path, + [Parameter(Mandatory=$false)] + [ValidateSet([System.EnvironmentVariableTarget]::User, [System.EnvironmentVariableTarget]::Machine)] + [System.EnvironmentVariableTarget]$Target=[System.EnvironmentVariableTarget]::Machine + ) + $pathEnvVar = [Environment]::GetEnvironmentVariable("PATH", $Target).Split(';') + $currentSessionPath = $env:PATH.Split(';') + foreach($p in $Path) { + if($p -notin $pathEnvVar) { + $pathEnvVar += $p + } + if($p -notin $currentSessionPath) { + $currentSessionPath += $p + } + } + $env:PATH = $currentSessionPath -join ';' + $newPathEnvVar = $pathEnvVar -join ';' + [Environment]::SetEnvironmentVariable("PATH", $newPathEnvVar, $Target) +} + +function Install-Tool { + [CmdletBinding(DefaultParameterSetName = "URL")] + Param( + [Parameter(Mandatory=$true, ParameterSetName = "URL")] + [String]$URL, + [Parameter(Mandatory=$true, ParameterSetName = "LocalPath")] + [String]$LocalPath, + [Parameter(ParameterSetName = "URL")] + [Parameter(ParameterSetName = "LocalPath")] + [String[]]$Params=@(), + [Parameter(ParameterSetName = "URL")] + [Parameter(ParameterSetName = "LocalPath")] + [Int[]]$AllowedExitCodes=@(0) + ) + PROCESS { + $installerPath = $LocalPath + if($PSCmdlet.ParameterSetName -eq "URL") { + $installerPath = Join-Path $env:TEMP $URL.Split('/')[-1] + Start-FileDownload -URL $URL -Destination $installerPath + } + Write-Output "Installing ${installerPath}" + $kwargs = @{ + "FilePath" = $installerPath + "ArgumentList" = $Params + "NoNewWindow" = $true + "PassThru" = $true + "Wait" = $true + } + if((Get-ChildItem $installerPath).Extension -eq '.msi') { + $kwargs["FilePath"] = "msiexec.exe" + $kwargs["ArgumentList"] = @("/i", $installerPath) + $Params + } + $p = Start-Process @kwargs + if($p.ExitCode -notin $AllowedExitCodes) { + Throw "Installation failed. Exit code: $($p.ExitCode)" + } + if($PSCmdlet.ParameterSetName -eq "URL") { + Start-ExecuteWithRetry ` + -ScriptBlock { Remove-Item -Force -Path $installerPath -ErrorAction Stop } ` + -RetryMessage "Failed to remove ${installerPath}. Retrying" + } + } +}