Private/Get-WinslopFixConfig.ps1

function Get-WinslopFixConfig {
    <#
    .SYNOPSIS
        Loads the WinslopFix configuration from disk with fallback defaults.
    .DESCRIPTION
        Reads configuration from the deployed config.json at the installation path.
        If no deployed config exists, falls back to the bundled DefaultConfig.json.
        If neither exists, returns hardcoded safe defaults.

        An optional ConfigPath parameter allows callers to override the config
        file location entirely (useful for testing and RMM one-shot deployments).
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [string]$ConfigPath
    )

    process {
        # Hardcoded safe defaults — the final fallback if no JSON is found
        $defaults = [ordered]@{
            Strategy              = 'MemoryThreshold'
            PerProcessThresholdMB = 512
            AggregateThresholdMB  = 2048
            TimerSeconds          = 60
            ScanIntervalSeconds   = 10
            ProcessName           = 'WorkloadsSessionHost'
            EventLogSource        = 'WinslopFix'
            EventLogName          = 'Application'
        }

        # Resolution order: explicit path > deployed config > bundled default
        $candidatePaths = @(
            $ConfigPath
            (Join-Path $env:ProgramData 'WinslopFix\config.json')
            (Join-Path $PSScriptRoot '..\Config\DefaultConfig.json')
        ) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }

        $loadedConfig = $null
        foreach ($path in $candidatePaths) {
            if (Test-Path -LiteralPath $path -PathType Leaf) {
                try {
                    $raw = Get-Content -LiteralPath $path -Raw -ErrorAction Stop
                    $loadedConfig = $raw | ConvertFrom-Json -ErrorAction Stop
                    Write-Verbose "WinslopFix config loaded from: $path"
                    break
                }
                catch {
                    Write-Warning "Failed to parse config at '$path': $($_.Exception.Message)"
                }
            }
        }

        if ($null -eq $loadedConfig) {
            Write-Verbose 'No config file found. Using hardcoded defaults.'
            return [PSCustomObject]$defaults
        }

        # Merge loaded values over defaults — ensures new config keys always have a value
        $merged = [ordered]@{}
        foreach ($key in $defaults.Keys) {
            $value = $loadedConfig.PSObject.Properties[$key]
            if ($null -ne $value) {
                $merged[$key] = $value.Value
            }
            else {
                $merged[$key] = $defaults[$key]
            }
        }

        return [PSCustomObject]$merged
    }
}