使用PowerShell将命名参数传递给ArgumentList

12

我有一个接受3个命名参数的PowerShell脚本,请告诉我如何从命令行传递它们。我尝试了下面的代码,但它没有起作用。它只将整个值分配给了P3。我的要求是P1应该包含1,P2应该包含2,并且P3应该被分配为3。

Invoke-Command -ComputerName server -FilePath "D:\test.ps1" -ArgumentList  {-P1 1 -P2 2 -P3 3}

以下是脚本文件代码。

Param (
    [string]$P3,
    [string]$P2,
    [string]$P1
)
Write-Output "P1 Value :" $P1
Write-Output "P2 Value:" $P2
Write-Output "P3 Value :" $P3

可能是重复的问题:如何使用Invoke-Command传递命名参数? - Loïc MICHEL
7个回答

19

一个选项:

$params = @{
P1 = 1
P2 = 2 
P3 = 3
}

$ScriptPath = 'D:\Test.ps1'

$sb = [scriptblock]::create(".{$(get-content $ScriptPath -Raw)} $(&{$args} @params)")

Invoke-Command -ComputerName server -ScriptBlock $sb

1
是的,我能够使用上述代码实现结果。 - Parveen Kumar
1
请问您能告诉我如何使用C#代码实现相同的效果吗?因为我的需求是从C#中运行这个PowerShell脚本。 - Parveen Kumar
很抱歉,我不太懂C#语言,无法为您进行转换。 - mjolinor
这段代码非常好用,直到你将一个数组作为参数传入。当发生这种情况时,你只会得到“System.String[]”作为值,因为参数块在展开时实际上被应用了 .ToString() 方法。我还没有找到可靠的避免这种情况的方法,如果有人有解决方案,我很乐意看到它! - CarlR
@CarlR - 尝试使用 P3 = "@('a','b','c')" - mjolinor
它运行良好。要传递带有空格的字符串,您必须使用额外的撇号将值包装起来。例如:P1 =“'我的字符串参数'” - sephirot

5
mjolinor 的代码很好,但我花了几分钟才理解它。
该代码实现了一个简单的功能 - 生成一个带内置参数的脚本块内容:
&{
    Param (
        [string]$P3,
        [string]$P2,
        [string]$P1
    )
    Write-Output "P1 Value:" $P1
    Write-Output "P2 Value:" $P2
    Write-Output "P3 Value:" $P3
} -P1 1 -P2 2 -P3 3

然后这个脚本块被传递给Invoke-Command。

为了简化代码:

".{$(get-content $ScriptPath -Raw)} $(&{$args} @params)"

$scriptContent = Get-Content $ScriptPath -Raw
$formattedParams = &{ $args } @params
# The `.{}` statement could be replaced with `&{}` here, because we don't need to persist variables after script call.
$scriptBlockContent = ".{ $scriptContent } $formattedParams"
$sb = [scriptblock]::create($scriptBlockContent)

让我们制作一个基本的C#实现:

void Run()
{
    var parameters = new Dictionary<string, string>
    {
        ["P1"] = "1",
        ["P2"] = "2",
        ["P3"] = "3"
    };

    var scriptResult = InvokeScript("Test.ps1", "server", parameters)
    Console.WriteLine(scriptResult);
}

string InvokeScript(string filePath, string computerName, Dictionary<string, string> parameters)
{
    var innerScriptContent = File.ReadAllText(filePath);
    var formattedParams = string.Join(" ", parameters.Select(p => $"-{p.Key} {p.Value}"));
    var scriptContent = "$sb = { &{ " + innerScriptContent + " } " + formattedParams + " }\n" +
        $"Invoke-Command -ComputerName {computerName} -ScriptBlock $sb";

    var tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".ps1");
    File.WriteAllText(tempFile, scriptContent);

    var psi = new ProcessStartInfo
        {
            FileName = "powershell",
            Arguments = $@"-ExecutionPolicy Bypass -File ""{tempFile}""",
            RedirectStandardOutput = true,
            UseShellExecute = false
        };

    var process = Process.Start(psi);
    var responseText = process.StandardOutput.ReadToEnd();

    File.Delete(tempFile);

    return responseText;
}

