Modules/businessdev.ALbuild.Core/Public/Get-ALbuildConfig.ps1
|
function Get-ALbuildConfig { <# .SYNOPSIS Returns the effective ALbuild machine/runtime configuration. .DESCRIPTION Machine-scoped tooling settings (cache folders, the AL Tool store, retry, telemetry, licensing) - NOT the committed, per-workspace project settings (country, artifact type, test runner, ...), which are loaded with Get-ALbuildProjectConfig. Builds a [ALbuildConfig] instance by merging, in increasing precedence: 1. Built-in defaults (the ALbuildConfig constructor) 2. The PER-USER config file, if present (legacy location) 3. The MACHINE-WIDE config file, if present 4. In-memory overrides set with Set-ALbuildConfig The machine file deliberately outranks the per-user one. These are properties of the SERVER - which drive holds the artifact cache, where the AL Tool lives - so an administrator's system-wide setting must not be silently undone by a file in some account's profile. That precedence is what makes the setting trustworthy on a shared build agent; a developer box can still keep a personal file, it just loses against an explicit machine setting. ALBUILD_CONFIG names one explicit file and replaces the layering entirely. Why it matters: the per-user file used to be the only store, so the agent account could hold 'G:\alb' in its own profile while an administrator on the same machine read the built-in default 'C:\alb' - two different caches on one server, with nothing pointing out the split. Both halves of that trap are now closed: the machine file is the canonical store, and a config that exists only per-user is reported as such instead of passing for a machine setting. The merged instance is cached for the session; use -Refresh to rebuild it (for example after editing a config file on disk). .PARAMETER Name Optional single setting name to return instead of the whole object. .PARAMETER Refresh Rebuilds the cached configuration from defaults + files + overrides. .PARAMETER Source Returns where the configuration comes from instead of the values: the candidate files, whether they exist, and which settings each one contributes. Use it when a value is not what you expect - the answer is almost always "a different file than you think". .EXAMPLE Get-ALbuildConfig .EXAMPLE Get-ALbuildConfig -Name ArtifactCacheFolder .EXAMPLE Get-ALbuildConfig -Source Shows the machine and per-user file paths, which exist, and the settings each provides. .OUTPUTS ALbuildConfig, the requested setting value, or (with -Source) a PSCustomObject describing the resolution. #> [CmdletBinding()] param( [Parameter(Position = 0)] [string] $Name, [switch] $Refresh, [switch] $Source ) # The layer files, in increasing precedence. An explicit ALBUILD_CONFIG replaces both. $envPath = if (-not [string]::IsNullOrWhiteSpace($env:ALBUILD_CONFIG)) { $env:ALBUILD_CONFIG } else { $null } $userPath = if ($envPath) { $null } else { Get-ALbuildConfigPath -Scope User } $machinePath = if ($envPath) { $null } else { Get-ALbuildConfigPath -Scope Machine } $readLayer = { param([string] $path) # Returns a hashtable of the file's settings, or $null when there is nothing usable to read. if ([string]::IsNullOrWhiteSpace($path) -or -not (Test-Path -LiteralPath $path)) { return $null } try { $parsed = Get-Content -LiteralPath $path -Raw -Encoding UTF8 | ConvertFrom-Json $hash = @{} foreach ($property in $parsed.PSObject.Properties) { $hash[$property.Name] = $property.Value } return $hash } catch { Write-Warning "ALbuild: ignoring invalid config file '$path': $($_.Exception.Message)" return $null } } if ($Refresh -or $Source -or -not $script:ALbuildConfig) { $config = [ALbuildConfig]::new() $layers = [System.Collections.Generic.List[object]]::new() foreach ($layer in @( @{ Scope = 'Environment'; Path = $envPath }, @{ Scope = 'User'; Path = $userPath }, @{ Scope = 'Machine'; Path = $machinePath })) { if (-not $layer.Path) { continue } $settings = & $readLayer $layer.Path $layers.Add([PSCustomObject]@{ Scope = $layer.Scope Path = $layer.Path Exists = [bool] (Test-Path -LiteralPath $layer.Path) Settings = $settings }) if ($settings) { $config.Apply($settings, $true) } } # Layer 4: in-memory overrides. if ($script:ALbuildConfigOverrides -and $script:ALbuildConfigOverrides.Count -gt 0) { $config.Apply($script:ALbuildConfigOverrides, $false) } $script:ALbuildConfig = $config $script:ALbuildConfigLayers = $layers # Diagnose the split that caused this to exist. Once per session, because the config is cached. $userLayer = @($layers | Where-Object { $_.Scope -eq 'User' -and $_.Settings }) | Select-Object -First 1 $machineLayer = @($layers | Where-Object { $_.Scope -eq 'Machine' -and $_.Settings }) | Select-Object -First 1 if ($userLayer -and -not $machineLayer) { Write-Warning ("ALbuild is configured PER USER only ('$($userLayer.Path)'). Other accounts on this " + 'machine - build agents, scheduled tasks, another administrator - will not see these settings and ' + 'will fall back to the built-in defaults, which is how one server ends up with two artifact ' + "caches. Make it machine-wide with: Set-ALbuildConfig -Settings @{ ... } -Persist (elevated).") } elseif ($userLayer -and $machineLayer) { $shadowed = @($userLayer.Settings.Keys | Where-Object { $machineLayer.Settings.ContainsKey($_) -and "$($machineLayer.Settings[$_])" -ne "$($userLayer.Settings[$_])" }) if ($shadowed.Count -gt 0) { Write-Warning ("ALbuild: the machine configuration '$($machinePath)' overrides the per-user file " + "'$($userLayer.Path)' for: $($shadowed -join ', '). The machine setting wins. Remove the " + 'per-user file to stop the two from disagreeing.') } } } if ($Source) { return [PSCustomObject]@{ EffectiveFrom = @($script:ALbuildConfigLayers | Where-Object { $_.Settings } | Select-Object -ExpandProperty Scope) Layers = @($script:ALbuildConfigLayers | ForEach-Object { [PSCustomObject]@{ Scope = $_.Scope Path = $_.Path Exists = $_.Exists Settings = if ($_.Settings) { @($_.Settings.Keys | Sort-Object) } else { @() } } }) Overrides = if ($script:ALbuildConfigOverrides) { @($script:ALbuildConfigOverrides.Keys | Sort-Object) } else { @() } Effective = $script:ALbuildConfig.ToOrderedDictionary() } } if ($PSBoundParameters.ContainsKey('Name')) { return $script:ALbuildConfig.Get($Name) } return $script:ALbuildConfig } |