从脚本中使用参数调用第二个脚本

82
我有一个脚本,它读取一个配置文件,生成一组名称-值对,我想将它们作为参数传递给第二个PowerShell脚本中的函数。
在设计时,我不知道将在此配置文件中放置哪些参数,因此在需要调用第二个PowerShell脚本的地方,我基本上只有一个变量,其中包含指向此脚本的路径,以及一个作为参数传递到路径变量所指定脚本的数组。
因此,包含指向第二个脚本路径的变量($scriptPath)可能具有如下值:
"c:\the\path\to\the\second\script.ps1"

包含参数的变量 ($argumentList) 可能长这样:

-ConfigFilename "doohickey.txt" -RootDirectory "c:\some\kind\of\path" -Max 11

我该如何从当前状态执行script.ps1并且传入所有来自$argumentList的参数?

我希望第二个脚本中的任何write-host命令都能在调用第一个脚本的控制台中可见。

我已经尝试过点号引用(dot-sourcing)、Invoke-Command、Invoke-Expression和Start-Job,但是没有一种方法不会产生错误。

例如,我认为最简单的路线是尝试使用以下方式调用Start-Job:

Start-Job -FilePath $scriptPath -ArgumentList $argumentList

...但是这会导致出现以下错误:

System.Management.Automation.ValidationMetadataException:
Attribute cannot be added because it would cause the variable
ConfigFilename with value -ConfigFilename to become invalid.

在这种情况下,“ConfigFilename”是第二个脚本定义的参数列表中的第一个参数,我的调用显然试图将其值设置为“-ConfigFilename”,这显然是要通过名称来识别参数,而不是设置其值。

我漏掉了什么?

编辑:

好的,这是一个模拟将要被调用的脚本的示例,在名为invokee.ps1的文件中。

Param(
[parameter(Mandatory=$true)]
[alias("rc")]
[string]
[ValidateScript( {Test-Path $_ -PathType Leaf} )]
$ConfigurationFilename,
[alias("e")]
[switch]
$Evaluate,
[array]
[Parameter(ValueFromRemainingArguments=$true)]
$remaining)

function sayHelloWorld()
{
    Write-Host "Hello, everybody, the config file is <$ConfigurationFilename>."
    if ($ExitOnErrors)
    {
        Write-Host "I should mention that I was told to evaluate things."
    }
    Write-Host "I currently live here: $gScriptDirectory"
    Write-Host "My remaining arguments are: $remaining"
    Set-Content .\hello.world.txt "It worked"
}

$gScriptPath = $MyInvocation.MyCommand.Path
$gScriptDirectory = (Split-Path $gScriptPath -Parent)
sayHelloWorld

这里是一个调用脚本的模拟,存储在名为invoker.ps1的文件中:

