Private/Config-Functions.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    Configuration management functions for MakeMeAdminCLI.

.DESCRIPTION
    Provides functions to read, validate, and manage configuration settings
    from the config.json file stored in the module directory.

.NOTES
    Author: MakeMeAdminCLI
    Version: 1.0.0
#>


# Module-level variables
# Only set ModuleRoot if not already set by the parent module (psm1)
if (-not $script:ModuleRoot -or -not (Test-Path $script:ModuleRoot)) {
    # When running standalone (e.g., from Service-Main.ps1), determine path from script location
    $script:ModuleRoot = Split-Path -Parent $PSScriptRoot
    if (-not (Test-Path (Join-Path $script:ModuleRoot "config.json"))) {
        # Fallback to installed module location
        $script:ModuleRoot = Join-Path $env:ProgramFiles "WindowsPowerShell\Modules\MakeMeAdminCLI"
    }
}
$script:ConfigFilePath = Join-Path $script:ModuleRoot "config.json"
$script:DefaultStateFolder = Join-Path $env:ProgramData "MakeMeAdminCLI"

# Default configuration values
$script:DefaultConfig = @{
    DefaultDurationMinutes = 15
    MaxDurationMinutes = 60
    MinDurationMinutes = 1
    EventLogSource = "MakeMeAdminCLI"
    AllowedUsers = @()
    DeniedUsers = @()
    PipeName = "MakeMeAdminCLI"
    TaskPath = "\Microsoft\Windows\MakeMeAdminCLI"
    StateFilePath = $null
}

function Get-MakeMeAdminConfig {
    <#
    .SYNOPSIS
        Retrieves the current configuration settings.

    .DESCRIPTION
        Reads the configuration from config.json and returns it as a PowerShell object.
        If the configuration file doesn't exist or is invalid, returns default values.

    .OUTPUTS
        PSCustomObject containing the configuration settings.

    .EXAMPLE
        $config = Get-MakeMeAdminConfig
        Write-Host "Default duration: $($config.DefaultDurationMinutes) minutes"
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param()

    try {
        if (Test-Path $script:ConfigFilePath) {
            $jsonContent = Get-Content -Path $script:ConfigFilePath -Raw -ErrorAction Stop
            $config = $jsonContent | ConvertFrom-Json -ErrorAction Stop

            # Merge with defaults to ensure all properties exist
            $result = @{}
            foreach ($key in $script:DefaultConfig.Keys) {
                if ($null -ne $config.$key) {
                    $result[$key] = $config.$key
                } else {
                    $result[$key] = $script:DefaultConfig[$key]
                }
            }

            # Set default state file path if not specified
            if (-not $result.StateFilePath) {
                $result.StateFilePath = Join-Path $script:DefaultStateFolder "state.json"
            }

            return [PSCustomObject]$result
        } else {
            Write-Warning "Configuration file not found at '$script:ConfigFilePath'. Using defaults."
            $result = $script:DefaultConfig.Clone()
            $result.StateFilePath = Join-Path $script:DefaultStateFolder "state.json"
            return [PSCustomObject]$result
        }
    }
    catch {
        Write-Warning "Error reading configuration: $($_.Exception.Message). Using defaults."
        $result = $script:DefaultConfig.Clone()
        $result.StateFilePath = Join-Path $script:DefaultStateFolder "state.json"
        return [PSCustomObject]$result
    }
}

