CISAzureBenchmark.psm1

#Requires -Version 5.1
# CIS Microsoft Azure Foundations Benchmark v5.0.0 - Compliance Checker Module
# Covers all 155 controls (93 Automated + 62 Manual)

$ModuleRoot = $PSScriptRoot

# Load default module configuration
$script:CISConfigPath = Join-Path (Join-Path $ModuleRoot 'Data') 'ModuleConfig.psd1'
$script:CISConfig = if (Test-Path $script:CISConfigPath) {
    Import-PowerShellDataFile -Path $script:CISConfigPath
} else {
    @{}
}

# Load benchmark version from ControlDefinitions and module version from manifest
$script:CISBenchmarkVersion = 'v5.0.0'  # fallback
$script:CISModuleVersion = '5.1.0'      # fallback
try {
    $defPath = Join-Path (Join-Path $ModuleRoot 'Data') 'ControlDefinitions.psd1'
    if (Test-Path $defPath) {
        $defs = Import-PowerShellDataFile -Path $defPath
        if ($defs.BenchmarkVersion) { $script:CISBenchmarkVersion = $defs.BenchmarkVersion }
    }
} catch { }
try {
    $manifestPath = Join-Path $ModuleRoot 'CISAzureBenchmark.psd1'
    if (Test-Path $manifestPath) {
        $manifest = Import-PowerShellDataFile -Path $manifestPath
        if ($manifest.ModuleVersion) { $script:CISModuleVersion = $manifest.ModuleVersion }
    }
} catch { }

# Ensure System.Web.HttpUtility is available (used for HTML encoding in reports)
Add-Type -AssemblyName System.Web -ErrorAction SilentlyContinue

function Merge-Hashtable {
    <#
    .SYNOPSIS
        Recursively merges $Source into $Target. Nested hashtables are merged; other values are overwritten.
    #>

    param(
        [hashtable]$Target,
        [hashtable]$Source
    )
    foreach ($key in $Source.Keys) {
        if ($Target.ContainsKey($key) -and $Target[$key] -is [hashtable] -and $Source[$key] -is [hashtable]) {
            Merge-Hashtable -Target $Target[$key] -Source $Source[$key]
        } else {
            $Target[$key] = $Source[$key]
        }
    }
}

function Set-CISConfigOverride {
    <#
    .SYNOPSIS
        Deep-merges user config overrides into the module configuration.
    .DESCRIPTION
        Nested hashtable values are recursively merged so users can override individual
        keys without losing other defaults at the same level.
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$ConfigPath)

    if (-not (Test-Path $ConfigPath)) {
        Write-Warning "Config file not found: $ConfigPath. Using defaults."
        return
    }
    $userConfig = Import-PowerShellDataFile -Path $ConfigPath
    Merge-Hashtable -Target $script:CISConfig -Source $userConfig
}

# Dot-source all function files in order: Private -> Checks -> Reports -> Public
$Private = @(Get-ChildItem -Path "$ModuleRoot/Private/*.ps1" -ErrorAction SilentlyContinue)
$Checks  = @(Get-ChildItem -Path "$ModuleRoot/Checks/*.ps1"  -ErrorAction SilentlyContinue)
$Reports = @(Get-ChildItem -Path "$ModuleRoot/Reports/*.ps1" -ErrorAction SilentlyContinue)
$Public  = @(Get-ChildItem -Path "$ModuleRoot/Public/*.ps1"  -ErrorAction SilentlyContinue)

foreach ($file in @($Private + $Checks + $Reports + $Public)) {
    try {
        . $file.FullName
    }
    catch {
        Write-Error "Failed to load $($file.FullName): $_"
    }
}

# Export public functions
Export-ModuleMember -Function @(
    'Connect-CISAzureBenchmark'
    'Disconnect-CISAzureBenchmark'
    'Invoke-CISAzureBenchmark'
    'Get-CISControlList'
    'Export-CISReport'
    'Compare-CISBenchmarkResults'
    'Export-CISRemediationScript'
)