Private/Configuration.ps1

function Get-ICDefaultEventLogs {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Minimal', 'Standard', 'Extended')]
        [string]$Profile
    )

    $minimal = @(
        'System',
        'Application',
        'Security',
        'Microsoft-Windows-PowerShell/Operational',
        'Microsoft-Windows-Windows Defender/Operational'
    )

    $standardAdditional = @(
        'Windows PowerShell',
        'Microsoft-Windows-TaskScheduler/Operational',
        'Microsoft-Windows-WMI-Activity/Operational',
        'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational',
        'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational',
        'Microsoft-Windows-Sysmon/Operational',
        'Microsoft-Windows-AppLocker/EXE and DLL',
        'Microsoft-Windows-CodeIntegrity/Operational',
        'Microsoft-Windows-Bits-Client/Operational'
    )

    $extendedAdditional = @(
        'Microsoft-Windows-Windows Firewall With Advanced Security/Firewall',
        'Microsoft-Windows-SMBServer/Security',
        'Microsoft-Windows-Kernel-PnP/Configuration',
        'Microsoft-Windows-GroupPolicy/Operational',
        'Microsoft-Windows-WinRM/Operational',
        'Microsoft-Windows-PrintService/Operational',
        'OpenSSH/Operational'
    )

    switch ($Profile) {
        'Minimal'  { return $minimal }
        'Standard' { return @($minimal + $standardAdditional | Select-Object -Unique) }
        'Extended' { return @($minimal + $standardAdditional + $extendedAdditional | Select-Object -Unique) }
    }
}

function New-ICCommonConfiguration {
    [CmdletBinding()]
    param()

    return [ordered]@{
        SpreadsheetSafeCsv              = $true
        MaximumArchiveEntries           = 20000
        MaximumArchiveEntryBytes        = 1073741824L
        MaximumArchiveExpandedBytes     = 21474836480L
        MaximumArchiveCompressionRatio  = 250
    }
}

function Get-ICDefaultConfiguration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Minimal', 'Standard', 'Extended')]
        [string]$Profile
    )

    $allCollectors = @($script:ICCollectorDefinitions.Keys)
    $common = New-ICCommonConfiguration

    $profileConfiguration = switch ($Profile) {
        'Minimal' {
            [ordered]@{
                Collectors                   = @('System', 'Storage', 'Processes', 'Services', 'Network', 'Sessions', 'LocalAccounts', 'Defender', 'PowerShell', 'SecurityConfiguration', 'EventLogs', 'InstalledSoftware', 'Certificates')
                EventLogs                    = @(Get-ICDefaultEventLogs -Profile Minimal)
                EventLookbackHours           = 12
                MaximumEventsPerLog          = 250
                MaximumCapsuleBytes          = 1073741824L
                MaximumEvtxBytesPerLog       = 67108864L
                NativeCommandTimeoutSeconds  = 30
                MaximumNativeOutputBytes     = 10485760L
                MaximumTimelineEntries       = 2000
                DataHandlingProfile          = 'Minimized'
                ExportEvtx                   = $false
                ExportScheduledTaskXml       = $false
                IncludeProcessCommandLines   = $false
                HashProcessExecutables       = $false
                MaximumExecutableHashes      = 50
                HashPersistenceFiles         = $false
                CollectWmiSubscriptions      = $false
                CollectDefenderPreferences   = $false
                CollectWindowsUpdateHistory  = $false
                MaximumWindowsUpdateHistory  = 100
                CollectSignedDrivers         = $false
                MaximumSignedDrivers         = 2500
                MaximumFirewallRules         = 2000
                MaximumEventMessageChars     = 4096
                MaximumPrefetchFiles         = 256
                MaximumArtifactFileBytes     = 8388608L
            }
        }
        'Standard' {
            [ordered]@{
                Collectors                   = $allCollectors
                EventLogs                    = @(Get-ICDefaultEventLogs -Profile Standard)
                EventLookbackHours           = 24
                MaximumEventsPerLog          = 1000
                MaximumCapsuleBytes          = 5368709120L
                MaximumEvtxBytesPerLog       = 268435456L
                NativeCommandTimeoutSeconds  = 60
                MaximumNativeOutputBytes     = 26214400L
                MaximumTimelineEntries       = 10000
                DataHandlingProfile          = 'Full'
                ExportEvtx                   = $true
                ExportScheduledTaskXml       = $true
                IncludeProcessCommandLines   = $true
                HashProcessExecutables       = $false
                MaximumExecutableHashes      = 150
                HashPersistenceFiles         = $true
                CollectWmiSubscriptions      = $true
                CollectDefenderPreferences   = $true
                CollectWindowsUpdateHistory  = $true
                MaximumWindowsUpdateHistory  = 200
                CollectSignedDrivers         = $false
                MaximumSignedDrivers         = 5000
                MaximumFirewallRules         = 5000
                MaximumEventMessageChars     = 8192
                MaximumPrefetchFiles         = 512
                MaximumArtifactFileBytes     = 33554432L
            }
        }
        'Extended' {
            [ordered]@{
                Collectors                   = $allCollectors
                EventLogs                    = @(Get-ICDefaultEventLogs -Profile Extended)
                EventLookbackHours           = 72
                MaximumEventsPerLog          = 5000
                MaximumCapsuleBytes          = 21474836480L
                MaximumEvtxBytesPerLog       = 1073741824L
                NativeCommandTimeoutSeconds  = 120
                MaximumNativeOutputBytes     = 104857600L
                MaximumTimelineEntries       = 50000
                DataHandlingProfile          = 'Full'
                ExportEvtx                   = $true
                ExportScheduledTaskXml       = $true
                IncludeProcessCommandLines   = $true
                HashProcessExecutables       = $true
                MaximumExecutableHashes      = 300
                HashPersistenceFiles         = $true
                CollectWmiSubscriptions      = $true
                CollectDefenderPreferences   = $true
                CollectWindowsUpdateHistory  = $true
                MaximumWindowsUpdateHistory  = 500
                CollectSignedDrivers         = $true
                MaximumSignedDrivers         = 10000
                MaximumFirewallRules         = 10000
                MaximumEventMessageChars     = 16384
                MaximumPrefetchFiles         = 1024
                MaximumArtifactFileBytes     = 134217728L
            }
        }
    }

    foreach ($key in $common.Keys) {
        $profileConfiguration[$key] = $common[$key]
    }

    return $profileConfiguration
}