该代码生成一个临时脚本并执行它。
示例脚本:
$sb = {
    &{
        Param (
            [string]$P3,
            [string]$P2,
            [string]$P1
        )
        Write-Output "P1 Value:" $P1
        Write-Output "P2 Value:" $P2
        Write-Output "P3 Value:" $P3
     } -P1 1 -P2 2 -P3 3
}
Invoke-Command -ComputerName server -ScriptBlock $sb

请问一下 $(&{args}) 是什么意思?我正在尝试理解它如何与 @params 结合,以便生成像“-P1 1 -P2 2 -P3 3”这样的字符串。 - batbrat
从内部开始工作,{$args} 是一个脚本块,它只是返回它所接收到的任何参数。&{args} 执行该脚本块。将其包装在 $() - $(&{$args}) 中使其成为子表达式,以便在字符串中扩展。由于给定的参数是哈希表,输出是一系列键/值对 - 传递给脚本块的展开操作的结果。 - mjolinor
我喜欢这个解决方案,你的解释很好。但是,如果您的变量是一个带有空格的字符串,我该如何封装参数,例如:"-P2 'This is P2' -P1 'This Is P1' -P3 'This is P3'"? - tommylux
你能改进这个吗?如果 ($StrArgs -is [Hashtable]) { $formattedParams = Foreach ($i in $StrArgs.keys) { "-${i}: "$($StrArgs.$i)"" }} Else { $formattedParams = $StrArgs } - tommylux

1
如果您正在尝试使用命名参数(-P1 1 -P2 2)与-FilePath一起使用,那么我发现这样可以解决问题。使用脚本块来运行文件,而不是使用-FilePath。
Invoke-Command -ComputerName server -ScriptBlock {& "D:\test.ps1" -P1 1 -P2 2 -P3 3}

1
这是一个简单的解决方案:

[PowerShell]::Create().AddCommand('D:\test.ps1').AddParameters(@{ P1 = 1; P2 = 2; P3 = 3 }).Invoke()

这是输出:
PS C:\Windows\system32> [PowerShell]::Create().AddCommand('D:\test.ps1').AddParameters(@{ P1 = 1; P2 = 2; P3 = 3 }).Invoke()
P1 Value :
1
P2 Value:
2
P3 Value :
3

只有这段代码才能让我从另一个外部函数中得到结果。 - Garric
由于执行的结果,Invoke-Command 无法将窗体返回给我。 - Garric
我的窗体在另一个线程上,但你的代码运行良好。 - Garric
是的,更多。您的解决方案不会破坏空格,不像 $sb = [scriptblock]::create(".{$(get-content $ScriptPath -Raw)} $(&{$args} @params)")。 - Garric

0
使用哈希表:
icm -ComputerName test -ScriptBlock{$args} -ArgumentList @{"p1"=1;"p2"=2;"p3"=3} 

请问您能否提供更多关于此事的细节? - Parveen Kumar
7
这个不起作用——整个哈希表被作为第一个参数传递。文档表示参数需要是逗号分隔的列表(非关联数组)(来源)。 - Eric Eskildsen

0

如果你愿意完全跳过 Invoke-Command...

你的脚本可以像这样:

([string]$args).split('-') | %{ 
    if ($_.Split(' ')[0].ToUpper() -eq "P1") { $P1 = $_.Split(' ')[1] } 
    elseif ($_.Split(' ')[0].ToUpper() -eq "P2") { $P2 = $_.Split(' ')[1] }
    elseif ($_.Split(' ')[0].ToUpper() -eq "P3") { $P3 = $_.Split(' ')[1] } 
}

Write-Output "P1 Value :" $P1
Write-Output "P2 Value :" $P2
Write-Output "P3 Value :" $P3

你可以这样调用它:

D:\test.ps1 -P1 1 -P2 2 -P3 3


0
使用哈希表,确实!
#TestPs1.ps1
Param (
    [string]$P3,
    [string]$P2,
    [string]$P1
)
Write-Output "P1 Value :" $P1
Write-Output "P2 Value:" $P2
Write-Output "P3 Value :" $P3

$params = @{
    P3 = 3
    P2 = 2
}
#(just to prove it doesn't matter which order you put them in)
$params["P1"] = 1;
#Trhough the use of the "Splat" operator, we can add parameters directly onto the module
& ".\TestPs1.ps1" @params

输出:

P1 Value :
1
P2 Value:
2
P3 Value :
3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接