Avvio di .ps1 Script da PowerShell con parametri e credenziali e recupero dell'output tramite variabile


10

Ciao Stack Community :)

Ho un obiettivo semplice. Vorrei avviare alcuni script PowerShell da un altro script Powershell, ma ci sono 3 condizioni:

  1. Devo passare le credenziali (l'esecuzione si collega a un database con un utente specifico)
  2. Deve prendere alcuni parametri
  3. Vorrei passare l'output in una variabile

C'è una domanda simile Link . Ma la risposta è usare i file come un modo per comunicare tra 2 script PS. Vorrei solo evitare conflitti di accesso. @Update: lo script principale avvia pochi altri script. quindi la soluzione con i file può essere complicata, se l'esecuzione verrà eseguita da più utenti contemporaneamente.

Script1.ps1 è lo script che dovrebbe avere una stringa come output. (Giusto per essere chiari, è una sceneggiatura fittizia, quella vera ha 150 righe, quindi volevo solo fare un esempio)

param(  
[String]$DeviceName
)
#Some code that needs special credentials
$a = "Device is: " + $DeviceName
$a

ExecuteScripts.ps1 dovrebbe invocare quello con quelle 3 condizioni sopra menzionate

Ho provato più soluzioni. Questo per esempio:

$arguments = "C:\..\script1.ps1" + " -ClientName" + $DeviceName
$output = Start-Process powershell -ArgumentList $arguments -Credential $credentials
$output 

Non ne ricavo alcun output e non posso semplicemente chiamare lo script con

&C:\..\script1.ps1 -ClientName PCPC

Perché non riesco a passare -Credentialparametri ad esso ..

Grazie in anticipo!


Se si tratta solo di conflitti di accesso: la creazione di nomi di file univoci per ogni invocazione risolverebbe il tuo problema, giusto?
mklement0

1
@ mklement0 se è l'unico modo, impilerei con quella soluzione. Sto solo generando nomi di file casuali, controllando se esiste tale file ... Eseguirò da 6 a 10 script dal mio codice Java e avrebbe bisogno di 6-10 file ogni volta che sto usando o qualcun altro usa la mia applicazione. Quindi si tratta anche di esibizione
Dmytro il

Risposte:


2

Nota:

  • La seguente soluzione funziona con qualsiasi programma esterno e cattura invariabilmente l'output come testo .

  • Per invocare un'altra istanza di PowerShell e acquisirne l'output come oggetti ricchi (con limitazioni), vedere la soluzione variante nella sezione inferiore o considerare la risposta utile di Mathias R. Jessen , che utilizza l' SDK di PowerShell .

Ecco una dimostrazione di concetto basata sull'uso diretto dei tipi System.Diagnostics.Processe System.Diagnostics.ProcessStartInfo.NET per acquisire l'output del processo in memoria (come indicato nella tua domanda, Start-Processnon è un'opzione, perché supporta solo l'acquisizione dell'output nei file , come mostrato in questa risposta ) :

Nota:

  • A causa dell'esecuzione come utente diverso, questo è supportato solo su Windows (a partire da .NET Core 3.1), ma in entrambe le versioni di PowerShell.

  • A causa della necessità di essere eseguito come un altro utente e della necessità di acquisire l'output, .WindowStylenon è possibile utilizzare il comando nascosto (perché l'utilizzo .WindowStylerichiede .UseShellExecutedi essere $true, che è incompatibile con tali requisiti); tuttavia, poiché viene catturato tutto l'output , l'impostazione .CreateNoNewWindowper $trueottenere risultati nascosti in modo efficace.

# 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

Quanto sopra produce qualcosa di simile al seguente, dimostrando che il processo è stato eseguito correttamente con l'identità dell'utente fornita:

Running `cmd /c echo %USERNAME%` as user jdoe yielded:
jdoe

Dato che stai chiamando un'altra istanza di PowerShell , ti consigliamo di farlo sfruttare la capacità dell'interfaccia della riga di comando di PowerShell di rappresentare l'output in formato CLIXML, che consente di deserializzare l'output in oggetti ricchi , sebbene con fedeltà di tipo limitata , come spiegato in questa risposta correlata .

# 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

Quanto sopra produce qualcosa di simile al seguente, a dimostrazione che l' [datetime]istanza (System.DateTime ) prodotta da è Get-Datestata deserializzata come tale:

Running `Get-Date` as user jdoe yielded:

Friday, March 27, 2020 6:26:49 PM

as data type:
System.DateTime

5

Start-Processsarebbe la mia ultima scelta per invocare PowerShell da PowerShell, soprattutto perché tutto l'I / O diventa stringhe e non oggetti (deserializzati).

Due alternative:

1. Se l'utente è un amministratore locale e PSRemoting è configurato

Se una sessione remota contro il computer locale (purtroppo limitata agli amministratori locali) è un'opzione, sceglierei sicuramente Invoke-Command:

$strings = Invoke-Command -FilePath C:\...\script1.ps1 -ComputerName localhost -Credential $credential

$strings conterrà i risultati.


2. Se l'utente non è un amministratore nel sistema di destinazione

È possibile scrivere il proprio "solo locale Invoke-Command" eseguendo la rotazione di uno spazio di esecuzione fuori processo da:

  1. Creazione di un PowerShellProcessInstance, con un accesso diverso
  2. Creazione di uno spazio di esecuzione in detto processo
  3. Esegui il tuo codice in detto spazio di esecuzione fuori processo

Ho messo insieme una tale funzione di seguito, vedere i commenti in linea per una procedura dettagliata:

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()
            }
        }
    }
}

