diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 8aa34993..40bdb919 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -118,3 +118,98 @@ steps: notify: - github_commit_status: context: "pr_changed_files Tests: Edge Cases" + + - group: ":windows: install_windows_10_sdk Tests" + steps: + - label: ":windows: install_windows_10_sdk Tests - Version file with valid format and version" + command: | + "20348" | Out-File .windows-10-sdk-version + .\tests\test-install-windows-10-sdk.ps1 -ExpectedExitCode 0 + agents: + queue: windows + notify: + - github_commit_status: + context: "install_windows_10_sdk Tests - Version file with valid format and version" + + - label: ":windows: install_windows_10_sdk Tests - Version file with one new line" + command: | + "20348`n" | Out-File .windows-10-sdk-version + .\tests\test-install-windows-10-sdk.ps1 -ExpectedExitCode 0 + agents: + queue: windows + notify: + - github_commit_status: + context: "install_windows_10_sdk Tests - Version file with one new line" + + - label: ":windows: install_windows_10_sdk Tests - Version file with more than one new line" + command: | + "20348`n`n" | Out-File .windows-10-sdk-version + .\tests\test-install-windows-10-sdk.ps1 -ExpectedExitCode 0 + agents: + queue: windows + notify: + - github_commit_status: + context: "install_windows_10_sdk Tests - Version file with more than one new line" + + - label: ":windows: install_windows_10_sdk Tests - Version file with leading whitespaces" + command: | + " 19041" | Out-File .windows-10-sdk-version + .\tests\test-install-windows-10-sdk.ps1 -ExpectedExitCode 0 + agents: + queue: windows + notify: + - github_commit_status: + context: "install_windows_10_sdk Tests - Version file with leading whitespaces" + + - label: ":windows: install_windows_10_sdk Tests - Version file with trailing whitespaces" + command: | + "18362 " | Out-File .windows-10-sdk-version + .\tests\test-install-windows-10-sdk.ps1 -ExpectedExitCode 0 + agents: + queue: windows + notify: + - github_commit_status: + context: "install_windows_10_sdk Tests - Version file with trailing whitespaces" + + - label: ":windows: install_windows_10_sdk Tests - Version file with a word" + command: | + "not an integer" | Out-File .windows-10-sdk-version + .\tests\test-install-windows-10-sdk.ps1 ` + -ExpectedExitCode 1 ` + -ExpectedErrorKeyphrase "Expected an integer" + agents: + queue: windows + notify: + - github_commit_status: + context: "install_windows_10_sdk Tests - Version file with a word" + + - label: ":windows: install_windows_10_sdk Tests - Missing version file" + command: | + .\tests\test-install-windows-10-sdk.ps1 ` + -ExpectedExitCode 1 ` + -ExpectedErrorKeyphrase "No Windows 10 SDK version file found at .windows-10-sdk-version" + agents: + queue: windows + notify: + - github_commit_status: + context: "install_windows_10_sdk Tests - Version file with a word" + + - label: ":windows: install_windows_10_sdk Tests - Version file with version number that is not in the allowed list" + command: | + "12345" | Out-File .windows-10-sdk-version + .\tests\test-install-windows-10-sdk.ps1 ` + -ExpectedExitCode 1 ` + -ExpectedErrorKeyphrase "Invalid Windows 10 SDK version: 12345" + agents: + queue: windows + notify: + - github_commit_status: + context: "install_windows_10_sdk Tests - Version file with version number that is not in the allowed list" + + - label: ":windows: prepare_windows_host_for_app_distribution Tests - Skip Windows 10 SDK Installation" + command: .\tests\test-prepare-windows-host-for-app-distribution.ps1 + agents: + queue: windows + notify: + - github_commit_status: + context: "prepare_windows_host_for_app_distribution Tests - Skip Windows 10 SDK Installation" diff --git a/bin/install_windows_10_sdk.ps1 b/bin/install_windows_10_sdk.ps1 index 89a0c364..4861539f 100755 --- a/bin/install_windows_10_sdk.ps1 +++ b/bin/install_windows_10_sdk.ps1 @@ -1,7 +1,33 @@ +# Install the Windows 10 SDK and Visual Studio Build Tools using the value in .windows-10-sdk-version. +# +# The expected .windows-10-sdk-version format is a integer representing a valid SDK component id. +# The list of valid component ids can be found at +# https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2022 +# +# Example: +# +# 20348 + +param ( + [switch]$DryRun = $false +) + # Stop script execution when a non-terminating error occurs $ErrorActionPreference = "Stop" -Write-Host "--- :windows: Installing Windows 10 SDK and Visual Studio Build Tools" +Write-Output "--- :windows: Installing Windows 10 SDK and Visual Studio Build Tools" + +# See list at https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2022 +$allowedVersions = @( + "20348", + "19041", + "18362", + "17763", + "17134", + "16299", + "15063", + "14393" +) $windowsSDKVersionFile = ".windows-10-sdk-version" if (-not (Test-Path $windowsSDKVersionFile)) { @@ -9,9 +35,30 @@ if (-not (Test-Path $windowsSDKVersionFile)) { exit 1 } -$windows10SDKVersion = Get-Content $windowsSDKVersionFile +$windows10SDKVersion = (Get-Content -TotalCount 1 $windowsSDKVersionFile).Trim() + +if ($windows10SDKVersion -notmatch '^\d+$') { + Write-Output "[!] Invalid version file format." + Write-Output "Expected an integer, got: '$windows10SDKVersion'" + exit 1 +} + +if ($allowedVersions -notcontains $windows10SDKVersion) { + Write-Output "[!] Invalid Windows 10 SDK version: $windows10SDKVersion" + Write-Output "Allowed versions are:" + foreach ($version in $allowedVersions) { + Write-Output "- $version" + } + Write-Output "More info at https://learn.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-build-tools?view=vs-2022" + exit 1 +} + +Write-Output "Will attempt to set up Windows 10 ($windows10SDKVersion) SDK and Visual Studio Build Tools..." -Write-Host "Will attempt to set up Windows 10 ($windows10SDKVersion) SDK and Visual Studio Build Tools..." +if ($DryRun) { + Write-Output "Running in dry run mode, finishing here." + exit 0 +} # Download the Visual Studio Build Tools Bootstrapper Write-Output "~~~ Downloading Visual Studio Build Tools..." @@ -26,7 +73,7 @@ If (-not (Test-Path $buildToolsPath)) { Write-Output "[!] Failed to download Visual Studio Build Tools" Exit 1 } else { - Write-Output "Successfully downloaded Visual Studio Build Toosl at $buildToolsPath." + Write-Output "Successfully downloaded Visual Studio Build Tools at $buildToolsPath." } # Install the Windows SDK and other required components diff --git a/bin/path_aware_refreshenv.ps1 b/bin/path_aware_refreshenv.ps1 index 067182c7..09206511 100755 --- a/bin/path_aware_refreshenv.ps1 +++ b/bin/path_aware_refreshenv.ps1 @@ -16,10 +16,10 @@ # Stop script execution when a non-terminating error occurs $ErrorActionPreference = "Stop" -Write-Host "PATH before refreshenv is $env:PATH" +Write-Output "PATH before refreshenv is $env:PATH" $originalPath = "$env:PATH" -Write-Host "Calling refreshenv..." +Write-Output "Calling refreshenv..." refreshenv $mergedPath = "$env:PATH;$originalPath" -split ";" | Select-Object -Unique -Skip 1 $env:PATH = ($mergedPath -join ";") -Write-Host "PATH after refreshenv is $env:PATH" +Write-Output "PATH after refreshenv is $env:PATH" diff --git a/bin/prepare_windows_host_for_app_distribution.ps1 b/bin/prepare_windows_host_for_app_distribution.ps1 index c6bb7411..4ac2863a 100755 --- a/bin/prepare_windows_host_for_app_distribution.ps1 +++ b/bin/prepare_windows_host_for_app_distribution.ps1 @@ -8,24 +8,28 @@ # - Install the Windows 10 SDK if it detected a `.windows-10-sdk-version` file(2) # # (1) The certificate it installs is stored in our AWS SecretsManager storage (`windows-code-signing-certificate` secret ID) -# (2) You can skip the Win10 install even if `.windows-10-sdk-version` file is present by using the `SKIP_WINDOWS_10_SDK_INSTALL=1` env var before calling this script +# (2) You can skip the Windows 10 SDK installation regardless of whether `.windows-10-sdk-version` is present by calling the script with `-SkipWindows10SDKInstallation`. # # Note: In addition to calling this script, and depending on your client app, you might want to also install `npm` and the `Node.js` packages used by your client app on the agent too. For that part, you should use the `automattic/nvm` Buildkite plugin on the pipeline step's `plugins:` attribute. # +param ( + [switch]$SkipWindows10SDKInstallation = $false +) + # Stop script execution when a non-terminating error occurs $ErrorActionPreference = "Stop" -Write-Host "--- :windows: Setting up Windows for app distribution" +Write-Output "--- :windows: Setting up Windows for app distribution" -Write-Host "Current working directory: $PWD" +Write-Output "Current working directory: $PWD" -Write-Host "Enable long path behavior" +Write-Output "Enable long path behavior" # See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#maximum-path-length-limitation Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1 # Disable Windows Defender before starting – otherwise our performance is terrible -Write-Host "Disable Windows Defender..." +Write-Output "Disable Windows Defender..." $avPreference = @( @{DisableArchiveScanning = $true} @{DisableAutoExclusions = $true} @@ -61,12 +65,12 @@ $avPreference | Foreach-Object { # https://docs.microsoft.com/en-us/microsoft-365/security/defender-endpoint/microsoft-defender-antivirus-compatibility?view=o365-worldwide $atpRegPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection' if (Test-Path $atpRegPath) { - Write-Host "Set Microsoft Defender Antivirus to passive mode" + Write-Output "Set Microsoft Defender Antivirus to passive mode" Set-ItemProperty -Path $atpRegPath -Name 'ForceDefenderPassiveMode' -Value '1' -Type 'DWORD' } # From https://stackoverflow.com/a/46760714 -Write-Host "--- :windows: Setting up Package Manager" +Write-Output "--- :windows: Setting up Package Manager" $env:ChocolateyInstall = Convert-Path "$((Get-Command choco).Path)\..\.." Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" @@ -74,41 +78,46 @@ Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" # # See how this build failed # https://buildkite.com/automattic/beeper-desktop/builds/2895#01919738-7c6e-4b82-8d1d-1c1800481740 -Write-Host "--- :windows: :linux: Enable developer mode to use symlinks" +Write-Output "--- :windows: :linux: Enable developer mode to use symlinks" $developerMode = Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux if ($developerMode.State -eq 'Enabled') { - Write-Host "Developer Mode is already enabled." + Write-Output "Developer Mode is already enabled." } else { - Write-Host "Enabling Developer Mode..." + Write-Output "Enabling Developer Mode..." try { Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart } catch { - Write-Host "Failed to enable Developer Mode. Continuing without it..." + Write-Output "Failed to enable Developer Mode. Continuing without it..." } } -Write-Host "--- :lock_with_ink_pen: Download Code Signing Certificate" +Write-Output "--- :lock_with_ink_pen: Download Code Signing Certificate" $certificateBinPath = "certificate.bin" $EncodedText = aws secretsmanager get-secret-value --secret-id windows-code-signing-certificate ` | jq -r '.SecretString' ` | Out-File $certificateBinPath $certificatePfxPath = "certificate.pfx" certutil -decode $certificateBinPath $certificatePfxPath -Write-Host "Code signing certificate downloaded at: $((Get-Item $certificatePfxPath).FullName)" +Write-Output "Code signing certificate downloaded at: $((Get-Item $certificatePfxPath).FullName)" -Write-Host "--- :windows: Checking whether to install Windows 10 SDK..." +Write-Output "--- :windows: Checking whether to install Windows 10 SDK..." # When using Electron Forge and electron2appx, building Appx requires the Windows 10 SDK # # See https://github.com/hermit99/electron-windows-store/tree/v2.1.2?tab=readme-ov-file#usage +if ($SkipWindows10SDKInstallation) { + Write-Output "Run with SkipWindows10SDKInstallation = true. Skipping Windows 10 SDK installation check." + exit 0 +} + $windowsSDKVersionFile = ".windows-10-sdk-version" if (Test-Path $windowsSDKVersionFile) { - Write-Host "Found $windowsSDKVersionFile file, installing Windows 10 SDK..." + Write-Output "Found $windowsSDKVersionFile file, installing Windows 10 SDK..." & "$PSScriptRoot\install_windows_10_sdk.ps1" If ($LastExitCode -ne 0) { Exit $LastExitCode } } else { - Write-Host "No $windowsSDKVersionFile file found, skipping Windows 10 SDK installation." + Write-Output "No $windowsSDKVersionFile file found, skipping Windows 10 SDK installation." } diff --git a/tests/test-install-windows-10-sdk.ps1 b/tests/test-install-windows-10-sdk.ps1 new file mode 100644 index 00000000..23e6edb3 --- /dev/null +++ b/tests/test-install-windows-10-sdk.ps1 @@ -0,0 +1,48 @@ +param ( + [int]$ExpectedExitCode = 0, + [string]$ExpectedErrorKeyphrase = "" +) + +# Ensure the output is UTF-8 encoded so we can use emojis... +[System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +$emojiGreenCheck = "$([char]0x2705)" +$emojiRedCross = "$([char]0x274C)" + +Write-Output "Running test-install-windows-10-sdk.ps1 with ExpectedExitCode=$ExpectedExitCode and ExpectedErrorKeyphrase=$ExpectedErrorKeyphrase" + +if (($ExpectedExitCode -eq 0) -and ($ExpectedErrorKeyphrase -ne "")) { + Write-Output "$emojiRedCross Expected call to succeed (expected error code = 0), but given an error keyphrase to check." + exit 1 +} + +$output = & "$PSScriptRoot\..\bin\install_windows_10_sdk.ps1" -DryRun +$exitCode = $LASTEXITCODE + +if ($exitCode -ne $ExpectedExitCode) { + Write-Output "$emojiRedCross Expected exit code $ExpectedExitCode, got $exitCode" + Write-Output "Output was:" + Write-Output "$output" + exit 1 +} else { + Write-Output "$emojiGreenCheck Exit code matches expected value ($ExpectedExitCode)" +} + +# Only check error keyphrase if exit code is not 0 +if ($exitCode -eq 0) { + exit 0 +} + +# If keyphrase is empty, assume the caller is satisfied with only testing the exit code +if ($ExpectedErrorKeyphrase -eq "") { + Write-Output "Exit code match expectation and no error keyphrase was provided. Test completed." + exit 0 +} + +if ($output -match [regex]::Escape($ExpectedErrorKeyphrase)) { + Write-Output "$emojiGreenCheck Error keyphrase matches expected value ($ExpectedErrorKeyphrase)" + Write-Output "Test completed." +} else { + Write-Output "$emojiRedCross Expected error to contain '$ExpectedErrorKeyphrase', but got:" + Write-Output "$output" + exit 1 +} diff --git a/tests/test-prepare-windows-host-for-app-distribution.ps1 b/tests/test-prepare-windows-host-for-app-distribution.ps1 new file mode 100644 index 00000000..f73f1ad9 --- /dev/null +++ b/tests/test-prepare-windows-host-for-app-distribution.ps1 @@ -0,0 +1,53 @@ +# Tests the prepare_windows_host_for_app_distribution.ps1 script with the -SkipWindows10SDKInstallation parameter. +# +# We only test the skip behavior because the installation takes a "long" time to run. + +param ( + [int]$ExpectedExitCode = 0 +) + +# Ensure the output is UTF-8 encoded so we can use emojis... +[System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8 +$emojiGreenCheck = "$([char]0x2705)" +$emojiRedCross = "$([char]0x274C)" + +Write-Output "Testing prepare_windows_host_for_app_distribution.ps1 with -SkipWindows10SDKInstallation" + +# Create a valid SDK version file to ensure it's not being used +$sdkVersion = "20348" +"$sdkVersion" | Out-File .windows-10-sdk-version + +# Run the script with skip parameter +$output = & "$PSScriptRoot\..\bin\prepare_windows_host_for_app_distribution.ps1" -SkipWindows10SDKInstallation +$exitCode = $LASTEXITCODE + +# Check exit code +if ($exitCode -ne $ExpectedExitCode) { + Write-Output "$emojiRedCross Expected exit code $ExpectedExitCode, got $exitCode" + Write-Output "Output was:" + Write-Output "$output" + exit 1 +} else { + Write-Output "$emojiGreenCheck Exit code matches expected value ($ExpectedExitCode)" +} + +$expectedSkipMessage = "Run with SkipWindows10SDKInstallation = true. Skipping Windows 10 SDK installation check." +if ($output -match [regex]::Escape($expectedSkipMessage)) { + Write-Output "$emojiGreenCheck Found expected skip message in output" +} else { + Write-Output "$emojiRedCross Expected to find message about skipping due to parameter, but got:" + Write-Output "$output" + exit 1 +} + +# Verify SDK was not installed by checking the file system +$windowsSDKsRoot = "C:\Program Files (x86)\Windows Kits\10\bin" +$sdkPath = "$windowsSDKsRoot\10.0.$sdkVersion\x64" +If (Test-Path $sdkPath) { + Write-Output "$emojiRedCross Found SDK installation at $sdkPath when it should have been skipped" + exit 1 +} else { + Write-Output "$emojiGreenCheck Confirmed SDK was not installed at $sdkPath" +} + +Write-Output "Test completed successfully."