Public/Show-EFMenu.ps1

function Show-EFMenu {
    <#
    .SYNOPSIS
    Opens the guided EndpointForge console menu.
 
    .DESCRIPTION
    Provides a keyboard-friendly interactive menu over EndpointForge's object-based
    commands. The menu guides operators through assessment, detailed findings,
    remediation planning, safe WhatIf previews, scoped remediation, baseline selection
    and creation, and report export.
 
    Assessment and planning are read-only. Applying remediation requires an elevated
    session, selection of specific automatic controls, a fresh WhatIf preview, and an
    exact APPLY acknowledgement. EndpointForge never restarts the device automatically.
 
    The menu writes presentation text to the host and is silent on the success stream by
    default. Use PassThru to receive one session object when the menu closes. For
    unattended automation, call the underlying Get, Test, Export, and Invoke commands
    directly instead of using this interactive command.
 
    .PARAMETER Baseline
    The initial built-in baseline name, custom JSON path, or validated baseline object.
 
    .PARAMETER ReportDirectory
    The directory used by the Export session report action. It is created only when an
    export is requested. The default is EndpointForge Reports under Documents.
 
    .PARAMETER IncludeSoftware
    Includes installed software in assessments started from the menu.
 
    .PARAMETER MinimumFreeSpacePercent
    Sets the system-drive warning threshold for menu assessments.
 
    .PARAMETER MaximumUptimeDays
    Sets the uptime warning threshold for menu assessments.
 
    .PARAMETER NoColor
    Disables menu and dashboard colors. Color is also disabled when NO_COLOR is set or
    console output is redirected.
 
    .PARAMETER NoProgress
    Suppresses progress displays from assessment, planning, preview, and remediation.
 
    .PARAMETER NoPause
    Skips Press Enter pauses after actions. It never skips menu input, control selection,
    or the APPLY safety acknowledgement.
 
    .PARAMETER PassThru
    Returns one EndpointForge.MenuSession object after the menu closes.
 
    .EXAMPLE
    Show-EFMenu
 
    .EXAMPLE
    Show-EFMenu -Baseline .\Contoso.Workstation.json -IncludeSoftware
 
    .EXAMPLE
    $session = Show-EFMenu -NoColor -NoProgress -PassThru
 
    .OUTPUTS
    None by default. EndpointForge.MenuSession when PassThru is specified.
 
    .LINK
    Get-EFEndpointSummary
 
    .LINK
    Get-EFRemediationPlan
 
    .LINK
    Invoke-EFEndpointRemediation
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [AllowNull()]
        [object]$Baseline = 'EnterpriseRecommended',

        [string]$ReportDirectory,

        [switch]$IncludeSoftware,

        [ValidateRange(1, 99)]
        [int]$MinimumFreeSpacePercent = 15,

        [ValidateRange(1, 3650)]
        [int]$MaximumUptimeDays = 30,

        [switch]$NoColor,

        [switch]$NoProgress,

        [switch]$NoPause,

        [switch]$PassThru
    )

    $null = Test-EFWindows -Throw
    $startedAtUtc = [DateTime]::UtcNow
    $computerName = if ([string]::IsNullOrWhiteSpace($env:COMPUTERNAME)) { [Environment]::MachineName } else { $env:COMPUTERNAME }
    $width = Get-EFConsoleWidth
    $effectiveNoColor = [bool]$NoColor -or -not [string]::IsNullOrEmpty($env:NO_COLOR)
    try {
        $effectiveNoColor = $effectiveNoColor -or [Console]::IsOutputRedirected
    }
    catch {
        Write-Verbose 'Console redirection state is unavailable; using NoColor and NO_COLOR preferences.'
    }

    if ([string]::IsNullOrWhiteSpace($ReportDirectory)) {
        $documentsPath = [Environment]::GetFolderPath('MyDocuments')
        $reportRoot = if ([string]::IsNullOrWhiteSpace($documentsPath)) { (Get-Location).Path } else { $documentsPath }
        $ReportDirectory = Join-Path $reportRoot 'EndpointForge Reports'
    }
    $resolvedReportDirectory = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ReportDirectory)

    try {
        $resolvedBaseline = Resolve-EFBaseline -Baseline $Baseline
    }
    catch {
        throw [System.ArgumentException]::new("The initial baseline is invalid: $($_.Exception.Message)", $_.Exception)
    }
    $activeBaseline = $Baseline

    $history = [Collections.Generic.List[object]]::new()
    $menuErrors = [Collections.Generic.List[object]]::new()
    $lastSummary = $null
    $lastPlan = $null
    $lastPreview = $null
    $lastRemediation = $null
    $lastExportPath = $null
    $actionCount = 0
    $exitReason = 'Quit'
    $isAdministrator = Test-EFAdministrator
    $isRemoteSession = $false
    try {
        $senderInfo = Get-Variable -Name PSSenderInfo -ValueOnly -ErrorAction SilentlyContinue
        $isRemoteSession = $null -ne $senderInfo
    }
    catch {
        $isRemoteSession = $false
    }

    $addHistory = {
        param([string]$Action, [string]$Status, [string]$Message)
        $null = $history.Add([pscustomobject]@{
            PSTypeName = 'EndpointForge.MenuHistoryEntry'
            AtUtc      = [DateTime]::UtcNow
            Action     = $Action
            Status     = $Status
            Message    = $Message
        })
    }

    $running = $true
    :MenuLoop while ($running) {
        $isAdministrator = Test-EFAdministrator
        $lastAssessmentText = if ($null -eq $lastSummary) {
            'Not run'
        }
        else {
            "{0} at {1:u}" -f $lastSummary.OverallStatus, ([DateTime]$lastSummary.CompletedAtUtc)
        }

        Write-EFMenuLine -Text '' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text 'EndpointForge' -Color Cyan -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text 'Enterprise Windows endpoint automation' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text ('=' * [math]::Min(72, $width)) -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text ("Target: {0} ({1})" -f $computerName, $(if ($isRemoteSession) { 'remote session' } else { 'local endpoint' })) `
            -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text ("Access: {0}" -f $(if ($isAdministrator) { 'Administrator' } else { 'Standard user - assessment and preview available' })) `
            -Color $(if ($isAdministrator) { [ConsoleColor]::Green } else { [ConsoleColor]::Yellow }) `
            -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text ("Baseline: {0} {1}" -f $resolvedBaseline.Name, $resolvedBaseline.Version) -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text ("Last assessment: {0}" -f $lastAssessmentText) -NoColor:$effectiveNoColor -Width $width
        if ($isRemoteSession) {
            Write-EFMenuLine -Text '[REMOTE] Actions affect the endpoint named above, not your local workstation.' `
                -Color Yellow -NoColor:$effectiveNoColor -Width $width
        }

        Write-EFMenuLine -Text '' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text '1. Run or refresh assessment [read only]' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text '2. Review detailed findings [read only]' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text '3. Build remediation plan [read only]' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text '4. Preview selected automatic fixes [no changes]' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text '5. Apply selected automatic fixes [changes endpoint]' -Color Yellow -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text '6. Export current session report [writes JSON]' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text '7. Select a different baseline [clears cached results]' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text '8. Create a new baseline [writes JSON + schema]' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text 'H. Help and safety guide' -NoColor:$effectiveNoColor -Width $width
        Write-EFMenuLine -Text 'Q. Quit' -NoColor:$effectiveNoColor -Width $width

        $choice = Read-EFMenuInput -Prompt 'Choose an option'
        if ($null -eq $choice) {
            $exitReason = 'InputClosed'
            break MenuLoop
        }
        $normalizedChoice = $choice.Trim().ToUpperInvariant()
        $shouldPause = $true
        $currentAction = $normalizedChoice

        try {
            switch -Regex ($normalizedChoice) {
                '^(1|A|ASSESS|ASSESSMENT|REFRESH)$' {
                    $currentAction = 'Assessment'
                    $actionCount++
                    Write-EFMenuLine -Text 'Collecting endpoint evidence...' -Color Cyan -NoColor:$effectiveNoColor -Width $width
                    $summaryParameters = @{
                        Baseline                = $activeBaseline
                        IncludeSoftware         = [bool]$IncludeSoftware
                        MinimumFreeSpacePercent = $MinimumFreeSpacePercent
                        MaximumUptimeDays       = $MaximumUptimeDays
                        NoProgress              = [bool]$NoProgress
                    }
                    $lastSummary = Get-EFEndpointSummary @summaryParameters
                    $null = Show-EFEndpointSummary -InputObject $lastSummary -NoColor:$effectiveNoColor
                    & $addHistory 'Assessment' 'Completed' $lastSummary.OverallStatus
                    break
                }
                '^(2|D|DETAIL|DETAILS|FINDINGS)$' {
                    $currentAction = 'Detailed findings'
                    $actionCount++
                    if ($null -eq $lastSummary) {
                        Write-EFMenuLine -Text 'No cached assessment exists; collecting one now...' -Color Cyan -NoColor:$effectiveNoColor -Width $width
                        $summaryParameters = @{
                            Baseline                = $activeBaseline
                            IncludeSoftware         = [bool]$IncludeSoftware
                            MinimumFreeSpacePercent = $MinimumFreeSpacePercent
                            MaximumUptimeDays       = $MaximumUptimeDays
                            NoProgress              = [bool]$NoProgress
                        }
                        $lastSummary = Get-EFEndpointSummary @summaryParameters
                    }
                    $null = Show-EFEndpointSummary -InputObject $lastSummary -Detailed -NoColor:$effectiveNoColor
                    & $addHistory 'Detailed findings' 'Completed' "$($lastSummary.IssueCount) issue(s), $($lastSummary.UnknownCount) unknown"
                    break
                }
                '^(3|P|PLAN)$' {
                    $currentAction = 'Remediation plan'
                    $actionCount++
                    Write-EFMenuLine -Text 'Evaluating the active baseline...' -Color Cyan -NoColor:$effectiveNoColor -Width $width
                    $lastPlan = Get-EFRemediationPlan -Baseline $activeBaseline -NoProgress:$NoProgress
                    Write-EFMenuPlan -Plan $lastPlan -NoColor:$effectiveNoColor -Width $width
                    & $addHistory 'Remediation plan' 'Completed' $lastPlan.Summary
                    break
                }
                '^(4|V|PREVIEW)$' {
                    $currentAction = 'Remediation preview'
                    $actionCount++
                    $lastPlan = Get-EFRemediationPlan -Baseline $activeBaseline -NoProgress:$NoProgress
                    Write-EFMenuPlan -Plan $lastPlan -NoColor:$effectiveNoColor -Width $width
                    if ([int]$lastPlan.AutomaticCount -eq 0) {
                        Write-EFMenuLine -Text '[OK] There are no automatic changes to preview.' -Color Green -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Remediation preview' 'NotRequired' 'No automatic changes were available.'
                        break
                    }
                    $selectedControlIds = @(Select-EFMenuControlId -Steps $lastPlan.Steps -NoColor:$effectiveNoColor -Width $width)
                    if ($selectedControlIds.Count -eq 0) {
                        Write-EFMenuLine -Text '[CANCELLED] No controls were selected.' -Color Yellow -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Remediation preview' 'Cancelled' 'No controls were selected.'
                        break
                    }
                    $previewParameters = @{
                        Baseline   = $activeBaseline
                        ControlId  = $selectedControlIds
                        NoProgress = [bool]$NoProgress
                        WhatIf     = $true
                        Confirm    = $false
                    }
                    $lastPreview = Invoke-EFEndpointRemediation @previewParameters
                    Write-EFMenuRemediationReport -Report $lastPreview -NoColor:$effectiveNoColor -Width $width
                    & $addHistory 'Remediation preview' 'Completed' "$($selectedControlIds.Count) control(s) previewed."
                    break
                }
                '^(5|F|FIX|APPLY)$' {
                    $currentAction = 'Apply remediation'
                    $actionCount++
                    $lastPlan = Get-EFRemediationPlan -Baseline $activeBaseline -NoProgress:$NoProgress
                    Write-EFMenuPlan -Plan $lastPlan -NoColor:$effectiveNoColor -Width $width
                    if ([int]$lastPlan.AutomaticCount -eq 0) {
                        Write-EFMenuLine -Text '[OK] There are no automatic changes to apply.' -Color Green -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Apply remediation' 'NotRequired' 'No automatic changes were available.'
                        break
                    }
                    if (-not (Test-EFAdministrator)) {
                        Write-EFMenuLine -Text '[BLOCKED] Applying changes requires an elevated PowerShell session. Assessment, planning, and WhatIf preview remain available.' `
                            -Color Red -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Apply remediation' 'Blocked' 'Administrator access is required.'
                        break
                    }
                    $selectedControlIds = @(Select-EFMenuControlId -Steps $lastPlan.Steps -NoColor:$effectiveNoColor -Width $width)
                    if ($selectedControlIds.Count -eq 0) {
                        Write-EFMenuLine -Text '[CANCELLED] No controls were selected.' -Color Yellow -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Apply remediation' 'Cancelled' 'No controls were selected.'
                        break
                    }

                    Write-EFMenuLine -Text 'Running the required WhatIf preview first...' -Color Cyan -NoColor:$effectiveNoColor -Width $width
                    $previewParameters = @{
                        Baseline   = $activeBaseline
                        ControlId  = $selectedControlIds
                        NoProgress = [bool]$NoProgress
                        WhatIf     = $true
                        Confirm    = $false
                    }
                    $lastPreview = Invoke-EFEndpointRemediation @previewParameters
                    Write-EFMenuRemediationReport -Report $lastPreview -NoColor:$effectiveNoColor -Width $width
                    if ([int]$lastPreview.FailureCount -gt 0) {
                        Write-EFMenuLine -Text '[BLOCKED] The preview reported failures. Resolve them before applying changes.' `
                            -Color Red -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Apply remediation' 'Blocked' 'The required preview reported failures.'
                        break
                    }

                    $selectedSteps = @($lastPlan.Steps | Where-Object { $_.ControlId -in $selectedControlIds })
                    $selectedRebootCount = @($selectedSteps | Where-Object RequiresReboot).Count
                    Write-EFMenuLine -Text '' -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'CHANGE CONFIRMATION' -Color Red -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text ("Target: {0}" -f $computerName) -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text ("Baseline: {0} {1}" -f $resolvedBaseline.Name, $resolvedBaseline.Version) -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text ("Selected automatic controls: {0}" -f ($selectedControlIds -join ', ')) -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text ("Restart-impacting controls: {0}. EndpointForge will not restart the device." -f $selectedRebootCount) `
                        -Color Yellow -NoColor:$effectiveNoColor -Width $width
                    $acknowledgement = Read-EFMenuInput -Prompt 'Type APPLY to make these changes; anything else cancels'
                    if ($null -eq $acknowledgement -or $acknowledgement.Trim() -cne 'APPLY') {
                        Write-EFMenuLine -Text '[CANCELLED] Endpoint state was not changed.' -Color Yellow -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Apply remediation' 'Cancelled' 'The APPLY acknowledgement was not entered.'
                        break
                    }

                    $applyParameters = @{
                        Baseline   = $activeBaseline
                        ControlId  = $selectedControlIds
                        NoProgress = [bool]$NoProgress
                        Confirm    = $false
                    }
                    $lastRemediation = Invoke-EFEndpointRemediation @applyParameters
                    Write-EFMenuRemediationReport -Report $lastRemediation -NoColor:$effectiveNoColor -Width $width
                    & $addHistory 'Apply remediation' 'Completed' $lastRemediation.Summary

                    Write-EFMenuLine -Text 'Verifying endpoint state with a fresh assessment...' -Color Cyan -NoColor:$effectiveNoColor -Width $width
                    $summaryParameters = @{
                        Baseline                = $activeBaseline
                        IncludeSoftware         = [bool]$IncludeSoftware
                        MinimumFreeSpacePercent = $MinimumFreeSpacePercent
                        MaximumUptimeDays       = $MaximumUptimeDays
                        NoProgress              = [bool]$NoProgress
                    }
                    $lastSummary = Get-EFEndpointSummary @summaryParameters
                    $lastPlan = $null
                    $null = Show-EFEndpointSummary -InputObject $lastSummary -NoColor:$effectiveNoColor
                    break
                }
                '^(6|E|EXPORT)$' {
                    $currentAction = 'Export session report'
                    $actionCount++
                    if ($null -eq $lastSummary -and $null -eq $lastPlan -and $null -eq $lastPreview -and $null -eq $lastRemediation) {
                        Write-EFMenuLine -Text '[NOT READY] Run an assessment, plan, or preview before exporting.' -Color Yellow -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Export session report' 'NotReady' 'There were no cached results to export.'
                        break
                    }
                    $reportBundle = [pscustomobject][ordered]@{
                        PSTypeName       = 'EndpointForge.MenuReport'
                        SchemaVersion    = '1.0'
                        ExportedAtUtc    = [DateTime]::UtcNow
                        ComputerName     = $computerName
                        BaselineName     = [string]$resolvedBaseline.Name
                        BaselineVersion  = [string]$resolvedBaseline.Version
                        Summary          = $lastSummary
                        Plan             = $lastPlan
                        Preview          = $lastPreview
                        Remediation      = $lastRemediation
                        SessionHistory   = @($history)
                    }
                    $timestamp = [DateTime]::UtcNow.ToString('yyyyMMdd-HHmmssfff')
                    $fileName = "EndpointForge-$computerName-$timestamp.json"
                    $exportPath = Join-Path $resolvedReportDirectory $fileName
                    $createdFile = $reportBundle | Export-EFEndpointReport -Path $exportPath -PassThru
                    $lastExportPath = $createdFile.FullName
                    Write-EFMenuLine -Text ("[EXPORTED] {0}" -f $lastExportPath) -Color Green -NoColor:$effectiveNoColor -Width $width
                    & $addHistory 'Export session report' 'Completed' $lastExportPath
                    break
                }
                '^(7|B|BASELINE|SELECT)$' {
                    $currentAction = 'Select baseline'
                    $actionCount++
                    Write-EFMenuLine -Text '' -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'Available built-in baselines' -Color Cyan -NoColor:$effectiveNoColor -Width $width
                    foreach ($availableBaseline in @(Get-EFBaseline -ListAvailable)) {
                        Write-EFMenuLine -Text ("- {0} {1}: {2}" -f $availableBaseline.Name, $availableBaseline.Version, $availableBaseline.Description) `
                            -NoColor:$effectiveNoColor -Width $width -Indent 2
                    }
                    $baselineSelection = Read-EFMenuInput -Prompt 'Enter a built-in name or custom JSON path; press Enter to cancel'
                    if ($null -eq $baselineSelection -or [string]::IsNullOrWhiteSpace($baselineSelection)) {
                        Write-EFMenuLine -Text '[CANCELLED] The active baseline was not changed.' -Color Yellow -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Select baseline' 'Cancelled' 'No baseline was entered.'
                        break
                    }
                    $candidateBaseline = Resolve-EFBaseline -Baseline $baselineSelection.Trim()
                    $activeBaseline = $baselineSelection.Trim()
                    $resolvedBaseline = $candidateBaseline
                    $lastSummary = $null
                    $lastPlan = $null
                    $lastPreview = $null
                    $lastRemediation = $null
                    Write-EFMenuLine -Text ("[SELECTED] {0} {1}. Cached assessment and remediation results were cleared." -f `
                        $resolvedBaseline.Name, $resolvedBaseline.Version) -Color Green -NoColor:$effectiveNoColor -Width $width
                    & $addHistory 'Select baseline' 'Completed' "$($resolvedBaseline.Name) $($resolvedBaseline.Version)"
                    break
                }
                '^(8|N|NEW|CREATE)$' {
                    $currentAction = 'Create baseline'
                    $actionCount++
                    Write-EFMenuLine -Text '' -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'Create a baseline' -Color Cyan -NoColor:$effectiveNoColor -Width $width
                    $newName = Read-EFMenuInput -Prompt 'Baseline name (for example Contoso.Workstation); press Enter to cancel'
                    if ($null -eq $newName -or [string]::IsNullOrWhiteSpace($newName)) {
                        Write-EFMenuLine -Text '[CANCELLED] No baseline was created.' -Color Yellow -NoColor:$effectiveNoColor -Width $width
                        & $addHistory 'Create baseline' 'Cancelled' 'No name was entered.'
                        break
                    }
                    $newDescription = Read-EFMenuInput -Prompt 'Description (optional)'
                    $newVersion = Read-EFMenuInput -Prompt 'Version [1.0.0]'
                    if ([string]::IsNullOrWhiteSpace($newVersion)) { $newVersion = '1.0.0' }
                    Write-EFMenuLine -Text 'Template: 1 Starter, 2 EnterpriseRecommended, 3 AuditOnly' -NoColor:$effectiveNoColor -Width $width
                    $templateChoice = Read-EFMenuInput -Prompt 'Template [1]'
                    $newTemplate = switch (([string]$templateChoice).Trim().ToUpperInvariant()) {
                        '' { 'Starter' }
                        '1' { 'Starter' }
                        'STARTER' { 'Starter' }
                        '2' { 'EnterpriseRecommended' }
                        'ENTERPRISERECOMMENDED' { 'EnterpriseRecommended' }
                        '3' { 'AuditOnly' }
                        'AUDITONLY' { 'AuditOnly' }
                        default { throw [System.ArgumentException]::new("Unknown template '$templateChoice'. Choose 1, 2, or 3.") }
                    }
                    $defaultBaselinePath = Join-Path (Join-Path $resolvedReportDirectory 'Baselines') ("{0}.json" -f $newName.Trim())
                    $newPath = Read-EFMenuInput -Prompt "Output path [$defaultBaselinePath]"
                    if ([string]::IsNullOrWhiteSpace($newPath)) { $newPath = $defaultBaselinePath }
                    $newBaselineParameters = @{
                        Name        = $newName.Trim()
                        Version     = $newVersion.Trim()
                        Template    = $newTemplate
                        Path        = $newPath.Trim()
                    }
                    if (-not [string]::IsNullOrWhiteSpace($newDescription)) {
                        $newBaselineParameters.Description = $newDescription.Trim()
                    }
                    $createdBaseline = New-EFBaseline @newBaselineParameters
                    $activeBaseline = $createdBaseline.Path
                    $resolvedBaseline = Resolve-EFBaseline -Baseline $activeBaseline
                    $lastSummary = $null
                    $lastPlan = $null
                    $lastPreview = $null
                    $lastRemediation = $null
                    Write-EFMenuLine -Text ("[CREATED] {0}" -f $createdBaseline.Path) -Color Green -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text ("Schema: {0}" -f $createdBaseline.SchemaPath) -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'The new baseline is now active. Review it before planning or applying changes.' -Color Yellow -NoColor:$effectiveNoColor -Width $width
                    & $addHistory 'Create baseline' 'Completed' $createdBaseline.Path
                    break
                }
                '^(H|HELP|\?)$' {
                    $currentAction = 'Help'
                    $actionCount++
                    Write-EFMenuLine -Text '' -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'Safety guide' -Color Cyan -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text '1-3 only read endpoint state. Option 4 uses PowerShell WhatIf and cannot change state.' -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'Option 5 applies only the controls you select. It requires Administrator access, always runs a fresh preview, and requires you to type APPLY exactly.' -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'EndpointForge never restarts the endpoint. A result may tell you that a restart should be scheduled through your normal change process.' -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'Options 6 and 8 write files. Selecting or creating a baseline clears cached results so they cannot be mistaken for results from the new baseline.' -NoColor:$effectiveNoColor -Width $width
                    Write-EFMenuLine -Text 'For automation, leave the menu and use Get-EFEndpointSummary, Get-EFRemediationPlan, Export-EFEndpointReport, and Invoke-EFEndpointRemediation directly.' -NoColor:$effectiveNoColor -Width $width
                    & $addHistory 'Help' 'Viewed' 'Safety guide displayed.'
                    break
                }
                '^(Q|QUIT|EXIT)$' {
                    $exitReason = 'Quit'
                    $running = $false
                    $shouldPause = $false
                    break
                }
                default {
                    Write-EFMenuLine -Text '[INVALID] Choose 1-8, H, or Q.' -Color Yellow -NoColor:$effectiveNoColor -Width $width
                    $shouldPause = $false
                }
            }
        }
        catch [System.Management.Automation.PipelineStoppedException] {
            throw
        }
        catch {
            $errorRecord = [pscustomobject]@{
                PSTypeName = 'EndpointForge.MenuError'
                AtUtc      = [DateTime]::UtcNow
                Action     = $currentAction
                Message    = $_.Exception.Message
                ErrorId    = $_.FullyQualifiedErrorId
            }
            $null = $menuErrors.Add($errorRecord)
            & $addHistory $currentAction 'Failed' $_.Exception.Message
            Write-EFMenuLine -Text ("[ERROR] {0}" -f $_.Exception.Message) -Color Red -NoColor:$effectiveNoColor -Width $width
            Write-EFMenuLine -Text 'The menu is still active. Correct the issue or choose another action.' -NoColor:$effectiveNoColor -Width $width
        }

        if ($shouldPause -and -not $NoPause -and $running) {
            $pauseInput = Read-EFMenuInput -Prompt 'Press Enter to return to the menu'
            if ($null -eq $pauseInput) {
                $exitReason = 'InputClosed'
                break MenuLoop
            }
        }
    }

    if ($PassThru) {
        [pscustomobject]@{
            PSTypeName       = 'EndpointForge.MenuSession'
            ComputerName     = $computerName
            StartedAtUtc     = $startedAtUtc
            CompletedAtUtc   = [DateTime]::UtcNow
            ExitReason       = $exitReason
            IsRemoteSession  = $isRemoteSession
            IsAdministrator  = $isAdministrator
            BaselineName     = [string]$resolvedBaseline.Name
            BaselineVersion  = [string]$resolvedBaseline.Version
            BaselinePath     = [string](Get-EFPropertyValue -InputObject $resolvedBaseline -Name 'SourcePath')
            ReportDirectory  = $resolvedReportDirectory
            ActionCount      = $actionCount
            ErrorCount       = $menuErrors.Count
            LastExportPath   = $lastExportPath
            LastSummary      = $lastSummary
            LastPlan         = $lastPlan
            LastPreview      = $lastPreview
            LastRemediation  = $lastRemediation
            History          = @($history)
            Errors           = @($menuErrors)
        }
    }
}