Quindi utilizzare in questo modo:

$output = Invoke-RunAs -FilePath C:\path\to\script1.ps1 -Credential $targetUser -NamedArguments @{ClientDevice = "ClientName"}

0

rcv.ps1

param(
    $username,
    $password
)

"The user is:  $username"
"My super secret password is:  $password"

esecuzione da un altro script:

.\rcv.ps1 'user' 'supersecretpassword'

produzione:

The user is:  user
My super secret password is:  supersecretpassword

1
Devo passare le credenziali per questa mostra ...
Dmytro,

aggiornato le porzioni pertinenti.
thepip3r

Per chiarire: l'intento non è solo quello di passare le credenziali, ma di eseguire come l'utente identificato dalle credenziali.
mklement0

1
@ mklement0, grazie per il chiarimento perché non mi è stato affatto chiaro dalle diverse iterazioni della domanda posta.
thepip3r

0

Cosa puoi fare nel modo seguente per passare un parametro a uno script ps1.

Il primo script può essere origin.ps1 in cui scriviamo:

& C:\scripts\dest.ps1 Pa$$w0rd parameter_a parameter_n

Lo script di destinazione dest.ps1 può avere il codice seguente per acquisire le variabili

$var0 = $args[0]
$var1 = $args[1]
$var2 = $args[2]
Write-Host "my args",$var0,",",$var1,",",$var2

E il risultato sarà

my args Pa$$w0rd, parameter_a, parameter_n

1
L'obiettivo principale è quello di combinare tutte le condizioni in 1 esecuzione. Devo passare i parametri e passare le credenziali!
Dmytro,

Cosa intendi con "combina tutte le condizioni in 1 esecuzione". Non credo che tu possa aggiungere un parametro con il simbolo "-" come hai fatto tu .. Penso che devi riformattare le stringhe nello script di destinazione
Andy McRae il

Devo eseguire alcuni file PS1 con parametri e passare i -Credential $credentialsparametri a questa esecuzione e ottenere l'output da esso in una variabile. La ps1. Lo script che eseguo sta lanciando una stringa di parole singole alla fine. Guarda il modo in cui l'ho fatto, Start-processma questa funzione non genera output
Dmytro il

Penso che powershell non ti permetta di passare un parametro come questo $ argomenti = "C: \ .. \ script1.ps1" + "-ClientName" + $ DeviceName. Probabilmente dovresti pensare di eliminare il "-"
Andy McRae il

1
detto questo. Start-Process esegue lo script con parametri e credenziali, bit non salva questo output in una variabile. Se sto provando ad accedere alla $outputvariabile, è NULL. L'altra idea che proviene da @ mklement0 è quella di salvare l'output in un file. Ma nel mio caso causerà un'enorme quantità di file in un unico posto. Tutto creato da utenti diversi con script diversi
Dmytro il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.