Public/Invoke-IntuneRemediationTest.ps1

function Invoke-IntuneRemediationTest {
    <#
    .EXTERNALHELP IntuneScriptLab-Help.xml
    .SYNOPSIS
        Runs a detection/remediation pair like Intune and reports the portal status.
    #>

    [CmdletBinding()]
    [OutputType('IntuneScriptLab.RemediationResult')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$DetectionPath,

        [Parameter(Position = 1)]
        [string]$RemediationPath,

        [ValidateSet('x86', 'x64', 'arm64')]
        [string]$Architecture = 'x86',

        [ValidateSet('User', 'System')]
        [string]$Context = 'User',

        # Run as this account instead of the current user (with -Context User): a scheduled task in
        # the account's own session, the way the agent runs user-context scripts as the signed-in
        # user; needs an elevated session
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [ValidateRange(1, 86400)]
        [int]$TimeoutSeconds = 300
    )
    Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')"

    $warnings = [System.Collections.Generic.List[string]]::new()
    $scriptRunSplat = @{
        Architecture   = $Architecture
        Context        = $Context
        TimeoutSeconds = $TimeoutSeconds
    }
    if ($Credential) {
        if ($Context -eq 'System') {
            throw '-Credential applies to -Context User; System runs as NT AUTHORITY\SYSTEM'
        }
        $scriptRunSplat.Credential = $Credential
    }
    $pre = Invoke-IslScriptRun -Path $DetectionPath -Phase 'detect' @scriptRunSplat
    $remediation = $null
    $post = $null

    if ($pre.TimedOut) {
        $status = 'TimedOut'
    }
    elseif ($pre.ExitCode -eq 0) {
        $status = 'Without issues'
    }
    else {
        if ($pre.ExitCode -ne 1) {
            $warnings.Add("Detection exited $($pre.ExitCode): Intune treats any non-zero exit as 'issue found' " +
                "(observed with 2 and -1); use 1 to be explicit")
        }
        if (-not $RemediationPath) {
            $status = 'Issue detected (no remediation script)'
        }
        else {
            $remediation = Invoke-IslScriptRun -Path $RemediationPath -Phase 'remediate' @scriptRunSplat
            if ($remediation.TimedOut) { $status = 'TimedOut' }
            elseif ($remediation.ExitCode -ne 0) { $status = 'Failed' }
            else {
                $post = Invoke-IslScriptRun -Path $DetectionPath -Phase 'detect' @scriptRunSplat
                $status = if ($post.TimedOut) { 'TimedOut' }
                elseif ($post.ExitCode -eq 0) { 'Fixed' }
                else { 'Recurred' }
            }
        }
    }

    $intune = Get-IslIntuneOutput -StdOut $pre.StdOut -StdErr $pre.StdErr
    if ($intune.DroppedLines -gt 0) { $warnings.Add("Detection wrote $($intune.DroppedLines + 1) lines; Intune " +
        "reports only the last one") }
    if ($intune.OutputTruncated) { $warnings.Add('Detection output exceeds 2,048 characters; Intune keeps the ' +
        'last 2,048') }
    if ($intune.ErrorTruncated) { $warnings.Add('Detection error output exceeds 2,048 characters; Intune keeps ' +
        'the last 2,048') }
    if ($pre.StdOut -match '[^\x00-\x7F]') { $warnings.Add('Non-ASCII in output: Intune reports it through the ' +
        'OEM code page (see IntuneOutput for the effect)') }
    if ($status -eq 'Without issues' -and $pre.StdErr) { $warnings.Add('Detection wrote to stderr but exited 0; ' +
        'Intune shows the error text but still reports "without issues"') }

    $postIntune = if ($post) { Get-IslIntuneOutput -StdOut $post.StdOut -StdErr $post.StdErr } else { $null }
    $remIntune = if ($remediation) {
        Get-IslIntuneOutput -StdOut $remediation.StdOut -StdErr $remediation.StdErr
    }
    else { $null }

    Write-Verbose "Completed $($MyInvocation.MyCommand.Name)"
    [pscustomobject]@{
        PSTypeName         = 'IntuneScriptLab.RemediationResult'
        Status             = $status
        IntuneOutput       = $intune.Output
        IntuneError        = $intune.Error
        RemediationOutput  = if ($remIntune) { $remIntune.Output } else { $null }
        PostOutput         = if ($postIntune) { $postIntune.Output } else { $null }
        PreDetection       = $pre
        Remediation        = $remediation
        PostDetection      = $post
        Warnings           = $warnings.ToArray()
        Architecture       = $Architecture
        Context            = $Context
        RunAs              = $pre.RunAs
        Host               = $pre.Host
    }
}