在Powershell ISE中从另一个PS1脚本调用PowerShell脚本PS1

230

我想在PowerShell ISE中的第二个脚本myScript2.ps1内调用myScript1.ps1脚本。

以下代码在管理员模式下的PowerShell中可以正常工作,但在PowerShell ISE中无法正常运行:

#Call myScript1 from myScript2
invoke-expression -Command .\myScript1.ps1

当我在PowerShell ISE中执行MyScript2.ps1时,出现以下错误:

该项“.\myScript1.ps1”未被识别为 cmdlet、函数、脚本文件或可运行的程序的名称。请检查名称的拼写,如果包含路径,请验证路径是否正确,然后重试。

14个回答

120
为了在同一目录下运行脚本。
PowerShell 3.0及更高版本中,您可以使用自动变量$PSScriptRoot
$PSScriptRoot/myScript1.ps1

在 PowerShell 1.0 和 2.0 中,您应该使用此特定属性:
& "$(Split-Path $MyInvocation.MyCommand.Path)/myScript1.ps1"

你应该使用这个而不是其他的原因可以用这个示例脚本来说明。

## ScriptTest.ps1
Write-Host "InvocationName:" $MyInvocation.InvocationName
Write-Host "Path:" $MyInvocation.MyCommand.Path

这里是一些结果。

PS C:\Users\JasonAr> .\ScriptTest.ps1
调用名称:.\ScriptTest.ps1
路径:C:\Users\JasonAr\ScriptTest.ps1
PS C:\Users\JasonAr> . .\ScriptTest.ps1 调用名称:. 路径:C:\Users\JasonAr\ScriptTest.ps1
PS C:\Users\JasonAr> & ".\ScriptTest.ps1" 调用名称:& 路径:C:\Users\JasonAr\ScriptTest.ps1

PowerShell 3.0 及更高版本中,您可以使用自动变量 $PSScriptRoot

## ScriptTest.ps1
Write-Host "Script:" $PSCommandPath
Write-Host "Path:" $PSScriptRoot

PS C:\Users\jarcher> .\ScriptTest.ps1 脚本:C:\ Users \ jarcher \ ScriptTest.ps1 路径:C:\ Users \ jarcher

一个晚期的补充:如果你担心方差(或者,实际上,只是想要“稳定”的代码),你可能想使用“Write-Output”而不是“Write-Host”。 - KlaymenDK
37
回答:请看一下Split-Path的实际使用示例。您还需要展示如何在一个脚本中调用另一个脚本。 - Jeremy
2
这绝对不是对所问问题的回答。你只是在解释如何查找当前正在执行脚本的路径,而问题是如何在当前正在执行的脚本中执行一个单独的脚本。令人困惑的是,它已经得到了100多个赞... - adamency
它绝对回答了这个问题。问题的关键在于如何找到兄弟脚本的位置以便运行它。问问题的人接受了我的答案,因为它帮助他们解决了问题。 - JasonMArcher
1
您需要在任何变量前加上 "& "。在这个示例中,应该是:"& $PSScriptRoot/myScript1.ps1" - Waldron

107

我正在从myScript2.ps1调用myScript1.ps1。

假设这两个脚本都在同一位置,首先使用以下命令获取脚本的位置:

$PSScriptRoot

然后,像这样添加您想要调用的脚本名称:

& "$PSScriptRoot\myScript1.ps1"

这应该可以运行。


10
"& `$PSScriptRoot\myScript1.ps1"足够了。 - Weihui Guo
2
& $PSScriptRoot\myScript1.ps1 就足够了。 - Gregor Simončič
9
仅当变量中不包含空格字符时才这样做。如果变量包含空格,未加引号的命令可能会由于将命令“分成两部分”而导致语法错误。保留引号是最安全的做法。 - Adrian Wiik

45

MyScript1.ps1的当前路径与myScript2.ps1不同。您可以获取MyScript2.ps1的文件夹路径,将其连接到MyScript1.ps1,然后执行它。两个脚本必须在相同的位置。

