Home > Back-end >  Catch output from function running as different user
Catch output from function running as different user

Time:11-01

function Get-WSLIP {
wsl -- ip -o -4 -json addr list eth0 `
| ConvertFrom-Json `
| %{ $_.addr_info.local } `
| ?{ $_ }
}

I have this function I want to run as a different user where WSL is installed and catch the output by assigning this function to a variable.

I've tried runas, but it seems it wants to open a second process and I was unable to catch the output.

CodePudding user response:

Launching a process with a different user identity invariably launches the process in a new window on Windows, which means that you cannot directly capture that process' output.

You have two options:

  • Use the System.Diagnostics.Process API directly, which allows you to capture the process' output in memory, as text, via the .RedirectStandardOutput (and .RedirectStandardError) properties of the System.Diagnostics.ProcessStartInfo class.

    • This approach is nontrivial, especially if you want to also capture stderr output without the risk of deadlocks; a stdout-output-only-capturing solution can be found in this answer.

    • It precludes running the process hidden, because the use of another user's credentials (.UserName and .PassWord properties) requires that .UseShellExecute be $false, which in turn means that the .WindowStyle property is ignored.

  • Use Start-Process if you want (the option) to run the process hidden, although that requires the use of a temporary file to capture the output:

    • Pitfalls: You must ensure that the target user has permission:

      • to access the working directory of the newly launched process, which requires passing a suitable directory to -WorkingDirectory
      • to write to the (temporary) file path passed to -RedirectStandardOutput / -RedirectStandardError.
    • Note that -RedirectStandardOutput / -RedirectStandardError capture the output from the named streams separately, and must be separate files. If you want to merge the streams and capture them interleaved in a single file, you must launch your process via a shell (such as PowerShell or cmd.exe) and use its stream-merging features, typically 2>&1.

The following Start-Process-based solution shows how to run the process hidden and capture stdout output (only):

# Prompt for the target user's credentials.
$cred = Get-Credential

# Determine the working directory and temporary file path.
# IMPORTANT: THE TARGET USER MUST HAVE PERMISSION TO
#  * ACCESS the working directory
#  * access and WRITE TO the temp file.
$workingDir = "$env:SystemRoot\Temp"
$tmpFile = Join-Path "$env:SystemRoot\Temp" "~$PID.tmp"

# Launch the process as the target user, hidden, save its stdout
# to the temporary file, and wait for it to terminate.
(Start-Process -WindowStyle Hidden -PassThru -WorkingDirectory $workingDir -RedirectStandardOutput $tmpFile -Credential $cred powershell.exe @'
  -noprofile -command
  "[array] (wsl -- ip -o -4 -json addr list eth0 | ConvertFrom-Json).addr_info.local -ne ''"
'@).WaitForExit()

# Get the captured output and remove the temp. file.
$output = Get-Content $tmpFile; Remove-Item $tmpFile

# Print the captured result
$output

Note:

  • The powershell.exe CLI call above uses a streamlined version of the command in your question.

  • While Start-Process has a dedicated -Wait switch to await the launched process' termination, as of PowerShell Core 7.2 it appears to be incompatible with -Credential, i.e. with running as a different user (resulting in an Access denied error while still launching the process, albeit asynchronously).

    • The workaround is to use -PassThru, in order to make the normally output-less Start-Process emit a System.Diagnostics.Process instance describing the launched process, and call .WaitForExit() on that instance.
  • Related