Public/Common/Set-AutomationSecrets.ps1
|
function Set-AutomationSecrets { <# .SYNOPSIS Exports organisation PATs from the workspace secrets file as environment variables. .DESCRIPTION Generalisation of the NKDAClient-United-Machine Set-MigrationSecrets.ps1 script. For every organisation entry in secrets.json two kinds of environment variables are set: * A derived, predictable name of the form AZDO_PAT_<ORG> (org upper-cased, non-alphanumeric characters replaced with underscores). * Every explicit name listed in the entry's EnvVars array. This is how .NET Options binding feeds tools like the Azure DevOps Migration Tools / Migration Platform, whose committed JSON configs leave AccessToken empty and bind names such as MigrationTools__Endpoints__Source__Authentication__AccessToken from the environment. Entries with an empty or placeholder token are skipped with a warning. Only variable NAMES are printed and returned - values are never written to the console or logs. .PARAMETER SecretsPath Path to the secrets JSON file. Defaults to the initialised workspace's secrets path (<workspace>\secrets\secrets.json). .PARAMETER Scope Where to set the variables: Process (default), User, or Machine (Machine requires elevation). User/Machine also set the current process so values are usable immediately. .PARAMETER NoClobber Leave any variable that is already set. This is what init.ps1 uses, so a CI-provided secret or a deliberate per-shell override always wins over the workspace secrets file. .EXAMPLE Set-AutomationSecrets .EXAMPLE Set-AutomationSecrets -NoClobber #> [CmdletBinding()] param( [string]$SecretsPath, [ValidateSet('Process', 'User', 'Machine')] [string]$Scope = 'Process', [switch]$NoClobber ) if (-not $SecretsPath) { if (-not $script:Workspace) { throw "No -SecretsPath given and no workspace initialised. Run Initialize-AutomationWorkspace (or init.ps1) first, or pass -SecretsPath." } $SecretsPath = $script:Workspace.SecretsPath } if (-not (Test-Path -LiteralPath $SecretsPath)) { throw "Secrets file not found: $SecretsPath. Copy secrets\secrets.example.json to secrets\secrets.json and fill in the PATs." } $entries = Get-AutomationSecrets -SecretsPath $SecretsPath $scopeEnum = [System.EnvironmentVariableTarget]::$Scope $setNames = [System.Collections.Generic.List[string]]::new() $keptNames = [System.Collections.Generic.List[string]]::new() foreach ($entry in $entries) { if (-not $entry.Org) { continue } if (-not $entry.AccessToken) { Write-Warning "Skipping org '$($entry.Org)': token is empty or a placeholder." continue } $names = [System.Collections.Generic.List[string]]::new() foreach ($explicit in $entry.EnvVars) { if (-not $names.Contains([string]$explicit)) { $names.Add([string]$explicit) } } $derived = Get-DerivedPatEnvVarName -Org $entry.Org if (-not $names.Contains($derived)) { $names.Add($derived) } foreach ($name in $names) { if ($NoClobber -and -not [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) { if (-not $keptNames.Contains($name)) { $keptNames.Add($name) } continue } Set-Item -Path ("Env:{0}" -f $name) -Value $entry.AccessToken if ($Scope -ne 'Process') { [System.Environment]::SetEnvironmentVariable($name, $entry.AccessToken, $scopeEnum) } if (-not $setNames.Contains($name)) { $setNames.Add($name) } } } if ($keptNames.Count) { Write-FixStep "Left $($keptNames.Count) environment variable(s) already set (CI secrets and shell overrides win): $($keptNames -join ', ')" } Write-FixStep "Loaded $($setNames.Count) environment variable(s) from secrets (scope: $Scope)." $setNames | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray } return $setNames } |