## MyScript2.ps1 ##
$ScriptPath = Split-Path $MyInvocation.InvocationName
& "$ScriptPath\MyScript1.ps1"

我该如何初始化$MyInvocation变量? - Nicola C.
1
你不需要,它是一个自动变量。 - Shay Levy
它可以工作,但在调用脚本之前会出现以下错误:Split-Path:无法将参数“Path”绑定到参数,因为它是一个空字符串。 在第4行第25个字符处:
  • $ScriptPath = Split-Path <<<< $MyInvocation.InvocationName
    • CategoryInfo : InvalidData: (:) [Split-Path], ParameterBindingValidationException
    • FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.SplitPathCommand
- Nicola C.
创建一个新脚本,将 $MyInvocation.InvocationName 放入其中并运行该脚本。您能获取到脚本的路径吗? - Shay Levy
@JasonMArcher - 为什么要改成这样呢?据我所知,两者都会产生相同的输出结果? - manojlds
增加了一个答案来解释事情。 - JasonMArcher

23

一行解决方案:

& ((Split-Path $MyInvocation.InvocationName) + "\MyScript1.ps1")

1
这很好,但如果脚本位于同一目录中,为什么不只是& '.\MyScript1.ps' - JoePC
6
这句话的意思是“这是使用当前目录而不是脚本目录。当然,它们通常是相同的...但并不总是!”。需要注意的是,该语句强调了当前目录和脚本目录之间的区别,尽管它们可能经常相同。 - noelicus
我对同一行代码提出疑问 - 如果您在调用它之前运行“Set-Location”命令以移动到该文件夹,那么这应该通过设置位置并同时使其成为当前位置来实现。 - Jamie
首先,这不再是一行代码了 :). 但是,它只适用于您不介意更改工作目录的情况,这是相当假设且不良实践。 - noelicus

18

这只是为了在另一个文件中传递参数而提供的额外信息

您期望在哪里使用参数?

PrintName.ps1

Param(
    [Parameter( Mandatory = $true)]
    $printName = "Joe"    
)


Write-Host $printName

如何调用文件

Param(
    [Parameter( Mandatory = $false)]
    $name = "Joe"    
)


& ((Split-Path $MyInvocation.InvocationName) + "\PrintName.ps1") -printName $name
如果没有提供任何输入,它将默认为 "Joe" 并将其作为参数传递给 PrintName.ps1 文件中的 printName 参数,该参数将打印出 "Joe" 字符串。

9

要在与调用者相同的文件夹(或子文件夹)中轻松执行脚本文件,您可以使用以下方法:

# Get full path to the script:
$ScriptRoute = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, "Scriptname.ps1"))

# Execute script at location:
&"$ScriptRoute"

5
你可能已经找到了答案,但这是我做的方法。 我通常在我的安装脚本开头放置以下行:
```bash set -e ```
这一行将使脚本在出现错误时立即停止执行。
if(!$PSScriptRoot){ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent } #In case if $PSScriptRoot is empty (version of powershell V.2).  

那么我可以使用$PSScriptRoot变量作为当前脚本(路径)的位置,例如下面的示例:

if(!$PSScriptRoot){ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent } #In case if $PSScriptRoot is empty (version of powershell V.2).  

Try {
If (Test-Path 'C:\Program Files (x86)') {
    $ChromeInstallArgs= "/i", "$PSScriptRoot\googlechromestandaloneenterprise64_v.57.0.2987.110.msi", "/q", "/norestart", "/L*v `"C:\Windows\Logs\Google_Chrome_57.0.2987.110_Install_x64.log`""
    Start-Process -FilePath msiexec -ArgumentList $ChromeInstallArgs -Wait -ErrorAction Stop
    $Result= [System.Environment]::ExitCode
} Else {
    $ChromeInstallArgs= "/i", "$PSScriptRoot\googlechromestandaloneenterprise_v.57.0.2987.110.msi", "/q", "/norestart", "/L*v `"C:\Windows\Logs\Google_Chrome_57.0.2987.110_Install_x86.log`""
    Start-Process -FilePath msiexec -ArgumentList $ChromeInstallArgs -Wait -ErrorAction Stop
    $Result= [System.Environment]::ExitCode
    }

} ### End Try block


