Nota:
La siguiente solución funciona con cualquier programa externo y captura la salida invariablemente como texto .
Para invocar otra instancia de PowerShell y capturar su salida como objetos enriquecidos (con limitaciones), vea la solución variante en la sección inferior o considere la útil respuesta de Mathias R. Jessen , que usa el SDK de PowerShell .
Aquí hay una prueba de concepto basada en el uso directo de los tipos System.Diagnostics.Processy System.Diagnostics.ProcessStartInfo.NET para capturar la salida del proceso en la memoria (como se indicó en su pregunta, Start-Processno es una opción, ya que solo admite la captura de salida en archivos , como se muestra en esta respuesta ) :
Nota:
Debido a que se ejecuta como un usuario diferente, esto solo es compatible con Windows (a partir de .NET Core 3.1), pero en ambas ediciones de PowerShell.
Debido a la necesidad de ejecutarse como un usuario diferente y la necesidad de capturar la salida, .WindowStyleno se puede utilizar para ejecutar el comando oculto (porque el uso .WindowStyledebe .UseShellExecuteser $true, lo que es incompatible con estos requisitos); sin embargo, ya está siendo toda la salida capturado , el establecimiento .CreateNoNewWindowde $trueresultados de eficacia en la ejecución oculto.
# Get the target user's name and password.
$cred = Get-Credential
# Create a ProcessStartInfo instance
# with the relevant properties.
$psi = [System.Diagnostics.ProcessStartInfo] @{
# For demo purposes, use a simple `cmd.exe` command that echoes the username.
# See the bottom section for a call to `powershell.exe`.
FileName = 'cmd.exe'
Arguments = '/c echo %USERNAME%'
# Set this to a directory that the target user
# is permitted to access.
WorkingDirectory = 'C:\' #'
# Ask that output be captured in the
# .StandardOutput / .StandardError properties of
# the Process object created later.
UseShellExecute = $false # must be $false
RedirectStandardOutput = $true
RedirectStandardError = $true
# Uncomment this line if you want the process to run effectively hidden.
# CreateNoNewWindow = $true
# Specify the user identity.
# Note: If you specify a UPN in .UserName
# (user@doamin.com), set .Domain to $null
Domain = $env:USERDOMAIN
UserName = $cred.UserName
Password = $cred.Password
}
# Create (launch) the process...
$ps = [System.Diagnostics.Process]::Start($psi)
# Read the captured standard output.
# By reading to the *end*, this implicitly waits for (near) termination
# of the process.
# Do NOT use $ps.WaitForExit() first, as that can result in a deadlock.
$stdout = $ps.StandardOutput.ReadToEnd()
# Uncomment the following lines to report the process' exit code.
# $ps.WaitForExit()
# "Process exit code: $($ps.ExitCode)"
"Running ``cmd /c echo %USERNAME%`` as user $($cred.UserName) yielded:"
$stdout
Lo anterior produce algo como lo siguiente, mostrando que el proceso se ejecutó con éxito con la identidad de usuario dada:
Running `cmd /c echo %USERNAME%` as user jdoe yielded:
jdoe
Como está llamando a otra instancia de PowerShell , es posible que desee aprovechar la capacidad de PowerShell CLI para representar la salida en formato CLIXML, que permite deserializar la salida en objetos ricos , aunque con una fidelidad de tipo limitada , como se explica en esta respuesta relacionada .
# Get the target user's name and password.
$cred = Get-Credential
# Create a ProcessStartInfo instance
# with the relevant properties.
$psi = [System.Diagnostics.ProcessStartInfo] @{
# Invoke the PowerShell CLI with a simple sample command
# that calls `Get-Date` to output the current date as a [datetime] instance.
FileName = 'powershell.exe'
# `-of xml` asks that the output be returned as CLIXML,
# a serialization format that allows deserialization into
# rich objects.
Arguments = '-of xml -noprofile -c Get-Date'
# Set this to a directory that the target user
# is permitted to access.
WorkingDirectory = 'C:\' #'
# Ask that output be captured in the
# .StandardOutput / .StandardError properties of
# the Process object created later.
UseShellExecute = $false # must be $false
RedirectStandardOutput = $true
RedirectStandardError = $true
# Uncomment this line if you want the process to run effectively hidden.
# CreateNoNewWindow = $true
# Specify the user identity.
# Note: If you specify a UPN in .UserName
# (user@doamin.com), set .Domain to $null
Domain = $env:USERDOMAIN
UserName = $cred.UserName
Password = $cred.Password
}
# Create (launch) the process...
$ps = [System.Diagnostics.Process]::Start($psi)
# Read the captured standard output, in CLIXML format,
# stripping the `#` comment line at the top (`#< CLIXML`)
# which the deserializer doesn't know how to handle.
$stdoutCliXml = $ps.StandardOutput.ReadToEnd() -replace '^#.*\r?\n'
# Uncomment the following lines to report the process' exit code.
# $ps.WaitForExit()
# "Process exit code: $($ps.ExitCode)"
# Use PowerShell's deserialization API to
# "rehydrate" the objects.
$stdoutObjects = [Management.Automation.PSSerializer]::Deserialize($stdoutCliXml)
"Running ``Get-Date`` as user $($cred.UserName) yielded:"
$stdoutObjects
"`nas data type:"
$stdoutObjects.GetType().FullName
Lo anterior genera algo como lo siguiente, mostrando que la salida de [datetime]instancia ( System.DateTime) Get-Datefue deserializada como tal:
Running `Get-Date` as user jdoe yielded:
Friday, March 27, 2020 6:26:49 PM
as data type:
System.DateTime