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 theSystem.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
.
- to access the working directory of the newly launched process, which requires passing a suitable directory to
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 orcmd.exe
) and use its stream-merging features, typically2>&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 anAccess denied
error while still launching the process, albeit asynchronously).- The workaround is to use
-PassThru
, in order to make the normally output-lessStart-Process
emit aSystem.Diagnostics.Process
instance describing the launched process, and call.WaitForExit()
on that instance.
- The workaround is to use