Wie übergebe ich benannte Parameter mit Invoke-Command?

77

Ich habe ein Skript, das ich über Invoke-Command remote ausführen kann

Invoke-Command -ComputerName (Get-Content C:\Scripts\Servers.txt) `
               -FilePath C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1

Solange ich Standardparameter verwende, funktioniert es einwandfrei. Das Skript verfügt jedoch über zwei benannte [switch] -Parameter (-Debug und -Clear).

Wie kann ich die geschalteten Parameter über den Invoke-Befehl übergeben? Ich habe die -ArgumentList ausprobiert, aber es werden Fehler angezeigt, daher muss die Syntax falsch sein oder so. Jede Hilfe wird sehr geschätzt.

Sean
quelle

Antworten:

97

-ArgumentListbasiert auf der Verwendung mit Scriptblock- Befehlen wie:

Invoke-Command -Cn (gc Servers.txt) {param($Debug=$False, $Clear=$False) C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 } -ArgumentList $False,$True

Wenn Sie es mit a aufrufen, werden -Filedie Parameter immer noch wie ein dummes, bespritztes Array übergeben. Ich habe eine Feature-Anfrage eingereicht , um diese zum Befehl hinzuzufügen (bitte stimmen Sie ab).

Sie haben also zwei Möglichkeiten:

Wenn Sie ein Skript haben, das so aussieht , an einem Netzwerkspeicherort, auf den vom Remotecomputer aus zugegriffen werden kann (dies -Debugist impliziert, da Parameterdas Skript bei Verwendung des Attributs implizit CmdletBinding und damit alle allgemeinen Parameter erhält):

param(
   [Parameter(Position=0)]
   $one
,
   [Parameter(Position=1)]
   $two
,
   [Parameter()]
   [Switch]$Clear
)

"The test is for '$one' and '$two' ... and we $(if($DebugPreference -ne 'SilentlyContinue'){"will"}else{"won't"}) run in debug mode, and we $(if($Clear){"will"}else{"won't"}) clear the logs after."

Ohne sich auf die Bedeutung von $Clear... einzulassen, wenn Sie aufrufen möchten, dass Sie eine der folgenden Invoke-CommandSyntaxen verwenden können:

icm -cn (gc Servers.txt) { 
    param($one,$two,$Debug=$False,$Clear=$False)
    C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 @PSBoundParameters
} -ArgumentList "uno", "dos", $false, $true

In diesem Fall dupliziere ich ALLE Parameter, die mir im Skriptblock wichtig sind, damit ich Werte übergeben kann. Wenn ich sie hart codieren kann (was ich tatsächlich getan habe), muss ich das nicht tun und verwenden PSBoundParameters, ich kann nur die übergeben, die ich brauche. Im zweiten Beispiel unten werde ich das $ Clear übergeben, um zu demonstrieren, wie Switch-Parameter übergeben werden:

icm -cn $Env:ComputerName { 
    param([bool]$Clear)
    C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $(Test-Path $Profile)

Die andere Option

Wenn sich das Skript auf Ihrem lokalen Computer befindet und Sie die Parameter nicht so ändern möchten, dass sie positionell sind, oder wenn Sie Parameter angeben möchten, die allgemeine Parameter sind (damit Sie sie nicht steuern können), möchten Sie den Inhalt von erhalten das Skript und binden Sie es in Ihren Skriptblock ein :

$script = [scriptblock]::create( @"
param(`$one,`$two,`$Debug=`$False,`$Clear=`$False)
&{ $(Get-Content C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -delimiter ([char]0)) } @PSBoundParameters
"@ )

Invoke-Command -Script $script -Args "uno", "dos", $false, $true

PostScript:

Wenn Sie wirklich eine Variable für den Skriptnamen übergeben müssen, hängt es davon ab, ob die Variable lokal oder remote definiert ist. Wenn Sie eine Variable $Scriptoder eine Umgebungsvariable $Env:Scriptmit dem Namen eines Skripts haben, können Sie diese im Allgemeinen mit dem Aufrufoperator (&) ausführen: &$Scriptoder&$Env:Script

Wenn es sich um eine Umgebungsvariable handelt, die bereits auf dem Remotecomputer definiert ist, ist dies alles, was dazu gehört. Wenn es sich um eine lokale Variable handelt, müssen Sie sie an den Remote-Skriptblock übergeben:

Invoke-Command -cn $Env:ComputerName { 
    param([String]$Script, [bool]$Clear)
    & $ScriptPath "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $ScriptPath, (Test-Path $Profile)
Jaykul
quelle
ArgumentList ist auch mit -FilePath verfügbar. Invoke-Command [-FilePath] <string> [[-Session] <PSSession []>] [-AsJob] [-HideComputerName] [-JobName <string>] [-T hrottleLimit <int>] [-ArgumentList <Object [ ]>] [-InputObject <psobject>] [<CommonParameters>]
Ravikanth
1
Ja, Ravikanth, aber es scheint, dass die ArgumentList durch Splattern in das Skript erstellt wird, sodass Sie keine benannten Parameter angeben können .
Jaykul
8
Es ist eine Schande, dass StackOverflow das PowerShell-Skript nicht versteht. Die Syntaxhervorhebung der Skriptnamen lässt zu wünschen übrig.
Jaykul
OK, das sieht gut aus ... muss das ausprobieren. Ich habe jedoch eine Folgefrage: Wenn ich icm -FilePath verwende, wird das Skript auf den Remote-Server kopiert und dann ausgeführt. Wenn ich icm -Scriptblock verwende, scheint es nicht, das Skript zuerst zu kopieren - es scheint anzunehmen, dass das Skript bereits auf dem Remote-Server in dem im Skriptblock angegebenen Pfad vorhanden ist. Ist das auch deine Erfahrung?
Sean
2
Nichts wird wirklich auf den Remote-Computer kopiert. Das von Ihnen angegebene Skript wird in ScriptBlock konvertiert und dann wird ScriptBlock an den Remote-Computer weitergeleitet, und Ihr Verständnis von -ScriptBlock ist korrekt
Ravikanth
6

Meine Lösung bestand darin, den Skriptblock dynamisch zu schreiben mit [scriptblock]:Create:

# Or build a complex local script with MARKERS here, and do substitutions
# I was sending install scripts to the remote along with MSI packages
# ...for things like Backup and AV protection etc.

$p1 = "good stuff"; $p2 = "better stuff"; $p3 = "best stuff"; $etc = "!"
$script = [scriptblock]::Create("MyScriptOnRemoteServer.ps1 $p1 $p2 $etc")
#strings get interpolated/expanded while a direct scriptblock does not

# the $parms are now expanded in the script block itself
# ...so just call it:
$result = invoke-command $computer -script $script

Passing Argumente waren sehr frustrierend, verschiedene Methoden versucht, zum Beispiel
-arguments, $using:p1etc. und dies gerade arbeitet als ohne Probleme erwünscht.

Da ich den Inhalt und die Variablenerweiterung der Zeichenfolge steuere, die die [scriptblock](oder Skriptdatei) auf diese Weise erstellt, gibt es kein wirkliches Problem mit der Beschwörung "invoke-command".

(Es sollte nicht so schwer sein. :))

HerbM
quelle
Genau. Dies ist die einzige Lösung, mit der ich arbeiten kann, nur weil keine Parameter übergeben werden müssen. Und nicht in der Lage, den $ -Fehler direkt zu verbreiten. Anderes Beispiel: Mit [scriptblock] :: Create ("New-Item -Path '$ BackupPath' -ItemType Directory -Force") muss If ($ result.Exists) im Aufrufer erzwungen werden, um zu überprüfen, ob ein Fehler aufgetreten ist. PSVersion = 5.1
Reverpie
5

Ich vermute, es ist eine neue Funktion, seit dieser Beitrag erstellt wurde - übergeben Sie Parameter mit $ Using: var an den Skriptblock. Dann ist es einfach, Parameter zu übergeben, vorausgesetzt, das Skript befindet sich bereits auf dem Computer oder an einem bekannten Netzwerkstandort relativ zum Computer

Am Hauptbeispiel wäre es:

icm -cn $Env:ComputerName { 
    C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -one "uno" -two "dos" -Debug -Clear $Using:Clear
}
RobG
quelle
Ich weiß nicht, ob es neu ist oder nicht, aber $ Using war genau das, wonach ich gesucht habe. Dank dafür!
JesusIsMyDriver.dll
3

Ich brauchte etwas, um Skripte mit benannten Parametern aufzurufen. Wir haben die Richtlinie, keine ordinale Positionierung von Parametern zu verwenden und den Parameternamen zu verlangen.

Mein Ansatz ähnelt dem oben beschriebenen, ruft jedoch den Inhalt der Skriptdatei ab, die Sie aufrufen möchten, und sendet einen Parameterblock mit den Parametern und Werten.

Einer der Vorteile davon ist, dass Sie optional auswählen können, welche Parameter an die Skriptdatei gesendet werden sollen, wobei nicht obligatorische Parameter mit Standardwerten berücksichtigt werden.

Angenommen, der temporäre Pfad enthält ein Skript namens "MyScript.ps1" mit dem folgenden Parameterblock:

[CmdletBinding(PositionalBinding = $False)]
param
(
    [Parameter(Mandatory = $True)] [String] $MyNamedParameter1,
    [Parameter(Mandatory = $True)] [String] $MyNamedParameter2,
    [Parameter(Mandatory = $False)] [String] $MyNamedParameter3 = "some default value"
)

So würde ich dieses Skript von einem anderen Skript aus aufrufen:

$params = @{
    MyNamedParameter1 = $SomeValue
    MyNamedParameter2 = $SomeOtherValue
}

If ($SomeCondition)
{
    $params['MyNamedParameter3'] = $YetAnotherValue
}

$pathToScript = Join-Path -Path $env:Temp -ChildPath MyScript.ps1

$sb = [scriptblock]::create(".{$(Get-Content -Path $pathToScript -Raw)} $(&{
        $args
} @params)")
Invoke-Command -ScriptBlock $sb

Ich habe dies in vielen Szenarien verwendet und es funktioniert wirklich gut. Eine Sache, die Sie gelegentlich tun müssen, ist, den Parameterwertzuweisungsblock in Anführungszeichen zu setzen. Dies ist immer dann der Fall, wenn der Wert Leerzeichen enthält.

Beispiel: Dieser Parameterblock wird verwendet, um ein Skript aufzurufen, das verschiedene Module an den von PowerShell verwendeten Standardspeicherort kopiert, C:\Program Files\WindowsPowerShell\Modulesder ein Leerzeichen enthält.

$params = @{
        SourcePath      = "$WorkingDirectory\Modules"
        DestinationPath = "'$(Join-Path -Path $([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath 'WindowsPowershell\Modules')'"
    }

Hoffe das hilft!

CarlR
quelle