Public/Save-CIEMConfig.ps1

function Save-CIEMConfig {
    <#
    .SYNOPSIS
        Saves CIEM configuration to config.json and updates the in-memory config.

    .DESCRIPTION
        Writes configuration changes to the config.json file and updates the
        $script:Config variable to ensure both file and memory are in sync.
        This should be called before Connect-CIEM when configuration changes.

    .PARAMETER Config
        The configuration object to save. If not provided, saves $script:Config.

    .PARAMETER Settings
        A hashtable of dot-notation paths and values to update in the config.
        Example: @{ 'azure.authentication.method' = 'ManagedIdentity' }

    .EXAMPLE
        Save-CIEMConfig -Settings @{ 'azure.authentication.method' = 'ManagedIdentity' }

    .EXAMPLE
        $script:Config.azure.authentication.method = 'ServicePrincipalSecret'
        Save-CIEMConfig
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Saves configuration file, no destructive action')]
    param(
        [Parameter()]
        [PSCustomObject]$Config,

        [Parameter()]
        [hashtable]$Settings
    )

    $ErrorActionPreference = 'Stop'

    Write-CIEMLog -Message "Save-CIEMConfig called with Settings: $($Settings.Keys -join ', ')" -Severity INFO -Component 'Save-CIEMConfig'

    $configPath = Join-Path -Path $script:ModuleRoot -ChildPath 'config.json'
    Write-CIEMLog -Message "Config path: $configPath" -Severity DEBUG -Component 'Save-CIEMConfig'

    # Load current config if not provided
    if (-not $Config) {
        if (Test-Path $configPath) {
            Write-CIEMLog -Message "Loading existing config from file..." -Severity DEBUG -Component 'Save-CIEMConfig'
            $Config = Get-Content $configPath -Raw | ConvertFrom-Json -AsHashtable
        } else {
            Write-CIEMLog -Message "Configuration file not found: $configPath" -Severity ERROR -Component 'Save-CIEMConfig'
            throw "Configuration file not found: $configPath"
        }
    } elseif ($Config -is [PSCustomObject]) {
        Write-CIEMLog -Message "Converting PSCustomObject Config to hashtable..." -Severity DEBUG -Component 'Save-CIEMConfig'
        # Convert PSCustomObject to hashtable for easier manipulation
        $Config = $Config | ConvertTo-Json -Depth 10 | ConvertFrom-Json -AsHashtable
    }

    # Apply settings if provided
    if ($Settings) {
        Write-CIEMLog -Message "Applying settings to config..." -Severity DEBUG -Component 'Save-CIEMConfig'
        foreach ($key in $Settings.Keys) {
            Write-CIEMLog -Message "Setting '$key' = '$($Settings[$key])'" -Severity DEBUG -Component 'Save-CIEMConfig'
            Set-NestedHashtableValue -Hashtable $Config -Path $key -Value $Settings[$key]
        }
    }

    # Save to file
    Write-CIEMLog -Message "Saving config to file..." -Severity DEBUG -Component 'Save-CIEMConfig'
    $jsonContent = $Config | ConvertTo-Json -Depth 10
    Set-Content -Path $configPath -Value $jsonContent -Encoding UTF8

    # Update in-memory config
    Write-CIEMLog -Message "Updating in-memory config..." -Severity DEBUG -Component 'Save-CIEMConfig'
    $script:Config = Get-Content $configPath -Raw | ConvertFrom-Json

    Write-CIEMLog -Message "Configuration saved successfully" -Severity INFO -Component 'Save-CIEMConfig'
    Write-Verbose "Configuration saved to $configPath"
}

function Set-NestedHashtableValue {
    <#
    .SYNOPSIS
        Sets a value in a nested hashtable using dot notation path.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable]$Hashtable,

        [Parameter(Mandatory)]
        [string]$Path,

        [Parameter(Mandatory)]
        $Value
    )

    $parts = $Path -split '\.'
    $current = $Hashtable

    for ($i = 0; $i -lt $parts.Count - 1; $i++) {
        $part = $parts[$i]
        if (-not $current.ContainsKey($part)) {
            $current[$part] = @{}
        }
        $current = $current[$part]
    }

    $current[$parts[-1]] = $Value
}