Remarque:
La solution suivante fonctionne avec tout programme externe et capture invariablement la sortie sous forme de texte .
Pour appeler une autre instance PowerShell et capturer sa sortie en tant qu'objets riches (avec des limitations), consultez la solution variante dans la section inférieure ou considérez la réponse utile de Mathias R. Jessen , qui utilise le SDK PowerShell .
Voici une preuve de concept basée sur l'utilisation directe des types System.Diagnostics.Process
et System.Diagnostics.ProcessStartInfo
.NET pour capturer la sortie du processus en mémoire (comme indiqué dans votre question, Start-Process
n'est pas une option, car il prend uniquement en charge la capture de la sortie dans des fichiers , comme indiqué dans cette réponse ) :
Remarque:
En raison de l'exécution en tant qu'utilisateur différent, cela est pris en charge sous Windows uniquement (à partir de .NET Core 3.1), mais dans les deux éditions PowerShell.
En raison de la nécessité d'exécuter en tant qu'utilisateur différent et de capturer la sortie, .WindowStyle
ne peut pas être utilisé pour exécuter la commande cachée (car l'utilisation de .WindowStyle
require .UseShellExecute
est $true
, ce qui est incompatible avec ces exigences); cependant, étant donné que toutes les sorties sont capturées , le réglage .CreateNoNewWindow
sur $true
aboutit effectivement à une exécution cachée.
# 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
Ce qui précède donne quelque chose comme ce qui suit, montrant que le processus s'est correctement déroulé avec l'identité d'utilisateur donnée:
Running `cmd /c echo %USERNAME%` as user jdoe yielded:
jdoe
Puisque vous appelez une autre instance PowerShell , vous souhaiterez peut- être profiter de la capacité de la CLI PowerShell à représenter la sortie au format CLIXML, ce qui permet de désérialiser la sortie en objets riches , bien qu'avec une fidélité de type limitée , comme expliqué dans cette réponse associée .
# 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
Ce qui précède génère quelque chose comme ce qui suit, montrant que l' [datetime]
instance ( System.DateTime
) sortie par a Get-Date
été désérialisée en tant que telle:
Running `Get-Date` as user jdoe yielded:
Friday, March 27, 2020 6:26:49 PM
as data type:
System.DateTime