Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: Install Win 10 SDK, clearer name, no Node.js setup #144

Open
wants to merge 16 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ _None._

### Breaking Changes

_None._
- `prepare_windows_host_for_node.ps1` has been to `prepare_windows_host_for_app_distribution` [#144]
- `prepare_windows_host_for_app_distribution.ps1` no longer install Node.js.
Clients should use [`nvm-buildkite-plugin`](https://github.com/Automattic/nvm-buildkite-plugin) instead [#144]

### New Features

_None._
- Added new command to install Windows 10 SDK on Windows CI machines, `install_windows_10_sdk.ps1` [#144]
- `prepare_windows_host_for_app_distribution` automatically installs the Windows 10 SDK if version file is found [#144]
- Added new command to run `refreshenv` on Windows preserving the `PATH`, `path_aware_refreshenv` [#144]

### Bug Fixes

Expand Down
59 changes: 59 additions & 0 deletions bin/install_windows_10_sdk.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Stop script execution when a non-terminating error occurs
$ErrorActionPreference = "Stop"

Write-Host "--- :windows: Installing Windows 10 SDK and Visual Studio Build Tools"

$windowsSDKVersionFile = ".windows-10-sdk-version"
if (-not (Test-Path $windowsSDKVersionFile)) {
Write-Output "[!] No Windows 10 SDK version file found at $windowsSDKVersionFile."
exit 1
}

$windows10SDKVersion = Get-Content $windowsSDKVersionFile
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: how does this Get-Content handles possible newlines?

Especially, if the version file ends with a newline at end of file, is that trimmed?
Or maybe it's not, but that doesn't cause any issues because when you then reference $windows10SDKVersion in other commands like --add Microsoft.VisualStudio.Component.Windows10SDK.$windows10SDKVersion that's when the value is trimmed as usage/substitution site?
What if the file contains 2 newlines at end of file? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the -TotalCount 1 parameter of Get-Content can help?
Though even if that allows to limit only reading the first line of the file, not 100% sure if it'd properly trim the \n at the end of that read line, in that case you might need to also use Trim() or TrimEnd().

Tentative suggestion (not tested):

Suggested change
$windows10SDKVersion = Get-Content $windowsSDKVersionFile
$windows10SDKVersion = (Get-Content -TotalCount 1 $windowsSDKVersionFile).Trim()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spent more time than I care to admit on this but got a bunch of tests for the version parsing

image

I'll clean up tomorrow #153


Write-Host "Will attempt to set up Windows 10 ($windows10SDKVersion) SDK and Visual Studio Build Tools..."

# Download the Visual Studio Build Tools Bootstrapper
Write-Output "~~~ Downloading Visual Studio Build Tools..."

$buildToolsPath = ".\vs_buildtools.exe"

Invoke-WebRequest `
-Uri https://aka.ms/vs/17/release/vs_buildtools.exe `
-OutFile $buildToolsPath

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."
}

# Install the Windows SDK and other required components
Write-Output "~~~ Installing Visual Studio Build Tools..."
Start-Process `
-FilePath $buildToolsPath `
-ArgumentList "--quiet --wait --add Microsoft.VisualStudio.Component.Windows10SDK.$windows10SDKVersion" `
-NoNewWindow `
-Wait

# Check if the installation was successful in file system
$windowsSDKsRoot = "C:\Program Files (x86)\Windows Kits\10\bin"
$sdkPath = "$windowsSDKsRoot\10.0.$windows10SDKVersion.0\x64"
If (-not (Test-Path $sdkPath)) {
Write-Output "[!] Failed to install Windows 10 SDK: Could not find SDK at $sdkPath."
If (-not (Test-Path $windowsSDKsRoot)) {
Write-Output "[!] Expected $windowsSDKsRoot to exist, but it does not."
} else {
Write-Output " Found:"
Get-ChildItem -Path $windowsSDKsRoot | ForEach-Object { Write-Output " - $windowsSDKsRoot\$_" }
}
Exit 1
}
Comment on lines +32 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an easy way to check if $windows10SDKVersion is valid (i.e. an existing version, as opposed to if someone tried to use latest or none or whatever invalid text in the version file) and detect it early?

In particular, I'm wondering if the vs_buildtools.exe would fail with an explicit and clear error if that -add … argument we pass to it was invalid (i.e. if it didn't find the component), allowing us to print a specific error message in that case (like "Check the value you set in $windowsSDKVersionFile"), as opposed to the installer happily continuing with the installation of all the other default components—without complaining about this one not existing in the options—and us only finding out the component was not installed after the fact, thanks to your test on lines 42–51… (and even in that case, would it help to provide in the error messages a more explicit suggestion to check the syntax/version used in the version file?)


Write-Output "Visual Studio Build Tools + Windows 10 ($windows10SDKVersion) SDK installation completed. SDK path: $sdkPath."
Write-Output "Windows 10 SDK path: $sdkPath."

Write-Output "~~~ Cleaning up..."
Remove-Item -Path $buildToolsPath
Write-Output "All cleaned up."
20 changes: 20 additions & 0 deletions bin/path_aware_refreshenv.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Stop script execution when a non-terminating error occurs
$ErrorActionPreference = "Stop"

# It seems like calling refreshenv can erase PATH modifications that previous
# steps in an automation script might have made.
#
# See for example the logs in
# https://buildkite.com/automattic/beeper-desktop/builds/2893#01919717-d0d0-441d-a85d-0fe3223467d2/195
#
# To avoid the issue, we save the PATH pre-refreshenv and then manually add all
# the components that were removed.

Write-Host "PATH before refreshenv is $env:PATH"
$originalPath = "$env:PATH"
Write-Host "Calling refreshenv..."
refreshenv
$mergedPath = "$env:PATH;$originalPath" -split ";" | Select-Object -Unique -Skip 1
$env:PATH = ($mergedPath -join ";")
Comment on lines +17 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

Write-Host "PATH after refreshenv is $env:PATH"

Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
# Prepares a `windows` CI agent with all the necessary setup so it can build and distribute a windows app
#
# - Enables long path behavior
# - Disable Windows Defender on the CI agent
# - Install the "Chocolatey" package manager
# - Enable dev mode so the agent can support Linux-style symlinks
# - Download Code Signing Certificates(1)
# - 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
#
# 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.
#

# Stop script execution when a non-terminating error occurs
mokagio marked this conversation as resolved.
Show resolved Hide resolved
$ErrorActionPreference = "Stop"

if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
Write-Host "--- :bug: Running as Administrator"
} else {
Write-Host "--- :bug: Running as not Administrator"
$principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$roles = $principal.Identity.Groups | ForEach-Object {
$_.Translate([Security.Principal.NTAccount]).Value
}
Write-Host "Your roles are:"
$roles | ForEach-Object { Write-Host " - $_" }
}
Write-Host "--- :windows: Setting up Windows for app distribution"

Write-Host "--- :windows: Setting up Windows for Node and Electron builds"
Write-Host "Current working directory: $PWD"

Write-Host "Enable long path behavior"
# See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#maximum-path-length-limitation
Expand Down Expand Up @@ -60,11 +65,6 @@ if (Test-Path $atpRegPath) {
Set-ItemProperty -Path $atpRegPath -Name 'ForceDefenderPassiveMode' -Value '1' -Type 'DWORD'
}

Write-Host "--- :lock_with_ink_pen: Downloading Code Signing Certificate"
$EncodedText = aws secretsmanager get-secret-value --secret-id windows-code-signing-certificate | jq -r '.SecretString' | Out-File 'certificate.bin'
certutil -decode certificate.bin certificate.pfx
If ($LastExitCode -ne 0) { Exit $LastExitCode }

# From https://stackoverflow.com/a/46760714
Write-Host "--- :windows: Setting up Package Manager"
$env:ChocolateyInstall = Convert-Path "$((Get-Command choco).Path)\..\.."
Expand All @@ -89,46 +89,26 @@ if ($developerMode.State -eq 'Enabled') {
}
}

Write-Host "--- :node: Installing NVM"
choco install nvm.portable -y
If ($LastExitCode -ne 0) { Exit $LastExitCode }
Write-Host "--- :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-Host "--- :hammer: Custom PATH refresh post NVM installation to avoid losing previous PATH changes"
Write-Host "PATH before refreshenv is $env:PATH"
# It looks like out of the box, calling refreshenv at this point erases various PATH modifications made by the rest of our automation.
#
# See https://buildkite.com/automattic/beeper-desktop/builds/2893#01919717-d0d0-441d-a85d-0fe3223467d2/195
#
# To avoid the issue, we save the PATH pre-refreshenv and then manually add all the components that were removed.
$originalPath = "$env:PATH"
refreshenv
$mergedPath = "$env:PATH;$originalPath" -split ";" | Select-Object -Unique -Skip 1
$env:PATH = ($mergedPath -join ";")
Write-Host "PATH after refreshenv is $env:PATH"

$nvmRCPath = '.nvmrc'
if (-not (Test-Path $nvmRCPath)) {
Write-Host "No .nvmrc found. Skipping Node set up."
Exit 0
}

Write-Host "--- :node: Installing Node"
$nvmVersion=(Get-Content -Path $nvmRCPath -Total 1)
Write-Host "Switching to nvm version defined in .nvmrc: $nvmVersion"

nvm install $nvmVersion
nvm use $nvmVersion
If ($LastExitCode -ne 0) { Exit $LastExitCode }
Write-Host "--- :windows: Checking whether to install Windows 10 SDK..."

Write-Host "--- :hammer: Custom PATH refresh post NVM installation to avoid losing previous PATH changes"
Write-Host "PATH before refreshenv is $env:PATH"
# It looks like out of the box, calling refreshenv at this point erases various PATH modifications made by the rest of our automation.
# When using Electron Forge and electron2appx, building Appx requires the Windows 10 SDK
#
# See https://buildkite.com/automattic/beeper-desktop/builds/2893#01919717-d0d0-441d-a85d-0fe3223467d2/195
#
# To avoid the issue, we save the PATH pre-refreshenv and then manually add all the components that were removed.
$originalPath = "$env:PATH"
refreshenv
$mergedPath = "$env:PATH;$originalPath" -split ";" | Select-Object -Unique -Skip 1
$env:PATH = ($mergedPath -join ";")
Write-Host "PATH after refreshenv is $env:PATH"
# See https://github.com/hermit99/electron-windows-store/tree/v2.1.2?tab=readme-ov-file#usage

$windowsSDKVersionFile = ".windows-10-sdk-version"
if (Test-Path $windowsSDKVersionFile) {
Write-Host "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."
}