function pokeTheInvokee()
{
    $scriptPath = (Join-Path -Path "." -ChildPath "invokee.ps1")
    $scriptPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($scriptPath)
    
    $configPath = (Join-Path -Path "." -ChildPath "invoker.ps1")
    $configPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($configPath)
    
    $argumentList = @()
    $argumentList += ("-ConfigurationFilename", "`"$configPath`"")
    $argumentList += , "-Evaluate"

    Write-Host "Attempting to invoke-expression with: `"$scriptPath`" $argumentList"
    Invoke-Expression "`"$scriptPath`" $argumentList"
    Invoke-Expression ".\invokee.ps1 -ConfigurationFilename `".\invoker.ps1`" -Evaluate
    Write-Host "Invokee invoked."
}

pokeTheInvokee

当我运行invoker.ps1时,第一次调用Invoke-Expression时出现的错误是:

Invoke-Expression : You must provide a value expression on
the right-hand side of the '-' operator.
第二个调用工作得很好,但是一个显着的区别是第一个版本使用了路径中有空格的参数,而第二个版本则没有。我是否没有正确处理这些路径中的空格?

如果您为每个脚本提供源代码,那将会很有帮助。例如,创建一个POC(概念验证)项目来说明您的问题。然后我们可以测试和尝试,也许有人会找到解决方案或变通方法。 - Victor Zakharov
是的 - 我正在尝试制作一个迷你版本来进行说明。很快就会发布... - Hoobajoob
8个回答

72

啊哈。原来这只是一个简单的问题,脚本路径中有空格。

将Invoke-Expression行更改为:

Invoke-Expression "& `"$scriptPath`" $argumentList"

......足以启动它。感谢Neolisk提供的帮助和反馈!


4
补充你的答案,路径中有空格意味着必须使用引号。引号被 Invoke-Expression 视为字符串而不是命令。因此,如果要执行该字符串,需要使用“调用运算符”,即你示例开头的与号符号。这里提供了调用运算符的定义:http://ss64.com/ps/call.html。 - Aeropher
当我以这种方式尝试我的脚本时,它会出现语法错误,缺少结束引号。下面的方法2适用于我。 - Michele
1
我在我的脚本参数列表中的一个字符串参数中遇到了分号字符的问题。Invoke-Expression将其解释为语句分隔符,并仅将字符串的开头作为脚本参数传递。当我在我的代码中应用了这个答案后,它就正常工作了。 - Yuhis

51

Invoke-Expression应该可以完美地工作,只要确保你正确使用它。对于你的情况,应该是这样的:

Invoke-Expression "$scriptPath $argumentList"

我使用Get-Service测试了这种方法,看起来按预期工作。


Invoke-Expression "$scriptPath $argumentList" 立即返回,似乎没有运行脚本。如果我将其更改为 Invoke-Expression "powershell $scriptPath $argumentList",PowerGui 就会卡住。 - Hoobajoob
2
@Hoobajoob:Invoke-Expression不会立即返回,它会耐心等待被调用的脚本结束——我刚在一个POC项目上进行了测试。你所针对的脚本肯定有问题。 - Victor Zakharov
嗯 - 我已经以许多不同的方式从批处理文件、Visual Studio IDE、DOS命令行和PowerShell控制台会话运行了这个第二个脚本,但这是我第一次尝试从另一个PowerShell脚本运行它。脚本本身可能有哪些问题会导致无法通过Invoke-Expression成功启动它呢? - Hoobajoob
1
这只有在参数列表中的对象都是字符串类型时才有效,对吗? - David Klempfner
@Backwards_Dave: 这里的$scriptPath$argumentList都是字符串。一个字符串可以包含一个非字符串的参数(用字符串写)。希望它能回答你的问题。 - Victor Zakharov

38

实际上要简单得多:方法1:

Invoke-Expression $scriptPath $argumentList

方法2:

& $scriptPath $argumentList

方法三:

$scriptPath $argumentList

如果您的 scriptPath 中有空格,请不要忘记对其进行转义:`"$scriptPath`"


谢谢提供这些选项,我真的很喜欢另一个问题的答案中提供的额外选项。https://dev59.com/3pDea4cB1Zd3GeqPYji5#33206405 - dragon788

17

我们可以使用 splatting 来实现此目的:

& $command @args

其中@args自动变量 $args)被分解为参数数组。

在 PS 5.1 下。


16

以下是一个回答,涵盖了从一个 PowerShell 脚本中调用另一个 PowerShell 脚本的更一般性问题。如果您正在组合许多小型、狭义目的脚本的脚本,则可能需要这样做。

我发现只需使用点操作即可。也就是说,您只需执行以下操作:

# This is Script-A.ps1

. ./Script-B.ps1 -SomeObject $variableFromScriptA -SomeOtherParam 1234;

我发现所有的问答都很混乱和复杂,最终找到了上面的简单方法,它实际上就像在原始脚本中调用另一个脚本函数一样简单易懂。

点号源可以完整地“导入”其他脚本,使用以下代码:

. ./Script-B.ps1

现在就好像这两个文件合并了一样。

归根结底,我真正缺少的是应该构建一个可重用函数模块的概念。


& "./Script-B.ps1" -SomeObject $variableFromScriptA -SomeOtherParam 1234 - MortenB
如果我不需要将模块导出到其他系统,那么使用模块的优势是什么? - Blaisem

4

我尝试了使用Invoke-Expression命令的已接受解决方案,但它对我没有起作用,因为我的参数中有空格。我尝试解析参数并转义空格,但我无法正确地使其工作,而且在我看来这是一个非常不好的解决办法。

所以,在一些实验之后,我对这个问题的看法是:

function Invoke-Script
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]
        $Script,

        [Parameter(Mandatory = $false)]
        [object[]]
        $ArgumentList
    )

    $ScriptBlock = [Scriptblock]::Create((Get-Content $Script -Raw))
    Invoke-Command -NoNewScope -ArgumentList $ArgumentList -ScriptBlock $ScriptBlock -Verbose
}

# example usage
Invoke-Script $scriptPath $argumentList

这种解决方案唯一的缺点是你需要确保你的脚本没有“Script”或“ArgumentList”参数。

2
您可以像执行SQL查询一样执行它。 首先,构建您的命令/表达式并将其存储在变量中,然后执行/调用。
$command =  ".\yourExternalScriptFile.ps1" + " -param1 '$paramValue'"

这很简单,我认为不需要解释。现在已准备好执行您的命令了。

Invoke-Expression $command

我建议在这里捕获异常。

-1

我假设您想要从另一个 .ps1 文件运行 [$scriptPath 以及存储在 $argumentList 中的多个参数] 的 .ps1 文件

Invoke-Expression "& $scriptPath $argumentList"

这段代码应该可以正常工作


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