quickstarts/microsoft.devcenter/devbox-ready-to-code-image/tools/artifacts/windows-install-winget-packages/windows-install-winget-packages.ps1 (94 lines of code) (raw):
<#
.DESCRIPTION
Installs a set of WinGet packages. Relies on windows-install-winget being executed prior to this script.
.PARAMETER Packages
Packages to install in the format 'package-id-1[@version-1],package-id-2[@version-2],...'.
.PARAMETER IgnorePackageInstallFailures
Allows ignoring failures while installing individual packages and let image creation to continue to be able to inspect logs.
.EXAMPLE
Sample Bicep snippet for using the artifact:
// WinGet packages to install for all users during image creation.
// To discover ids of WinGet packages use 'winget search' command.
// To check whether a package supports machine-wide install run 'winget show --scope Machine --id <package-id>'
var winGetPackageIds = [
'WinDirStat.WinDirStat@1.1.2'
'Kubernetes.kubectl'
'Microsoft.Azure.AZCopy.10'
]
var additionalArtifacts = [
{
name: 'windows-install-winget-packages'
parameters: {
packages: join(winGetPackageIds, ',')
}
}
#>
param
(
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string] $Packages,
[Parameter(Mandatory = $false)] [bool] $IgnorePackageInstallFailures = $false
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$ProgressPreference = 'SilentlyContinue'
function Invoke-Executable {
param (
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string] $commandLine
)
Write-Host "--- Invoking $commandLine"
& ([ScriptBlock]::Create($commandLine))
}
function Install-WinGet-Packages {
param (
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string] $Packages,
[Parameter(Mandatory = $false)] [bool] $IgnorePackageInstallFailures
)
Write-Host "=== Microsoft.DesktopAppInstaller package info: $(Get-AppxPackage Microsoft.DesktopAppInstaller | Out-String)"
$winGetAppInfo = Get-Command "winget.exe" -ErrorAction SilentlyContinue
if (!$winGetAppInfo) {
throw 'Could not locate winget.exe'
}
$winGetPath = $winGetAppInfo.Path
Write-Host "=== Found $winGetPath ; 'winget.exe --info' output: $(Invoke-Executable "$winGetPath --info" | Out-String)"
$packagesArray = @($Packages -Split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
Write-Host "=== Installing $($packagesArray.Count) package(s)"
$script:successfullyInstalledCount = 0
foreach ($package in $packagesArray) {
Write-Host "=== Installing package $package"
$packageInfoParts = @($package -Split '@' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
$packageId = $packageInfoParts[0]
$versionArg = ''
if ($packageInfoParts.Count -gt 2) {
throw "Unexpected format for package $package. Expected format is package-id[@version]"
}
elseif ($packageInfoParts.Count -eq 2) {
$versionArg = "--version $($packageInfoParts[1])"
}
$runBlock = {
$global:LASTEXITCODE = 0
Invoke-Executable "$WinGetPath install --id $packageId $versionArg --exact --disable-interactivity --silent --no-upgrade --accept-package-agreements --accept-source-agreements --verbose-logs --scope machine --force"
if ($global:LASTEXITCODE -ne 0) {
throw "Failed to install $package with exit code $global:LASTEXITCODE. WinGet return codes are listed at https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md"
}
$script:successfullyInstalledCount++
}
RunWithRetries -runBlock $runBlock -retryAttempts 5 -waitBeforeRetrySeconds 1 -ignoreFailure $IgnorePackageInstallFailures
}
Write-Host "=== Successfully installed $script:successfullyInstalledCount of $($packagesArray.Count) package(s)"
Write-Host "=== Granting read and execute permissions to BUILTIN\Users on $env:ProgramFiles\WinGet\Packages"
Invoke-Executable "$env:SystemRoot\System32\icacls.exe ""$env:ProgramFiles\WinGet\Packages"" /t /q /grant ""BUILTIN\Users:(rx)"""
# Backup latest WinGet logs to allow inspection on a Dev Box VM
$winGetLogsDir = 'C:\.tools\Setup\Logs\WinGet'
mkdir $winGetLogsDir -ErrorAction SilentlyContinue | Out-Null
Invoke-Executable "robocopy.exe /R:5 /W:5 /S $env:LOCALAPPDATA\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir $winGetLogsDir"
& cmd.exe /c "echo Reset last exit code to 0"
}
if ((-not (Test-Path variable:global:IsUnderTest)) -or (-not $global:IsUnderTest)) {
try {
Import-Module -Force (Join-Path $(Split-Path -Parent $PSScriptRoot) '_common/windows-retry-utils.psm1')
Install-WinGet-Packages -Packages $Packages -IgnorePackageInstallFailures $IgnorePackageInstallFailures
}
catch {
Write-Error "!!! [ERROR] Unhandled exception:`n$_`n$($_.ScriptStackTrace)" -ErrorAction Stop
}
}