Private/Invoke-IslScriptRun.ps1

function Invoke-IslScriptRun {
    <#
    .SYNOPSIS
        Runs one script the way the Intune Management Extension does and captures the raw result.
 
    .DESCRIPTION
        Observed launch (PS-PROBE-*, REM-PROBE-*, Win32 log): AgentExecutor starts
        powershell.exe -NoProfile -ExecutionPolicy Bypass -File <script> with no -NonInteractive,
        working directory C:\WINDOWS\system32, the script copied to a cache folder, stdout and
        stderr redirected to files, and kills the process at the timeout. Console output goes
        through the OEM code page, which is what mangles non-ASCII in Intune's reports.
 
        Context System runs the same command through a scheduled task as NT AUTHORITY\SYSTEM in
        session 0, like the agent. User runs it directly, or, with -Credential, through a scheduled
        task as that account (in its own session when it has one, the way the agent runs
        user-context scripts as the signed-in user: REM-PROBE-USER64). For another account the
        cache lives under ProgramData rather than the caller's temp folder, with that account
        granted Modify on it, since the account has to read the copy and write its output there.
 
        Deviation: stdin is an empty stream rather than a hidden console, so a prompt returns
        immediately instead of hanging for the timeout. Test-IntuneScript's IslInteractiveCall
        rule is the check for that.
    #>

    [CmdletBinding()]
    [OutputType('IntuneScriptLab.RunResult')]
    param(
        [Parameter(Mandatory)]
        [string]$Path,

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

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

        # Run as this account (Context User); needs an elevated session
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential,

        [ValidateSet('Auto', 'Interactive', 'Password')]
        [string]$LogonType = 'Auto',

        # What the run is, for the result and the cache file name: detect, remediate, script
        [string]$Phase = 'script',

        [ValidateRange(1, 86400)]
        [int]$TimeoutSeconds = 300,

        [string]$WorkingDirectory = (Join-Path -Path $env:WINDIR -ChildPath 'System32')
    )

    $hostInfo = Get-IslHostPath -Architecture $Architecture
    $source = (Resolve-Path -LiteralPath $Path -ErrorAction Stop).ProviderPath

    # Like IMECache: the script runs from a copy, so $PSScriptRoot is not the source folder
    $runId = [guid]::NewGuid().ToString('N')
    $cache = if ($Credential) {
        Join-Path -Path $env:ProgramData -ChildPath "IntuneScriptLab\Runs\$runId"
    }
    else {
        Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "IntuneScriptLab\$runId"
    }
    $null = New-Item -ItemType Directory -Path $cache -Force
    $scriptCopy = Join-Path -Path $cache -ChildPath "$Phase.ps1"
    Copy-Item -LiteralPath $source -Destination $scriptCopy

    try {
        $processSplat = @{
            FilePath         = $hostInfo.Path
            Arguments        = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptCopy`""
            WorkingDirectory = $WorkingDirectory
            WorkFolder       = $cache
            Context          = $Context
            TimeoutSeconds   = $TimeoutSeconds
        }
        if ($Credential) {
            Grant-IslFolderAccess -Path $cache -Account $Credential.UserName
            $processSplat.Credential = $Credential
            $processSplat.LogonType = $LogonType
        }
        $run = Invoke-IslProcess @processSplat
    }
    finally {
        Remove-Item -LiteralPath $cache -Recurse -Force -ErrorAction SilentlyContinue
    }

    [pscustomobject]@{
        PSTypeName   = 'IntuneScriptLab.RunResult'
        Phase        = $Phase
        ScriptPath   = $source
        Host         = $hostInfo.Path
        Architecture = $Architecture
        Context      = $Context
        RunAs        = "$($run.UserName) ($($run.LogonType))"
        UserName     = $run.UserName
        LogonType    = $run.LogonType
        ExitCode     = $run.ExitCode
        TimedOut     = $run.TimedOut
        StdOut       = $run.StdOut
        StdErr       = $run.StdErr
        Duration     = $run.Duration
    }
}