Starten von .ps1 Script aus PowerShell mit Parametern und Anmeldeinformationen und Abrufen der Ausgabe mithilfe von Variablen

10

Hallo Stack Community :)

Ich habe ein einfaches Ziel. Ich möchte ein PowerShell-Skript von einem anderen Powershell-Skript aus starten, aber es gibt drei Bedingungen:

  1. Ich muss Anmeldeinformationen übergeben (die Ausführung stellt eine Verbindung zu einer Datenbank her, die einen bestimmten Benutzer hat).
  2. Es müssen einige Parameter benötigt werden
  3. Ich möchte die Ausgabe an eine Variable übergeben

Es gibt eine ähnliche Frage Link . Die Antwort ist jedoch, Dateien als Kommunikationsmittel zwischen zwei PS-Skripten zu verwenden. Ich möchte nur Zugriffskonflikte vermeiden. @Update: Das Hauptskript startet einige andere Skripte. Daher kann die Lösung mit Dateien schwierig sein, wenn die Ausführung von mehreren Benutzern gleichzeitig ausgeführt wird.

Script1.ps1 ist das Skript, das eine Zeichenfolge als Ausgabe haben sollte. (Nur um klar zu sein, es ist ein fiktives Skript, das echte hat 150 Zeilen, also wollte ich nur ein Beispiel machen)

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

ExecuteScripts.ps1 sollte diese mit den drei oben genannten Bedingungen aufrufen

Ich habe mehrere Lösungen ausprobiert. Dieser zum Beispiel:

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

Ich bekomme keine Ausgabe davon und kann das Skript nicht einfach mit aufrufen

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

Weil ich ihm keinen -CredentialParameter übergeben kann.

Vielen Dank im Voraus!

Dmytro
quelle
Wenn es nur um Zugriffskonflikte geht: Das Erstellen eindeutiger Dateinamen für jeden Aufruf würde Ihr Problem lösen, oder?
mklement0
1
@ mklement0 wenn es der einzige Weg ist, würde ich mit dieser Lösung stapeln. Nur zufällige Dateinamen generieren, prüfen, ob eine solche Datei vorhanden ist ... Ich werde 6 bis 10 Skripte aus meinem Java-Code ausführen und es würde jedes Mal 6 bis 10 Dateien benötigen, wenn ich meine Anwendung verwende oder jemand anderes sie verwendet. Es geht also auch um Leistung
Dmytro

Antworten:

2

Hinweis:

  • Die folgende Lösung funktioniert mit jedem externen Programm und erfasst die Ausgabe immer als Text .

  • Informationen zum Aufrufen einer anderen PowerShell-Instanz und zum Erfassen ihrer Ausgabe als Rich-Objekte (mit Einschränkungen) finden Sie in der Variantenlösung im unteren Abschnitt oder in der hilfreichen Antwort von Mathias R. Jessen , die das PowerShell-SDK verwendet .

Hier ist ein Proof-of-Concept, der auf der direkten Verwendung der Typen System.Diagnostics.Processund System.Diagnostics.ProcessStartInfo.NET zum Erfassen der Prozessausgabe im Speicher basiert (wie in Ihrer Frage angegeben, Start-Processist keine Option, da nur das Erfassen der Ausgabe in Dateien unterstützt wird , wie in dieser Antwort gezeigt ). ::

Hinweis:

  • Aufgrund der Ausführung als anderer Benutzer wird dies nur unter Windows (ab .NET Core 3.1) unterstützt, jedoch in beiden PowerShell-Editionen.

  • Da .WindowStyleder Befehl als anderer Benutzer ausgeführt werden muss und die Ausgabe erfasst werden muss, kann er nicht zum Ausführen des ausgeblendeten Befehls verwendet werden (da die Verwendung .WindowStyleerforderlich .UseShellExecuteist $true, was mit diesen Anforderungen nicht kompatibel ist). Da jedoch alle Ausgaben erfasst werden , führt die Einstellung .CreateNoNewWindowauf $trueeffektiv zu einer versteckten Ausführung.

# 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

Das Obige ergibt ungefähr Folgendes, was zeigt, dass der Prozess mit der angegebenen Benutzeridentität erfolgreich ausgeführt wurde:

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

Da Sie eine andere PowerShell- Instanz aufrufen , möchten Sie möglicherweise die Fähigkeit der PowerShell-CLI nutzen , die Ausgabe im CLIXML-Format darzustellen, wodurch die Ausgabe in umfangreiche Objekte deserialisiert werden kann , wenn auch mit eingeschränkter Typentreue , wie in dieser Antwort erläutert .