function Set-MakeMeAdminConfig {
    <#
    .SYNOPSIS
        Updates the configuration settings.

    .DESCRIPTION
        Modifies the config.json file with the provided settings.
        Only updates the settings that are provided; other settings remain unchanged.

    .PARAMETER DefaultDurationMinutes
        The default duration for temporary admin rights in minutes.

    .PARAMETER MaxDurationMinutes
        The maximum allowed duration in minutes.

    .PARAMETER MinDurationMinutes
        The minimum allowed duration in minutes.

    .PARAMETER AllowedUsers
        Array of usernames or SIDs that are allowed to request admin rights.
        Empty array means all users are allowed.

    .PARAMETER DeniedUsers
        Array of usernames or SIDs that are denied admin rights.

    .EXAMPLE
        Set-MakeMeAdminConfig -DefaultDurationMinutes 30 -MaxDurationMinutes 120
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [ValidateRange(1, 1440)]
        [int]$DefaultDurationMinutes,

        [ValidateRange(1, 1440)]
        [int]$MaxDurationMinutes,

        [ValidateRange(1, 1440)]
        [int]$MinDurationMinutes,

        [string[]]$AllowedUsers,

        [string[]]$DeniedUsers
    )

    try {
        # Get current config
        $config = Get-MakeMeAdminConfig
        $configHash = @{
            DefaultDurationMinutes = $config.DefaultDurationMinutes
            MaxDurationMinutes = $config.MaxDurationMinutes
            MinDurationMinutes = $config.MinDurationMinutes
            EventLogSource = $config.EventLogSource
            AllowedUsers = @($config.AllowedUsers)
            DeniedUsers = @($config.DeniedUsers)
            PipeName = $config.PipeName
            TaskPath = $config.TaskPath
            StateFilePath = $config.StateFilePath
        }

        # Update with provided values
        if ($PSBoundParameters.ContainsKey('DefaultDurationMinutes')) {
            $configHash.DefaultDurationMinutes = $DefaultDurationMinutes
        }
        if ($PSBoundParameters.ContainsKey('MaxDurationMinutes')) {
            $configHash.MaxDurationMinutes = $MaxDurationMinutes
        }
        if ($PSBoundParameters.ContainsKey('MinDurationMinutes')) {
            $configHash.MinDurationMinutes = $MinDurationMinutes
        }
        if ($PSBoundParameters.ContainsKey('AllowedUsers')) {
            $configHash.AllowedUsers = $AllowedUsers
        }
        if ($PSBoundParameters.ContainsKey('DeniedUsers')) {
            $configHash.DeniedUsers = $DeniedUsers
        }

        # Validate configuration
        if ($configHash.MinDurationMinutes -gt $configHash.MaxDurationMinutes) {
            throw "MinDurationMinutes ($($configHash.MinDurationMinutes)) cannot be greater than MaxDurationMinutes ($($configHash.MaxDurationMinutes))"
        }
        if ($configHash.DefaultDurationMinutes -lt $configHash.MinDurationMinutes -or
            $configHash.DefaultDurationMinutes -gt $configHash.MaxDurationMinutes) {
            throw "DefaultDurationMinutes ($($configHash.DefaultDurationMinutes)) must be between MinDurationMinutes ($($configHash.MinDurationMinutes)) and MaxDurationMinutes ($($configHash.MaxDurationMinutes))"
        }

        if ($PSCmdlet.ShouldProcess($script:ConfigFilePath, "Update configuration")) {
            $jsonContent = $configHash | ConvertTo-Json -Depth 10
            Set-Content -Path $script:ConfigFilePath -Value $jsonContent -Encoding UTF8 -Force
            Write-Verbose "Configuration saved to '$script:ConfigFilePath'"
        }
    }
    catch {
        throw "Failed to save configuration: $($_.Exception.Message)"
    }
}

function Test-MakeMeAdminConfig {
    <#
    .SYNOPSIS
        Validates the current configuration.

    .DESCRIPTION
        Checks that all configuration values are valid and returns a validation result.

    .OUTPUTS
        PSCustomObject with IsValid (bool) and Errors (array) properties.

    .EXAMPLE
        $result = Test-MakeMeAdminConfig
        if (-not $result.IsValid) {
            $result.Errors | ForEach-Object { Write-Error $_ }
        }
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param()

    $errors = @()
    $config = Get-MakeMeAdminConfig

    # Validate duration settings
    if ($config.MinDurationMinutes -lt 1) {
        $errors += "MinDurationMinutes must be at least 1"
    }
    if ($config.MaxDurationMinutes -lt 1) {
        $errors += "MaxDurationMinutes must be at least 1"
    }
    if ($config.MinDurationMinutes -gt $config.MaxDurationMinutes) {
        $errors += "MinDurationMinutes cannot exceed MaxDurationMinutes"
    }
    if ($config.DefaultDurationMinutes -lt $config.MinDurationMinutes) {
        $errors += "DefaultDurationMinutes cannot be less than MinDurationMinutes"
    }
    if ($config.DefaultDurationMinutes -gt $config.MaxDurationMinutes) {
        $errors += "DefaultDurationMinutes cannot exceed MaxDurationMinutes"
    }

    # Validate event log source
    if ([string]::IsNullOrWhiteSpace($config.EventLogSource)) {
        $errors += "EventLogSource cannot be empty"
    }

    # Validate pipe name
    if ([string]::IsNullOrWhiteSpace($config.PipeName)) {
        $errors += "PipeName cannot be empty"
    }

    # Validate task path
    if ([string]::IsNullOrWhiteSpace($config.TaskPath)) {
        $errors += "TaskPath cannot be empty"
    }

    return [PSCustomObject]@{
        IsValid = ($errors.Count -eq 0)
        Errors = $errors
        Config = $config
    }
}

function Get-StateFilePath {
    <#
    .SYNOPSIS
        Gets the path to the state file, ensuring the directory exists.

    .DESCRIPTION
        Returns the configured state file path and creates the parent directory if needed.

    .OUTPUTS
        String containing the full path to the state file.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param()

    $config = Get-MakeMeAdminConfig
    $stateFilePath = $config.StateFilePath

    if (-not $stateFilePath) {
        $stateFilePath = Join-Path $script:DefaultStateFolder "state.json"
    }

    # Ensure the directory exists
    $stateFolder = Split-Path -Parent $stateFilePath
    if (-not (Test-Path $stateFolder)) {
        try {
            New-Item -ItemType Directory -Path $stateFolder -Force | Out-Null
        }
        catch {
            Write-Warning "Failed to create state folder '$stateFolder': $($_.Exception.Message)"
        }
    }

    return $stateFilePath
}

function Test-UserAllowed {
    <#
    .SYNOPSIS
        Checks if a user is allowed to request admin rights based on configuration.

    .DESCRIPTION
        Evaluates the AllowedUsers and DeniedUsers lists to determine if a user
        can request temporary admin rights.

    .PARAMETER Username
        The username to check (in DOMAIN\User or User format).

    .PARAMETER UserSID
        The security identifier of the user.

    .OUTPUTS
        Boolean indicating whether the user is allowed.
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [string]$Username,

        [string]$UserSID
    )

    $config = Get-MakeMeAdminConfig

    # Check denied list first (takes precedence)
    if ($config.DeniedUsers -and $config.DeniedUsers.Count -gt 0) {
        foreach ($denied in $config.DeniedUsers) {
            if ($Username -like $denied -or $Username -eq $denied) {
                return $false
            }
            if ($UserSID -and ($UserSID -eq $denied)) {
                return $false
            }
        }
    }

    # If allowed list is empty, all users are allowed (except those denied)
    if (-not $config.AllowedUsers -or $config.AllowedUsers.Count -eq 0) {
        return $true
    }

    # Check allowed list
    foreach ($allowed in $config.AllowedUsers) {
        if ($Username -like $allowed -or $Username -eq $allowed) {
            return $true
        }
        if ($UserSID -and ($UserSID -eq $allowed)) {
            return $true
        }
    }

    return $false
}

function Get-ValidatedDuration {
    <#
    .SYNOPSIS
        Validates and returns a duration value within configured limits.

    .DESCRIPTION
        Takes a requested duration and ensures it falls within the configured
        minimum and maximum values. Returns the default duration if no value is provided.

    .PARAMETER RequestedDuration
        The duration in minutes requested by the user.

    .OUTPUTS
        Integer representing the validated duration in minutes.
    #>

    [CmdletBinding()]
    [OutputType([int])]
    param(
        [int]$RequestedDuration = 0
    )

    $config = Get-MakeMeAdminConfig

    if ($RequestedDuration -le 0) {
        return $config.DefaultDurationMinutes
    }

    if ($RequestedDuration -lt $config.MinDurationMinutes) {
        Write-Verbose "Requested duration $RequestedDuration is below minimum. Using $($config.MinDurationMinutes)."
        return $config.MinDurationMinutes
    }

    if ($RequestedDuration -gt $config.MaxDurationMinutes) {
        Write-Verbose "Requested duration $RequestedDuration exceeds maximum. Using $($config.MaxDurationMinutes)."
        return $config.MaxDurationMinutes
    }

    return $RequestedDuration
}

# Export module members (when dot-sourced from module)
if ($MyInvocation.MyCommand.ScriptBlock.Module) {
    Export-ModuleMember -Function @(
        'Get-MakeMeAdminConfig',
        'Set-MakeMeAdminConfig',
        'Test-MakeMeAdminConfig',
        'Get-StateFilePath',
        'Test-UserAllowed',
        'Get-ValidatedDuration'
    )
}