Public/Get-EFRemediationPlan.ps1

function Get-EFRemediationPlan {
    <#
    .SYNOPSIS
    Gets a read-only remediation plan for the local endpoint.
 
    .DESCRIPTION
    Evaluates selected controls and separates automatic changes, manual actions,
    blockers, compliant controls, and non-applicable controls. It never changes endpoint
    state and should be reviewed before Invoke-EFEndpointRemediation.
 
    .PARAMETER Baseline
    A built-in baseline name, JSON path, or validated baseline object.
 
    .PARAMETER ControlId
    Limits the plan to selected control identifiers.
 
    .PARAMETER IncludeCompliant
    Includes compliant and non-applicable steps in the Steps collection.
 
    .PARAMETER NoProgress
    Suppresses the compliance progress display.
 
    .EXAMPLE
    Get-EFRemediationPlan
 
    .EXAMPLE
    Get-EFRemediationPlan -ControlId EF-UAC-ENABLED -IncludeCompliant -NoProgress
 
    .OUTPUTS
    EndpointForge.RemediationPlan
    #>

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

        [string[]]$ControlId,

        [switch]$IncludeCompliant,

        [switch]$NoProgress
    )

    $resolvedBaseline = Resolve-EFBaseline -Baseline $Baseline
    $reportParameters = @{
        Baseline   = $resolvedBaseline
        NoProgress = $NoProgress
    }
    if ($PSBoundParameters.ContainsKey('ControlId')) {
        $reportParameters.ControlId = $ControlId
    }
    $compliance = Get-EFComplianceReport @reportParameters

    $controlsById = @{}
    $baselineCommandArgument = Get-EFBaselineCommandArgument -Baseline $resolvedBaseline
    foreach ($control in @($resolvedBaseline.Controls)) {
        $controlsById[[string]$control.Id] = $control
    }

    $allSteps = @(
        foreach ($result in @($compliance.Results)) {
            $control = $controlsById[[string]$result.ControlId]
            $isRemediable = [bool](Get-EFPropertyValue -InputObject $control -Name 'Remediable' -Default $false)
            $requiresReboot = [bool](Get-EFPropertyValue -InputObject $control -Name 'RequiresReboot' -Default $false)
            $action = switch ([string]$result.Status) {
                'Compliant' { 'NoAction' }
                'NotApplicable' { 'NotApplicable' }
                'Error' { 'Blocked' }
                'NonCompliant' { if ($isRemediable) { 'Automatic' } else { 'Manual' } }
            }
            $commandPreview = if ($action -eq 'Automatic' -and $null -ne $baselineCommandArgument) {
                "Invoke-EFEndpointRemediation $baselineCommandArgument -ControlId '$($result.ControlId)' -WhatIf"
            }
            else { $null }

            [pscustomobject]@{
                PSTypeName        = 'EndpointForge.RemediationPlanStep'
                ControlId         = [string]$result.ControlId
                Title             = [string]$result.Title
                Type              = [string](Get-EFPropertyValue -InputObject $control -Name 'Type')
                Severity          = [string]$result.Severity
                CurrentStatus     = [string]$result.Status
                CurrentValue      = $result.ActualValue
                DesiredValue      = $result.DesiredValue
                Action            = $action
                RequiresElevation = $action -eq 'Automatic'
                RequiresReboot    = $requiresReboot
                Message           = [string]$result.Message
                RecommendedAction = [string]$result.RecommendedAction
                CommandPreview    = $commandPreview
            }
        }
    )

    $automaticCount = @($allSteps | Where-Object Action -eq 'Automatic').Count
    $manualCount = @($allSteps | Where-Object Action -eq 'Manual').Count
    $blockedCount = @($allSteps | Where-Object Action -eq 'Blocked').Count
    $noActionCount = @($allSteps | Where-Object Action -eq 'NoAction').Count
    $notApplicableCount = @($allSteps | Where-Object Action -eq 'NotApplicable').Count
    $candidateCount = $automaticCount + $manualCount
    $planSteps = if ($IncludeCompliant) {
        $allSteps
    }
    else {
        @($allSteps | Where-Object Action -notin @('NoAction', 'NotApplicable'))
    }
    $summary = if ($blockedCount -gt 0) {
        "$candidateCount remediation candidate(s); $blockedCount control(s) are blocked by evaluation errors."
    }
    elseif ($candidateCount -gt 0) {
        "$automaticCount automatic change(s) and $manualCount manual action(s) are recommended."
    }
    else {
        'No remediation candidates were found.'
    }
    $nextStep = if ($blockedCount -gt 0) {
        'Resolve blocked evaluations, often by running elevated, then generate the plan again.'
    }
    elseif ($automaticCount -gt 0) {
        'Preview automatic changes with Invoke-EFEndpointRemediation -WhatIf.'
    }
    elseif ($manualCount -gt 0) {
        'Review manual actions and apply them through your approved enterprise process.'
    }
    else {
        'No action is required.'
    }

    [pscustomobject]@{
        PSTypeName         = 'EndpointForge.RemediationPlan'
        ComputerName       = $env:COMPUTERNAME
        BaselineName       = [string]$resolvedBaseline.Name
        BaselineVersion    = [string]$resolvedBaseline.Version
        CreatedAtUtc       = [DateTime]::UtcNow
        IsReady            = $blockedCount -eq 0
        RequiresElevation  = $automaticCount -gt 0
        PotentialReboot    = @($allSteps | Where-Object { $_.Action -eq 'Automatic' -and $_.RequiresReboot }).Count -gt 0
        CandidateCount     = $candidateCount
        WouldChangeCount   = $automaticCount
        AutomaticCount     = $automaticCount
        ManualCount        = $manualCount
        BlockedCount       = $blockedCount
        NoActionCount      = $noActionCount
        NotApplicableCount = $notApplicableCount
        ExitCode           = if ($blockedCount -gt 0) { 3 } elseif ($candidateCount -gt 0) { 2 } else { 0 }
        Summary            = $summary
        NextStep           = $nextStep
        Steps              = @($planSteps)
        Compliance         = $compliance
    }
}