Modules/businessdev.ALbuild.Apps/Public/Install-BcAzureSignTool.ps1
|
function Install-BcAzureSignTool { <# .SYNOPSIS Ensures AzureSignTool is installed (as a .NET global tool) and on PATH. .DESCRIPTION Installs the 'AzureSignTool' dotnet global tool so the 'azuresigntool' command is available to Invoke-BcAppSigning on a build agent - reinstating the auto-install behaviour of ALbuild V1. Idempotent: when AzureSignTool is already on PATH it does nothing (unless -Force). Requires the .NET SDK ('dotnet'). After installing it adds the global-tools folder (~/.dotnet/tools) to PATH for the current session so the freshly installed 'azuresigntool' is immediately resolvable. .PARAMETER PackageId The dotnet tool package id. Default 'AzureSignTool'. .PARAMETER Version Optional specific version to pin; otherwise the latest is installed. .PARAMETER Force Reinstall/update even when AzureSignTool is already available. .PARAMETER Source NuGet package source to install from, passed as --add-source. Defaults to nuget.org so the install works even when the agent has no NuGet sources configured (a fresh agent often has none, which fails with "No NuGet sources are defined or enabled"). Set to '' to rely solely on the agent's nuget.config, or to an internal feed URL for offline agents. .PARAMETER DotNetExecutable The .NET CLI executable. Default 'dotnet'. .EXAMPLE Install-BcAzureSignTool .OUTPUTS PSCustomObject: Installed (bool), Path, Version. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([PSCustomObject])] param( [string] $PackageId = 'AzureSignTool', [string] $Version, [switch] $Force, [string] $Source = 'https://api.nuget.org/v3/index.json', [string] $DotNetExecutable = 'dotnet' ) # dotnet installs --global tools under <home>\.dotnet\tools. That folder is not always on PATH on # a fresh agent right after install, and some agents relocate the CLI home off the user profile # (DOTNET_CLI_HOME), so the tool can land outside %USERPROFILE%\.dotnet\tools. Consider every # candidate home and, as a backstop, locate the executable directly on disk. $toolDirs = @( @($env:DOTNET_CLI_HOME, $env:USERPROFILE, $HOME) | Where-Object { $_ } | ForEach-Object { Join-Path (Join-Path $_ '.dotnet') 'tools' } | Select-Object -Unique ) $ensurePath = { foreach ($dir in $toolDirs) { if ((Test-Path -LiteralPath $dir) -and (($env:PATH -split [System.IO.Path]::PathSeparator) -notcontains $dir)) { $env:PATH = $dir + [System.IO.Path]::PathSeparator + $env:PATH } } } $resolveTool = { $cmd = Get-Command -Name 'azuresigntool' -ErrorAction SilentlyContinue | Select-Object -First 1 if ($cmd) { return $cmd } foreach ($dir in $toolDirs) { foreach ($exe in @('azuresigntool.exe', 'azuresigntool')) { $candidate = Join-Path $dir $exe if (Test-Path -LiteralPath $candidate) { return (Get-Command -Name $candidate -ErrorAction SilentlyContinue) } } } return $null } & $ensurePath $existing = & $resolveTool if ($existing -and -not $Force) { Write-ALbuildLog "AzureSignTool already available: $($existing.Source)." return [PSCustomObject]@{ Installed = $false; Path = $existing.Source; Version = $existing.Version } } $dotnet = Get-Command -Name $DotNetExecutable -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $dotnet) { throw "The .NET SDK ('$DotNetExecutable') is required to install AzureSignTool. Install the .NET SDK, or install '$PackageId' manually and pass -SignToolPath to Invoke-BcAppSigning." } if ($PSCmdlet.ShouldProcess($PackageId, 'Install AzureSignTool (dotnet global tool)')) { $verb = if ($Force) { 'update' } else { 'install' } # --add-source guarantees a usable feed even when the agent has no NuGet sources configured # ("No NuGet sources are defined or enabled"); --ignore-failed-sources keeps a single failing # feed from aborting the restore. $installArgs = @('tool', $verb, '--global', $PackageId, '--ignore-failed-sources') if ($Source) { $installArgs += @('--add-source', $Source) } if ($Version) { $installArgs += @('--version', $Version) } # SuccessExitCodes 0,1 keeps Invoke-ALbuildProcess from throwing on the benign exit 1 that # `dotnet tool install` returns for "already installed"; we still inspect the result below so a # genuine exit-1 failure is surfaced instead of being reported as a successful install. $result = Invoke-ALbuildProcess -FilePath $dotnet.Source -Arguments $installArgs -PassThru -SuccessExitCodes @(0, 1) $combined = "$($result.StdOut)`n$($result.StdErr)".Trim() if ($combined) { Write-ALbuildLog "dotnet $($installArgs -join ' '):`n$combined" } $benign = $combined -match 'already installed|is up to date' if ($result.ExitCode -ne 0 -and -not $benign) { throw "Failed to install AzureSignTool ('$PackageId') [dotnet exit $($result.ExitCode)]: $combined" } Write-ALbuildLog -Level Success "AzureSignTool '$PackageId' install step completed (dotnet exit $($result.ExitCode))." } & $ensurePath $tool = & $resolveTool if (-not $tool) { $listing = '(unavailable)' try { $listing = (Invoke-ALbuildProcess -FilePath $dotnet.Source -Arguments @('tool', 'list', '--global') -PassThru).StdOut } catch { $listing = "(could not list global tools: $($_.Exception.Message))" } throw ("AzureSignTool was installed but its command was not found. Searched PATH and: $($toolDirs -join ', '). " + "DOTNET_CLI_HOME='$($env:DOTNET_CLI_HOME)', USERPROFILE='$($env:USERPROFILE)'.`nInstalled global tools:`n$($listing.Trim())") } return [PSCustomObject]@{ Installed = $true; Path = $tool.Source; Version = $tool.Version } } |