function Copy-ICValue {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [object]$Value
    )

    if ($null -eq $Value) {
        return $null
    }

    if ($Value -is [System.Collections.IDictionary]) {
        $copy = [ordered]@{}
        foreach ($key in $Value.Keys) {
            $copy[$key] = Copy-ICValue -Value $Value[$key]
        }
        return $copy
    }

    if (($Value -is [System.Collections.IEnumerable]) -and -not ($Value -is [string])) {
        return @($Value | ForEach-Object { Copy-ICValue -Value $_ })
    }

    return $Value
}

function Merge-ICHashtable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [System.Collections.IDictionary]$Base,

        [Parameter(Mandatory)]
        [System.Collections.IDictionary]$Override
    )

    $result = Copy-ICValue -Value $Base
    foreach ($key in $Override.Keys) {
        if (
            $result.Contains($key) -and
            $result[$key] -is [System.Collections.IDictionary] -and
            $Override[$key] -is [System.Collections.IDictionary]
        ) {
            $result[$key] = Merge-ICHashtable -Base $result[$key] -Override $Override[$key]
        }
        else {
            $result[$key] = Copy-ICValue -Value $Override[$key]
        }
    }

    return $result
}

function Test-ICConfiguration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [System.Collections.IDictionary]$Configuration
    )

    $unknown = @($Configuration.Keys | Where-Object { $_ -notin $script:ICConfigurationKeys })
    if ($unknown.Count -gt 0) {
        throw "Unknown configuration key(s): $($unknown -join ', ')."
    }

    $missing = @($script:ICConfigurationKeys | Where-Object { -not $Configuration.Contains($_) })
    if ($missing.Count -gt 0) {
        throw "Missing configuration key(s): $($missing -join ', ')."
    }

    $unknownCollectors = @($Configuration.Collectors | Where-Object { $_ -notin $script:ICCollectorDefinitions.Keys })
    if ($unknownCollectors.Count -gt 0) {
        throw "Unknown collector name(s): $($unknownCollectors -join ', ')."
    }

    if (@($Configuration.Collectors).Count -eq 0) {
        throw 'At least one collector must be selected.'
    }

    if (@($Configuration.EventLogs | Where-Object { [string]::IsNullOrWhiteSpace([string]$_) }).Count -gt 0) {
        throw 'EventLogs cannot contain empty names.'
    }

    foreach ($key in $script:ICConfigurationLimits.Keys) {
        $value = $Configuration[$key]
        if ($value -isnot [int] -and $value -isnot [long]) {
            throw "Configuration key '$key' must be an integer."
        }
        $minimum = [int64]$script:ICConfigurationLimits[$key].Minimum
        $maximum = [int64]$script:ICConfigurationLimits[$key].Maximum
        if ([int64]$value -lt $minimum) {
            throw "Configuration key '$key' must be between $minimum and $maximum."
        }
        if ([int64]$value -gt $maximum) {
            throw "Configuration key '$key' exceeds the supported maximum of $maximum and must be between $minimum and $maximum."
        }
    }

    if ($Configuration.DataHandlingProfile -isnot [string] -or $Configuration.DataHandlingProfile -notin @('Full', 'Minimized')) {
        throw "Configuration key 'DataHandlingProfile' must be 'Full' or 'Minimized'."
    }

    if ([int64]$Configuration.MaximumArchiveExpandedBytes -lt [int64]$Configuration.MaximumCapsuleBytes) {
        throw "Configuration key 'MaximumArchiveExpandedBytes' must be greater than or equal to MaximumCapsuleBytes."
    }
    if ([int64]$Configuration.MaximumArchiveEntryBytes -gt [int64]$Configuration.MaximumArchiveExpandedBytes) {
        throw "Configuration key 'MaximumArchiveEntryBytes' cannot exceed MaximumArchiveExpandedBytes."
    }
    if ([int64]$Configuration.MaximumArchiveEntryBytes -lt [int64]$Configuration.MaximumEvtxBytesPerLog) {
        throw "Configuration key 'MaximumArchiveEntryBytes' must be greater than or equal to MaximumEvtxBytesPerLog."
    }

    foreach ($key in @(
        'ExportEvtx', 'ExportScheduledTaskXml', 'IncludeProcessCommandLines',
        'HashProcessExecutables', 'HashPersistenceFiles', 'CollectWmiSubscriptions',
        'CollectDefenderPreferences', 'CollectWindowsUpdateHistory', 'CollectSignedDrivers',
        'SpreadsheetSafeCsv'
    )) {
        if ($Configuration[$key] -isnot [bool]) {
            throw "Configuration key '$key' must be Boolean."
        }
    }

    return $true
}

