Saltar al contenido

Iniciando .ps1 Script desde PowerShell con parámetros y credenciales y obteniendo salida usando variable

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:

  1. Creando un PowerShellProcessInstance, con un inicio de sesión diferente
  2. Creando un espacio de ejecución en dicho proceso
  3. 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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *