Private/NC-Hlp.Configuration.ps1

#Requires -Version 5.0
using namespace System.Management.Automation

# Nebula.Core: (Private) Configuration helpers ======================================================================================================

function Import-NCConfigurationFile {
    <#
    .SYNOPSIS
        Loads a Nebula.Core configuration file.
    .DESCRIPTION
        Imports a PowerShell data file and returns its hashtable content. If the file is missing or invalid,
        returns an empty hashtable and logs the failure.
    .PARAMETER Path
        Path to the configuration file to import.
    #>

    [CmdletBinding()]
    param([string]$Path)

    if (-not (Test-Path -LiteralPath $Path)) {
        return @{}
    }

    try {
        $data = Import-PowerShellDataFile -LiteralPath $Path
        if ($data -isnot [hashtable]) {
            throw "Configuration file '$Path' does not contain a hashtable."
        }
        return $data
    }
    catch {
        Write-NCMessage "Failed to load Nebula.Core configuration from '$Path'. $($_.Exception.Message)" -Level ERROR
        return @{}
    }
}

function Merge-NCConfig {
    <#
    .SYNOPSIS
        Merges two configuration hashtables.
    .DESCRIPTION
        Copies override values into the base hashtable and returns the merged result.
    .PARAMETER Base
        Base configuration hashtable to update.
    .PARAMETER Override
        Hashtable containing values that should overwrite the base configuration.
    #>

    [CmdletBinding()]
    param(
        [hashtable]$Base,
        [hashtable]$Override
    )

    foreach ($key in $Override.Keys) {
        $Base[$key] = $Override[$key]
    }

    return $Base
}

if (-not $script:NCLicenseSources) {
    $script:NCLicenseSources = @{
        Primary = @{
            CacheFileName = 'M365_licenses.json'
            ApiUrl        = "https://api.github.com/repos/gioxx/Nebula.Core/commits?path=JSON/M365_licenses.json"
            FileUrl       = 'https://raw.githubusercontent.com/gioxx/Nebula.Core/main/JSON/M365_licenses.json'
        }
        Custom  = @{
            CacheFileName = 'M365_licenses_custom.json'
            ApiUrl        = "https://api.github.com/repos/gioxx/Nebula.Core/commits?path=JSON/M365_licenses_custom.json"
            FileUrl       = 'https://raw.githubusercontent.com/gioxx/Nebula.Core/main/JSON/M365_licenses_custom.json'
        }
    }
}

if (-not $script:NC_Defaults) {
    $script:NC_Defaults = [ordered]@{
        CSV_DefaultLimiter      = ";"
        CSV_Encoding            = 'UTF-8'
        CheckUpdatesOnConnect   = $true
        CheckUpdatesIntervalHours = 72
        DateTimeString_CSV      = 'yyyyMMdd'
        DateTimeString_Full     = 'yyy-MM-dd HH:mm:ss'
        LicenseCacheDays        = 7
        LicenseCacheDirectory   = (Join-Path $env:USERPROFILE '.NebulaCore\Cache')
        MaxFieldLength          = 35
        UsageLocation           = 'US'
        UserConfigRoot          = (Join-Path $env:USERPROFILE '.NebulaCore')
    }
}

function Initialize-NebulaConfig {
    <#
    .SYNOPSIS
        Builds the effective Nebula.Core configuration.
    .DESCRIPTION
        Starts from module defaults, then layers machine settings, user settings, and environment overrides.
        The merged configuration is stored in the script scope for use by the module.
    #>

    $config = [ordered]@{}
    foreach ($key in $script:NC_Defaults.Keys) {
        $config[$key] = $script:NC_Defaults[$key]
    }

    $userConfigPath = Join-Path -Path $script:NC_Defaults.UserConfigRoot -ChildPath 'settings.psd1'
    $machineConfigPath = Join-Path -Path $env:ProgramData -ChildPath 'Nebula.Core\settings.psd1'
    $userConfigExists = Test-Path -LiteralPath $userConfigPath
    $machineConfigExists = Test-Path -LiteralPath $machineConfigPath

    $userConfig = Import-NCConfigurationFile -Path $userConfigPath
    $machineConfig = Import-NCConfigurationFile -Path $machineConfigPath

    $machineOverrideKeys = [System.Collections.Generic.HashSet[string]]::new()
    if ($machineConfig.Count) {
        foreach ($key in $machineConfig.Keys) {
            $machineOverrideKeys.Add($key) | Out-Null
        }
        $config = Merge-NCConfig -Base $config -Override $machineConfig
    }

    $userOverrideKeys = [System.Collections.Generic.HashSet[string]]::new()
    if ($userConfig.Count) {
        foreach ($key in $userConfig.Keys) {
            $userOverrideKeys.Add($key) | Out-Null
        }
        $config = Merge-NCConfig -Base $config -Override $userConfig
    }

    $envOverrideKeys = [System.Collections.Generic.HashSet[string]]::new()
    foreach ($key in $config.Keys) {
        $envVarName = "NEBULA_{0}" -f ($key.ToUpperInvariant())
        $envValue = [Environment]::GetEnvironmentVariable($envVarName)
        if ($envValue) {
            $config[$key] = $envValue
            $envOverrideKeys.Add($key) | Out-Null
        }
    }

    $script:NC_Config = $config
    New-Variable -Name NCVars -Value $config -Scope Script -Force

    $script:NebulaConfigInfo = [ordered]@{
        UserConfigPath          = $userConfigPath
        UserConfigExists        = $userConfigExists
        UserConfigLoaded        = $userConfig.Count -gt 0
        UserOverrideKeys        = @($userOverrideKeys)
        MachineConfigPath       = $machineConfigPath
        MachineConfigExists     = $machineConfigExists
        MachineConfigLoaded     = $machineConfig.Count -gt 0
        MachineOverrideKeys     = @($machineOverrideKeys)
        EnvironmentOverrideKeys = @($envOverrideKeys)
    }
}