function Resolve-ICConfiguration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Minimal', 'Standard', 'Extended')]
        [string]$Profile,

        [string]$ConfigurationPath,

        [string[]]$Collectors,

        [string[]]$ExcludeCollector
    )

    $configuration = Get-ICDefaultConfiguration -Profile $Profile
    $explicitConfigurationKeys = @()

    if (-not [string]::IsNullOrWhiteSpace($ConfigurationPath)) {
        if (-not (Test-Path -LiteralPath $ConfigurationPath -PathType Leaf)) {
            throw "Configuration file '$ConfigurationPath' does not exist."
        }

        $override = Import-PowerShellDataFile -LiteralPath $ConfigurationPath
        $explicitConfigurationKeys = @($override.Keys)
        $configuration = Merge-ICHashtable -Base $configuration -Override $override
    }

    if ($configuration.DataHandlingProfile -eq 'Minimized') {
        foreach ($sensitiveKey in @(
            'IncludeProcessCommandLines', 'ExportScheduledTaskXml',
            'CollectDefenderPreferences', 'CollectWindowsUpdateHistory'
        )) {
            if ($sensitiveKey -notin $explicitConfigurationKeys) {
                $configuration[$sensitiveKey] = $false
            }
        }
    }

    if ($null -ne $Collectors -and $Collectors.Count -gt 0) {
        $configuration.Collectors = @($Collectors | Select-Object -Unique)
    }

    if ($null -ne $ExcludeCollector -and $ExcludeCollector.Count -gt 0) {
        $configuration.Collectors = @($configuration.Collectors | Where-Object { $_ -notin $ExcludeCollector })
    }

    $configuration.Collectors = @($configuration.Collectors | Select-Object -Unique)
    $configuration.EventLogs = @($configuration.EventLogs | Select-Object -Unique)

    [void](Test-ICConfiguration -Configuration $configuration)
    return $configuration
}