# 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

Die obigen Ausgaben geben ungefähr Folgendes aus und zeigen, dass die von [datetime]( System.DateTime) ausgegebene Instanz ( ) Get-Dateals solche deserialisiert wurde:

Running `Get-Date` as user jdoe yielded:

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

as data type:
System.DateTime
mklement0
quelle
5

Start-ProcessDies wäre meine letzte Wahl, um PowerShell über PowerShell aufzurufen - insbesondere, weil alle E / A zu Zeichenfolgen und nicht zu (deserialisierten) Objekten werden.

Zwei Alternativen:

1. Wenn der Benutzer ein lokaler Administrator ist und PSRemoting konfiguriert ist

Wenn eine Remote-Sitzung auf dem lokalen Computer (leider nur für lokale Administratoren möglich) möglich ist, würde ich auf jeden Fall Folgendes wählen Invoke-Command:

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

$strings wird die Ergebnisse enthalten.


2. Wenn der Benutzer kein Administrator auf dem Zielsystem ist

Sie können Ihr eigenes "Nur-Lokal Invoke-Command" schreiben, indem Sie einen Out-of-Process-Runspace hochfahren, indem Sie:

  1. Erstellen eines PowerShellProcessInstanceunter einem anderen Login
  2. Erstellen eines Runspace in diesem Prozess
  3. Führen Sie Ihren Code im besagten Out-of-Process-Runspace aus

Ich habe eine solche Funktion unten zusammengestellt, siehe Inline-Kommentare für eine Anleitung:

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

Dann verwenden Sie wie folgt:

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

rcv.ps1

param(
    $username,
    $password
)

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

Ausführung von einem anderen Skript:

.\rcv.ps1 'user' 'supersecretpassword'

Ausgabe:

The user is:  user
My super secret password is:  supersecretpassword
thepip3r
quelle
1
Ich muss Anmeldeinformationen an diese Prüfung weitergeben ...
Dmytro
aktualisierte die relevanten Teile.
thepip3r
Zur Verdeutlichung: Die Absicht besteht nicht nur darin, Anmeldeinformationen zu übergeben, sondern als der Benutzer auszuführen, der durch die Anmeldeinformationen identifiziert wird.
mklement0
1
@ mklement0, danke für die Klarstellung, denn das war mir durch die verschiedenen Iterationen der gestellten Frage überhaupt nicht klar.
thepip3r
0

Was Sie tun können, ist Folgendes, um einen Parameter an ein ps1-Skript zu übergeben.

Das erste Skript kann eine origin.ps1 sein, in die wir schreiben:

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

Das Zielskript dest.ps1 kann den folgenden Code zum Erfassen der Variablen enthalten

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

Und das Ergebnis wird sein

my args Pa$$w0rd, parameter_a, parameter_n
Andy McRae
quelle
1
Das Hauptziel ist es, alle Bedingungen in einer Ausführung zu kombinieren. Ich muss Parameter und Anmeldeinformationen übergeben!
Dmytro
Was meinst du mit "kombiniere alle Bedingungen zu einer Ausführung". Ich glaube nicht, dass Sie einen Parameter mit dem Symbol "-" hinzufügen können, wie Sie es getan haben. Ich denke, Sie müssen die Zeichenfolgen
Andy McRae
Ich muss eine PS1-Datei mit Parametern ausführen und -Credential $credentialsParameter an diese Ausführung übergeben und die Ausgabe davon in eine Variable erhalten. Die ps1. Das Skript, das ausgeführt wird, wirft am Ende eine einzelne Wortfolge. Schauen Sie sich nur an, wie ich es gemacht habe, Start-processaber diese Funktion generiert keine Ausgabe
Dmytro
Ich denke, mit Powershell können Sie einen Parameter wie diesen nicht übergeben. $ Arguments = "C: \ .. \ script1.ps1" + "-ClientName" + $ DeviceName. Sie sollten wahrscheinlich daran denken, das "-"
Andy McRae
1
das wurde gesagt. Start-Process führt das Skript mit Parametern und Anmeldeinformationen aus, speichert diese Ausgabe jedoch nicht in einer Variablen. Wenn ich versuche, auf eine $outputVariable zuzugreifen , ist sie NULL. Die andere Idee, die von @ mklement0 kam, ist das Speichern der Ausgabe in einer Datei. Aber in meinem Fall wird es eine große Menge von Dateien an einem Ort verursachen. Alle von verschiedenen Benutzern mit verschiedenen Skripten erstellt
Dmytro