Private/ConfigStore.ps1
|
<# .SYNOPSIS Internal helpers for reading and writing tcs module configuration files. .NOTES Private helpers for the tcs.core module. #> # Per-session cache of resolved module configuration, keyed by module name. # Populated by Get-ModuleConfig / Set-ModuleConfig and read by Invoke-TelemetryCollection. $script:ModuleConfigCache = @{} function Read-JsonFileAsHashtable { [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [string]$Path ) $result = @{} $json = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop if ([string]::IsNullOrWhiteSpace($json)) { return $result } $object = $json | ConvertFrom-Json -ErrorAction Stop foreach ($property in $object.PSObject.Properties) { $result[$property.Name] = $property.Value } return $result } function Write-JsonFile { [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)] [string]$Path, [Parameter(Mandatory)] [System.Collections.IDictionary]$Data ) $directory = Split-Path -Path $Path -Parent if ($directory -and -not (Test-Path -LiteralPath $directory)) { $null = New-Item -Path $directory -ItemType Directory -Force -ErrorAction Stop } # Sort keys so the file is stable and readable $ordered = [ordered]@{} foreach ($key in ($Data.Keys | Sort-Object)) { $ordered[$key] = $Data[$key] } $ordered | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $Path -Encoding UTF8 -ErrorAction Stop } function Get-DefaultModuleConfig { <# .SYNOPSIS Returns the tcs.core defaults, overlaid with the calling module's own Config/Module.Defaults.json when one exists. #> [CmdletBinding()] [OutputType([hashtable])] param( [string]$ModulePath ) $coreDefaultsPath = Join-Path -Path (Join-Path -Path $script:TcsCoreModuleRoot -ChildPath 'Config') -ChildPath 'Module.Defaults.json' $defaults = Read-JsonFileAsHashtable -Path $coreDefaultsPath if ($ModulePath) { $moduleDefaultsPath = Join-Path -Path (Join-Path -Path $ModulePath -ChildPath 'Config') -ChildPath 'Module.Defaults.json' if ((Test-Path -LiteralPath $moduleDefaultsPath) -and ((Resolve-Path -LiteralPath $moduleDefaultsPath).Path -ne (Resolve-Path -LiteralPath $coreDefaultsPath).Path)) { try { $moduleDefaults = Read-JsonFileAsHashtable -Path $moduleDefaultsPath foreach ($key in $moduleDefaults.Keys) { $defaults[$key] = $moduleDefaults[$key] } } catch { Write-Warning "Ignoring invalid defaults file '$moduleDefaultsPath': $($_.Exception.Message)" } } } return $defaults } function ConvertTo-ConfigValueType { <# .SYNOPSIS Converts a stored value to the type of its default (e.g. "True" -> $true), so values written by older versions of tcs.core are read consistently. #> [CmdletBinding()] [OutputType([object])] param( [AllowNull()] [object]$Value, [AllowNull()] [object]$DefaultValue ) if ($null -eq $Value -or $null -eq $DefaultValue) { return $Value } if ($DefaultValue -is [bool] -and $Value -isnot [bool]) { $parsedBool = $false if ([bool]::TryParse([string]$Value, [ref]$parsedBool)) { return $parsedBool } return $DefaultValue } if (($DefaultValue -is [int] -or $DefaultValue -is [long]) -and $Value -is [string]) { $parsedInt = 0 if ([int]::TryParse($Value, [ref]$parsedInt)) { return $parsedInt } return $DefaultValue } return $Value } function Find-ModuleManifestFile { <# .SYNOPSIS Walks up from a path to the nearest folder containing a module manifest (.psd1 with ModuleVersion). #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] param( [Parameter(Mandatory)] [string]$StartPath ) $searchPath = if (Test-Path -LiteralPath $StartPath -PathType Container) { $StartPath } else { Split-Path -Path $StartPath -Parent } while (-not [string]::IsNullOrEmpty($searchPath)) { $candidates = Get-ChildItem -LiteralPath $searchPath -Filter '*.psd1' -File -ErrorAction SilentlyContinue | Where-Object { $_.BaseName -ne 'PSScriptAnalyzerSettings' } foreach ($candidate in $candidates) { try { $data = Import-PowerShellDataFile -LiteralPath $candidate.FullName -ErrorAction Stop if ($data.ContainsKey('ModuleVersion')) { return $candidate } } catch { Write-Verbose "Skipping '$($candidate.FullName)': $($_.Exception.Message)" } } $parent = Split-Path -Path $searchPath -Parent if ($parent -eq $searchPath) { break } $searchPath = $parent } return $null } |