STL/azure-devops/provision-image.ps1

371 строка
11 KiB
PowerShell

# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
<#
.SYNOPSIS
Sets up a machine to be an image for a scale set.
.DESCRIPTION
provision-image.ps1 runs on an existing, freshly provisioned virtual machine,
and sets up that virtual machine as a build machine. After this is done,
(outside of this script), we take that machine and make it an image to be copied
for setting up new VMs in the scale set.
This script must either be run as admin, or one must pass AdminUserPassword;
if the script is run with AdminUserPassword, it runs itself again as an
administrator.
.PARAMETER AdminUserPassword
The administrator user's password; if this is $null, or not passed, then the
script assumes it's running on an administrator account.
#>
param(
[string]$AdminUserPassword = $null
)
$ErrorActionPreference = 'Stop'
<#
.SYNOPSIS
Gets a random file path in the temp directory.
.DESCRIPTION
Get-TempFilePath takes an extension, and returns a path with a random
filename component in the temporary directory with that extension.
.PARAMETER Extension
The extension to use for the path.
#>
Function Get-TempFilePath {
Param(
[String]$Extension
)
if ([String]::IsNullOrWhiteSpace($Extension)) {
throw 'Missing Extension'
}
$tempPath = [System.IO.Path]::GetTempPath()
$tempName = [System.IO.Path]::GetRandomFileName() + '.' + $Extension
return Join-Path $tempPath $tempName
}
<#
.SYNOPSIS
Downloads and extracts a ZIP file to a newly created temporary subdirectory.
.DESCRIPTION
DownloadAndExtractZip returns a path containing the extracted contents.
.PARAMETER Url
The URL of the ZIP file to download.
#>
Function DownloadAndExtractZip {
Param(
[String]$Url
)
if ([String]::IsNullOrWhiteSpace($Url)) {
throw 'Missing Url'
}
$ZipPath = Get-TempFilePath -Extension 'zip'
& curl.exe -L -o $ZipPath -s -S $Url
$TempSubdirPath = Get-TempFilePath -Extension 'dir'
Expand-Archive -Path $ZipPath -DestinationPath $TempSubdirPath -Force
return $TempSubdirPath
}
$TranscriptPath = 'C:\provision-image-transcript.txt'
if ([string]::IsNullOrEmpty($AdminUserPassword)) {
Start-Transcript -Path $TranscriptPath -UseMinimalHeader
} else {
Write-Host 'AdminUser password supplied; switching to AdminUser.'
# https://docs.microsoft.com/en-us/sysinternals/downloads/psexec
$PsToolsZipUrl = 'https://download.sysinternals.com/files/PSTools.zip'
Write-Host "Downloading: $PsToolsZipUrl"
$ExtractedPsToolsPath = DownloadAndExtractZip -Url $PsToolsZipUrl
$PsExecPath = Join-Path $ExtractedPsToolsPath 'PsExec64.exe'
# https://github.com/PowerShell/PowerShell/releases/latest
$PowerShellZipUrl = 'https://github.com/PowerShell/PowerShell/releases/download/v7.3.2/PowerShell-7.3.2-win-x64.zip'
Write-Host "Downloading: $PowerShellZipUrl"
$ExtractedPowerShellPath = DownloadAndExtractZip -Url $PowerShellZipUrl
$PwshPath = Join-Path $ExtractedPowerShellPath 'pwsh.exe'
$PsExecArgs = @(
'-u',
'AdminUser',
'-p',
'AdminUserPassword_REDACTED',
'-accepteula',
'-i',
'-h',
$PwshPath,
'-ExecutionPolicy',
'Unrestricted',
'-File',
$PSCommandPath
)
Write-Host "Executing: $PsExecPath $PsExecArgs"
$PsExecArgs[3] = $AdminUserPassword
$proc = Start-Process -FilePath $PsExecPath -ArgumentList $PsExecArgs -Wait -PassThru
Write-Host 'Reading transcript...'
Get-Content -Path $TranscriptPath
Write-Host 'Cleaning up...'
Remove-Item -Recurse -Path $ExtractedPsToolsPath
Remove-Item -Recurse -Path $ExtractedPowerShellPath
exit $proc.ExitCode
}
$Workloads = @(
'Microsoft.VisualStudio.Component.VC.ASAN',
'Microsoft.VisualStudio.Component.VC.CLI.Support',
'Microsoft.VisualStudio.Component.VC.CMake.Project',
'Microsoft.VisualStudio.Component.VC.CoreIde',
'Microsoft.VisualStudio.Component.VC.Llvm.Clang',
'Microsoft.VisualStudio.Component.VC.Runtimes.ARM.Spectre',
'Microsoft.VisualStudio.Component.VC.Runtimes.ARM64.Spectre',
'Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre',
'Microsoft.VisualStudio.Component.VC.Tools.ARM',
'Microsoft.VisualStudio.Component.VC.Tools.ARM64',
'Microsoft.VisualStudio.Component.VC.Tools.ARM64EC',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'Microsoft.VisualStudio.Component.Windows11SDK.22000'
)
$ReleaseInPath = 'Preview'
$Sku = 'Enterprise'
$VisualStudioBootstrapperUrl = 'https://aka.ms/vs/17/pre/vs_enterprise.exe'
$PythonUrl = 'https://www.python.org/ftp/python/3.11.2/python-3.11.2-amd64.exe'
$CudaUrl = 'https://developer.download.nvidia.com/compute/cuda/11.6.0/local_installers/cuda_11.6.0_511.23_windows.exe'
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
<#
.SYNOPSIS
Writes a message to the screen depending on ExitCode.
.DESCRIPTION
Since msiexec can return either 0 or 3010 successfully, in both cases
we write that installation succeeded, and which exit code it exited with.
If msiexec returns anything else, we write an error.
.PARAMETER ExitCode
The exit code that msiexec returned.
#>
Function PrintMsiExitCodeMessage {
Param(
$ExitCode
)
# 3010 is probably ERROR_SUCCESS_REBOOT_REQUIRED
if ($ExitCode -eq 0 -or $ExitCode -eq 3010) {
Write-Host "Installation successful! Exited with $ExitCode."
}
else {
Write-Error "Installation failed! Exited with $ExitCode."
}
}
<#
.SYNOPSIS
Install Visual Studio.
.DESCRIPTION
InstallVisualStudio takes the $Workloads array, and installs it with the
installer that's pointed at by $BootstrapperUrl.
.PARAMETER Workloads
The set of VS workloads to install.
.PARAMETER BootstrapperUrl
The URL of the Visual Studio installer, i.e. one of vs_*.exe.
.PARAMETER InstallPath
The path to install Visual Studio at.
.PARAMETER Nickname
The nickname to give the installation.
#>
Function InstallVisualStudio {
Param(
[String[]]$Workloads,
[String]$BootstrapperUrl,
[String]$InstallPath = $null,
[String]$Nickname = $null
)
try {
Write-Host 'Downloading Visual Studio...'
[string]$bootstrapperExe = Get-TempFilePath -Extension 'exe'
curl.exe -L -o $bootstrapperExe -s -S $BootstrapperUrl
Write-Host 'Installing Visual Studio...'
$args = @('/c', $bootstrapperExe, '--quiet', '--norestart', '--wait', '--nocache')
foreach ($workload in $Workloads) {
$args += '--add'
$args += $workload
}
if (-not ([String]::IsNullOrWhiteSpace($InstallPath))) {
$args += '--installpath'
$args += $InstallPath
}
if (-not ([String]::IsNullOrWhiteSpace($Nickname))) {
$args += '--nickname'
$args += $Nickname
}
$proc = Start-Process -FilePath cmd.exe -ArgumentList $args -Wait -PassThru
PrintMsiExitCodeMessage $proc.ExitCode
}
catch {
Write-Error "Failed to install Visual Studio! $($_.Exception.Message)"
}
}
<#
.SYNOPSIS
Installs Python.
.DESCRIPTION
InstallPython installs Python from the supplied URL.
.PARAMETER Url
The URL of the Python installer.
#>
Function InstallPython {
Param(
[String]$Url
)
Write-Host 'Downloading Python...'
[string]$installerPath = Get-TempFilePath -Extension 'exe'
curl.exe -L -o $installerPath -s -S $Url
Write-Host 'Installing Python...'
$proc = Start-Process -FilePath $installerPath -ArgumentList `
@('/passive', 'InstallAllUsers=1', 'PrependPath=1', 'CompileAll=1') -Wait -PassThru
$exitCode = $proc.ExitCode
if ($exitCode -eq 0) {
Write-Host 'Installation successful!'
}
else {
Write-Error "Installation failed! Exited with $exitCode."
}
}
<#
.SYNOPSIS
Installs NVIDIA's CUDA Toolkit.
.DESCRIPTION
InstallCuda installs the CUDA Toolkit.
.PARAMETER Url
The URL of the CUDA installer.
#>
Function InstallCuda {
Param(
[String]$Url
)
try {
Write-Host 'Downloading CUDA...'
[string]$installerPath = Get-TempFilePath -Extension 'exe'
curl.exe -L -o $installerPath -s -S $Url
Write-Host 'Installing CUDA...'
$proc = Start-Process -FilePath $installerPath -ArgumentList @('-s') -Wait -PassThru
$exitCode = $proc.ExitCode
if ($exitCode -eq 0) {
Write-Host 'Installation successful!'
}
else {
Write-Error "Installation failed! Exited with $exitCode."
}
}
catch {
Write-Error "Failed to install CUDA! $($_.Exception.Message)"
}
}
<#
.SYNOPSIS
Install or upgrade a pip package.
.DESCRIPTION
Installs or upgrades a pip package specified in $Package.
.PARAMETER Package
The name of the package to be installed or upgraded.
#>
Function PipInstall {
Param(
[String]$Package
)
try {
Write-Host "Installing or upgrading $Package..."
python.exe -m pip install --progress-bar off --upgrade $Package
Write-Host "Done installing or upgrading $Package."
}
catch {
Write-Error "Failed to install or upgrade $Package."
}
}
Write-Host 'AdminUser password not supplied; assuming already running as AdminUser.'
# Print the Windows version, so we can verify whether Patch Tuesday has been picked up.
cmd /c ver
Write-Host 'Configuring AntiVirus exclusions...'
Add-MpPreference -ExclusionPath C:\agent
Add-MpPreference -ExclusionPath D:\
Add-MpPreference -ExclusionProcess ninja.exe
Add-MpPreference -ExclusionProcess clang-cl.exe
Add-MpPreference -ExclusionProcess cl.exe
Add-MpPreference -ExclusionProcess link.exe
Add-MpPreference -ExclusionProcess python.exe
InstallPython $PythonUrl
InstallVisualStudio -Workloads $Workloads -BootstrapperUrl $VisualStudioBootstrapperUrl
InstallCuda -Url $CudaUrl
Write-Host 'Updating PATH...'
# Step 1: Read the system path, which was just updated by installing Python.
$currentSystemPath = [Environment]::GetEnvironmentVariable('Path', 'Machine')
# Step 2: Update the local path (for this running script), so PipInstall can run python.exe.
# Additional directories can be added here (e.g. if we extracted a zip file
# or installed something that didn't update the system path).
$Env:PATH="$($currentSystemPath)"
# Step 3: Update the system path, permanently recording any additional directories that were added in the previous step.
[Environment]::SetEnvironmentVariable('Path', "$Env:PATH", 'Machine')
Write-Host 'Finished updating PATH!'
Write-Host 'Running PipInstall...'
PipInstall pip
PipInstall psutil
Write-Host 'Finished running PipInstall!'
Write-Host 'Setting other environment variables...'
# The STL's PR/CI builds are totally unrepresentative of customer usage.
[Environment]::SetEnvironmentVariable('VSCMD_SKIP_SENDTELEMETRY', '1', 'Machine')
Write-Host 'Finished setting other environment variables!'
Write-Host 'Done!'