Private/Invoke-AsUser.ps1

function Invoke-AsUser {
    <#
    .SYNOPSIS
        Executes a script string as the currently logged-on user via CreateProcessAsUser.
    .DESCRIPTION
        Wraps the RunAsUser C# engine with Indago defaults:
        - Hidden window (no user-visible flash)
        - Elevated token (admin rights without password)
        - Output capture via stdout pipe
        - RMM job breakaway support (CREATE_BREAKAWAY_FROM_JOB)
        - Base64-encoded command (avoids file access and quoting issues)
    .PARAMETER ScriptText
        The PowerShell script to execute as the logged-on user.
    .PARAMETER TimeoutMs
        Maximum milliseconds to wait for the script to complete.
        Default: 300000 (5 minutes). Use -1 for infinite wait.
    .OUTPUTS
        [string] The captured stdout from the user-context process.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ScriptText,

        [Parameter(Mandatory = $false)]
        [int]$TimeoutMs = 300000
    )

    #region Verify C# type is loaded
    if (-not $script:IndagoState.TypeLoaded) {
        Write-Error 'Invoke-AsUser: The RunAsUser C# type is not loaded. User-context execution is unavailable.'
        return $null
    }
    #endregion

    #region Verify a user is logged on
    $loggedOnUser = Resolve-LoggedOnUser
    if ($null -eq $loggedOnUser) {
        Write-Error 'Invoke-AsUser: No interactive user session detected. This task requires a logged-on user.'
        return $null
    }
    Write-Verbose "Invoke-AsUser: Target user: $($loggedOnUser.FullName)"
    #endregion

    #region Encode and execute
    try {
        $encodedCommand = [Convert]::ToBase64String(
            [System.Text.Encoding]::Unicode.GetBytes($ScriptText)
        )
        $pwshPath = "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe"
        $pwshArgs = "-ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -EncodedCommand $encodedCommand"

        # Check command line length limit
        $maxLength = 32767
        if ($pwshArgs.Length -gt $maxLength) {
            Write-Verbose 'Invoke-AsUser: Encoded command exceeds command line limit. Using CacheToDisk mode.'
            return Invoke-AsUserCacheToDisk -ScriptText $ScriptText -TimeoutMs $TimeoutMs -LoggedOnUser $loggedOnUser
        }

        Write-Verbose "Invoke-AsUser: Executing as $($loggedOnUser.FullName) (hidden, elevated, breakaway)"

        $output = [RunAsUser.ProcessExtensions]::StartProcessAsCurrentUser(
            $pwshPath,                              # appPath
            "`"$pwshPath`" $pwshArgs",              # cmdLine
            (Split-Path $pwshPath -Parent),         # workDir
            $false,                                 # visible = hidden
            $TimeoutMs,                             # wait
            $true,                                  # elevated = admin token
            $true,                                  # redirectOutput = capture stdout
            $true                                   # breakaway = RMM job compat
        )

        return $output
    }
    catch {
        Write-Error "Invoke-AsUser: Failed to execute as user. Error: $($_.Exception.Message)"
        return $null
    }
    #endregion
}

function Invoke-AsUserCacheToDisk {
    <#
    .SYNOPSIS
        Fallback for scripts that exceed the command line length limit.
    .DESCRIPTION
        Writes the script to a user-accessible temp file, executes it via
        -File parameter, then cleans up.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ScriptText,

        [Parameter(Mandatory = $false)]
        [int]$TimeoutMs = 300000,

        [Parameter(Mandatory = $true)]
        [PSCustomObject]$LoggedOnUser
    )

    $tempDir = Join-Path -Path $env:SystemRoot -ChildPath 'Temp'
    $scriptGuid = [guid]::NewGuid().ToString('N')
    $tempFile = Join-Path -Path $tempDir -ChildPath "$scriptGuid.ps1"

    try {
        Set-Content -Path $tempFile -Value $ScriptText -Encoding UTF8 -Force -ErrorAction Stop

        # Grant user read+execute on the temp file
        try {
            $acl = Get-Acl -Path $tempFile -ErrorAction Stop
            $rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
                $LoggedOnUser.FullName, 'ReadAndExecute', 'Allow'
            )
            $acl.AddAccessRule($rule)
            Set-Acl -Path $tempFile -AclObject $acl -ErrorAction Stop
        }
        catch {
            Write-Warning "Invoke-AsUserCacheToDisk: Could not set ACL on temp file: $($_.Exception.Message)"
        }

        $pwshPath = "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe"
        $pwshArgs = "-ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -File `"$tempFile`""

        $output = [RunAsUser.ProcessExtensions]::StartProcessAsCurrentUser(
            $pwshPath,
            "`"$pwshPath`" $pwshArgs",
            (Split-Path $pwshPath -Parent),
            $false,
            $TimeoutMs,
            $true,
            $true,
            $true
        )

        return $output
    }
    catch {
        Write-Error "Invoke-AsUserCacheToDisk: Failed. Error: $($_.Exception.Message)"
        return $null
    }
    finally {
        if (Test-Path -Path $tempFile) {
            Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue
        }
    }
}