Private/FSvc.Persist.ps1
|
# Persisting Set-FSvcConfig values across sessions. # # Windows: writes user environment variables (registry-backed, visible to all # new processes, including scheduled tasks). Other platforms: the .NET "User" # environment target is a silent no-op, so a delimited block is written to the # PowerShell profile instead. The current process is always updated by # Set-FSvcConfig itself. $script:FSvcBlockStart = '# >>> fsvc (managed) >>>' $script:FSvcBlockEnd = '# <<< fsvc (managed) <<<' # True on Windows, across Windows PowerShell 5.1 (no $IsWindows) and PS 7+. function Test-IsWindowsHost { if ($null -ne $IsWindows) { return [bool]$IsWindows } return ($env:OS -eq 'Windows_NT') } # Maps friendly setting names to their FSVC_* environment variable names. # Unknown keys are ignored; empty values are kept so they can clear a setting. function ConvertTo-FSvcEnvironmentMap { param([hashtable]$Settings) $map = @{} if (-not $Settings) { return $map } foreach ($key in $Settings.Keys) { if ($script:FSvcEnvNames.ContainsKey($key)) { $map[$script:FSvcEnvNames[$key]] = [string]$Settings[$key] } } return $map } # Renders the managed profile block for an env-name -> value map. function New-FSvcProfileBlock { param([hashtable]$Map) if (-not $Map -or $Map.Count -eq 0) { return "" } $lines = @() foreach ($key in ($Map.Keys | Sort-Object)) { $value = ([string]$Map[$key]).Replace("'", "''") $lines += ("`$env:{0} = '{1}'" -f $key, $value) } return ($script:FSvcBlockStart + "`n" + ($lines -join "`n") + "`n" + $script:FSvcBlockEnd + "`n") } # Reads the managed block back into an env-name -> value map. function Read-FSvcProfileSettings { param([string]$ProfilePath) $map = @{} if (-not $ProfilePath -or -not (Test-Path -LiteralPath $ProfilePath)) { return $map } $text = Get-Content -LiteralPath $ProfilePath -Raw $pattern = "(?ms)" + [regex]::Escape($script:FSvcBlockStart) + "(.*?)" + [regex]::Escape($script:FSvcBlockEnd) $match = [regex]::Match($text, $pattern) if (-not $match.Success) { return $map } foreach ($line in ($match.Groups[1].Value -split "`n")) { $lm = [regex]::Match($line.Trim(), '^\$env:(?<n>[A-Za-z0-9_]+)\s*=\s*''(?<v>.*)''$') if ($lm.Success) { $map[$lm.Groups['n'].Value] = $lm.Groups['v'].Value.Replace("''", "'") } } return $map } # Inserts, replaces, or (with an empty $Block) removes the managed block, # preserving the rest of the profile. function Update-FSvcProfile { param([string]$ProfilePath, [AllowNull()][string]$Block) $existing = "" if (Test-Path -LiteralPath $ProfilePath) { $existing = Get-Content -LiteralPath $ProfilePath -Raw } $pattern = "(?ms)" + [regex]::Escape($script:FSvcBlockStart) + ".*?" + [regex]::Escape($script:FSvcBlockEnd) + "\r?\n?" $cleaned = [regex]::Replace($existing, $pattern, "").TrimEnd() if ($Block) { $cleaned = $cleaned + "`n`n" + $Block } elseif ($cleaned) { $cleaned = $cleaned + "`n" } $dir = Split-Path -Parent $ProfilePath if ($dir -and -not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } Set-Content -LiteralPath $ProfilePath -Value $cleaned -NoNewline } # Persists settings for future sessions. Windows writes user environment # variables (via $SetUserEnvironment, injectable for tests); other platforms # merge into the profile block. Empty values clear a persisted setting. function Set-FSvcPersistentSettings { param( [hashtable]$Settings, [bool]$OnWindows, [string]$ProfilePath, [AllowNull()][scriptblock]$SetUserEnvironment ) $map = ConvertTo-FSvcEnvironmentMap -Settings $Settings if ($map.Count -eq 0) { return } if ($OnWindows) { if (-not $SetUserEnvironment) { $SetUserEnvironment = { param($Name, $Value) if ($Value) { [Environment]::SetEnvironmentVariable($Name, $Value, 'User') } else { [Environment]::SetEnvironmentVariable($Name, $null, 'User') } } } foreach ($key in $map.Keys) { & $SetUserEnvironment $key $map[$key] } return } $merged = Read-FSvcProfileSettings -ProfilePath $ProfilePath foreach ($key in $map.Keys) { if ($map[$key]) { $merged[$key] = $map[$key] } else { [void]$merged.Remove($key) } } Update-FSvcProfile -ProfilePath $ProfilePath -Block (New-FSvcProfileBlock -Map $merged) } |