Catch  {
    $Result = [System.Environment]::Exitcode
    [System.Environment]::Exit($Result)
   }
[System.Environment]::Exit($Result)

在您的情况下,您可以将

Start-process...行替换为

Invoke-Expression $PSScriptRoot\ScriptName.ps1

您可以在微软网站上阅读有关$MYINVOCATION和$PSScriptRoot自动变量的更多信息:https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables

4
我提交我的示例供考虑。这是我在制作工具时从控制器脚本中调用一些代码的方式。执行实际操作的脚本也需要接受参数,所以这个示例展示了如何传递它们。它假定被调用的脚本与控制器脚本(进行调用的脚本)在同一个目录中。
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string[]]
$Computername,

[Parameter(Mandatory = $true)]
[DateTime]
$StartTime,

[Parameter(Mandatory = $true)]
[DateTime]
$EndTime
)

$ZAEventLogDataSplat = @{
    "Computername" = $Computername
    "StartTime"    = $StartTime
    "EndTime"      = $EndTime
}

& "$PSScriptRoot\Get-ZAEventLogData.ps1" @ZAEventLogDataSplat

以上是一个控制器脚本,接受3个参数。这些参数在param块中定义。控制器脚本然后调用名为Get-ZAEventLogData.ps1的脚本。为了举例,此脚本也接受相同的3个参数。当控制器脚本调用执行工作的脚本时,需要调用它并传递参数。上面展示了我如何通过splatting来做到这一点。


3

我曾经遇到过类似的问题,并且通过以下方式解决了它。

我的工作目录是一个通用脚本文件夹和几个特定脚本文件夹在同一根目录下,我需要调用特定的脚本文件夹(该文件夹使用特定问题的参数调用通用脚本)。 因此,工作目录如下:

\Nico\Scripts\Script1.ps1
             \Script2.ps1
      \Problem1\Solution1.ps1
               \ParameterForSolution1.config
      \Problem2\Solution2.ps1
               \ParameterForSolution2.config

Solutions1和Solutions2调用Scripts文件夹中的PS1文件,加载存储在ParameterForSolution中的参数。 因此,在PowerShell ISE中运行以下命令:

.\Nico\Problem1\Solution1.PS1

而Solution1.PS1中的代码如下:

# This is the path where my script is running
$path = split-path -parent $MyInvocation.MyCommand.Definition

# Change to root dir
cd "$path\..\.."

$script = ".\Script\Script1.PS1"

$parametro = "Problem1\ParameterForSolution1.config"
# Another set of parameter Script1.PS1 can receive for debuggin porpuose
$parametro +=' -verbose'

Invoke-Expression "$script $parametro"

2

我曾经遇到过这个问题。但我并没有使用任何巧妙的$MyInvocation方法来解决它。如果您通过右键单击脚本文件并选择编辑来打开ISE,然后从ISE中打开第二个脚本,您可以通过使用正常的.\script.ps1语法从一个脚本中调用另一个脚本。

我猜ISE有一个当前文件夹的概念,像这样打开文件将当前文件夹设置为包含脚本的文件夹。

当我在正常使用时从一个脚本中调用另一个脚本时,我只使用.\script.ps1,我认为修改脚本以使其在ISE中正常工作是不正确的...


因为问题在于脚本无法在不运行它所在文件夹的情况下正常工作,所以我对此进行了点踩。这与修改脚本以在ISE中工作无关,而是要修改脚本使其具有可移植性和灵活性,能够处理从不同目录运行的情况。在这种情况下,代码的行为不应取决于您从哪里调用它。 - Layne Bernardo

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