Este equipo redactor ha estado mucho tiempo buscando la solución a tu interrogante, te regalamos la resolución así que nuestro deseo es serte de gran apoyo.
Solución:
Start-Process
seria mi elección de último recurso para invocar PowerShell desde PowerShell, especialmente porque todas las E / S se convierten en cadenas y no en objetos (deserializados).
Dos alternativas:
1. Si el usuario es un administrador local y PSRemoting está configurado
Si una sesión remota contra la máquina local (desafortunadamente restringida a administradores locales) es una opción, definitivamente iría con Invoke-Command
:
$strings = Invoke-Command -FilePath C:...script1.ps1 -ComputerName localhost -Credential $credential
$strings
contendrá los resultados.
2. Si el usuario no es un administrador en el sistema de destino
Puede escribir su propio “solo local Invoke-Command
“girando un espacio de ejecución fuera de proceso al:
- Creando un
PowerShellProcessInstance
, con un inicio de sesión diferente - Creando un espacio de ejecución en dicho proceso
- Ejecute su código en dicho espacio de ejecución fuera de proceso
Reuní una función de este tipo a continuación, vea los comentarios en línea para un recorrido:
function Invoke-RunAs
[CmdletBinding()]
param(
[Alias('PSPath')]
[ValidateScript(Test-Path $_ -PathType Leaf)]
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]
$FilePath,
[Parameter(Mandatory = $true)]
[pscredential]
[System.Management.Automation.CredentialAttribute()]
$Credential,
[Alias('Args')]
[Parameter(ValueFromRemainingArguments = $true)]
[System.Object[]]
$ArgumentList,
[Parameter(Position = 1)]
[System.Collections.IDictionary]
$NamedArguments
)
begin
# First we set up a separate managed powershell process
Write-Verbose "Creating PowerShellProcessInstance and runspace"
$ProcessInstance = [System.Management.Automation.Runspaces.PowerShellProcessInstance]::new($PSVersionTable.PSVersion, $Credential, $null, $false)
# And then we create a new runspace in said process
$Runspace = [runspacefactory]::CreateOutOfProcessRunspace($null, $ProcessInstance)
$Runspace.Open()
Write-Verbose "Runspace state is $($Runspace.RunspaceStateInfo)"
process
foreach($path in $FilePath)
Write-Verbose "In process block, Path:'$path'"
try
# Add script file to the code we'll be running
$powershell = [powershell]::Create([initialsessionstate]::CreateDefault2()).AddCommand((Resolve-Path $path).ProviderPath, $true)
# Add named param args, if any
if($PSBoundParameters.ContainsKey('NamedArguments'))
Write-Verbose "Adding named arguments to script"
$powershell = $powershell.AddParameters($NamedArguments)
# Add argument list values if present
if($PSBoundParameters.ContainsKey('ArgumentList'))
Write-Verbose "Adding unnamed arguments to script"
foreach($arg in $ArgumentList)
$powershell = $powershell.AddArgument($arg)
# Attach to out-of-process runspace
$powershell.Runspace = $Runspace
# Invoke, let output bubble up to caller
$powershell.Invoke()
if($powershell.HadErrors)
foreach($e in $powershell.Streams.Error)
Write-Error $e
finally
# clean up
if($powershell -is [IDisposable])
$powershell.Dispose()
end
foreach($target in $ProcessInstance,$Runspace)
# clean up
if($target -is [IDisposable])
$target.Dispose()
Entonces usa así:
$output = Invoke-RunAs -FilePath C:pathtoscript1.ps1 -Credential $targetUser -NamedArguments @ClientDevice = "ClientName"
Nota:
-
La siguiente solucion funciona con alguna programa externo, y captura la salida invariablemente como texto.
-
Para invocar otra instancia de PowerShell y capturar su salida como objetos ricos (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 uso directo de la System.Diagnostics.Process
y System.Diagnostics.ProcessStartInfo
Tipos de .NET para capturar la salida del proceso en memoria (como se indica en su pregunta, Start-Process
no es una opción, porque 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 es compatible con Solo Windows (a partir de .NET Core 3.1), pero en ambas ediciones de PowerShell allí.
-
Debido a la necesidad de ejecutar como un usuario diferente y la necesidad de capturar la salida,
.WindowStyle
no se puede utilizar para ejecutar el comando oculto (porque usando.WindowStyle
requiere.UseShellExecute
ser$true
, que es incompatible con estos requisitos); sin embargo, dado que toda la salida se capturado, configuración.CreateNoNewWindow
para$true
efectivamente da como resultado una ejecución oculta.
# 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
# ([email protected]), 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, que muestra que el proceso se ejecutó correctamente con la identidad de usuario dada:
Running `cmd /c echo %USERNAME%` as user jdoe yielded:
jdoe
Ya que estas llamando a otro Potencia Shell ejemplo, es posible que desee aprovechar la capacidad de la CLI de PowerShell para representar la salida en formato CLIXML, lo 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
# ([email protected]), 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 da como resultado algo como lo siguiente, que muestra que el [datetime]
instanciaSystem.DateTime
) salida por Get-Date
fue deserializado como tal:
Running `Get-Date` as user jdoe yielded:
Friday, March 27, 2020 6:26:49 PM
as data type:
System.DateTime
Te invitamos a respaldar nuestro cometido mostrando un comentario y dejando una valoración te estamos eternamente agradecidos.