PowerShell:从脚本目录运行命令

181
我有一个PowerShell脚本,它在当前目录下执行一些操作。所以当在该目录中时,运行.\script.ps1可以正常工作。
现在我想从不同的目录调用该脚本,而不改变脚本的引用目录。所以我想调用..\..\dir\script.ps1,并且仍然希望该脚本像在其目录中调用时一样工作。
我应该如何实现这一点?或者我应该如何修改脚本才能在任何目录中运行?
7个回答

244

您的意思是您希望获得脚本自身的路径,以便您可以引用脚本旁边的文件吗?请尝试这个:

$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
Write-host "My directory is $dir"

通过 $MyInvocation 及其属性,您可以获取许多信息。

如果您想引用当前工作目录中的文件,可以使用 Resolve-Path 或 Get-ChildItem:

$filepath = Resolve-Path "somefile.txt"

编辑(根据OP的评论):

# temporarily change to the correct folder
Push-Location $dir

# do stuff, call ant, etc

# now back to previous directory
Pop-Location

也许还有其他使用Invoke-Command实现类似功能的方法。


1
嗯,好吧,也许我应该在我的脚本中更明确一些。实际上,这是一个调用带有一些参数的ant的脚本。所以我必须从那个文件夹中调用ant,以确保它能正确找到配置文件。理想情况下,我正在寻找临时更改脚本内部执行目录的方法。 - poke
4
那么在这种情况下,您将需要使用 Push-LocationPop-Location - JohnL
6
注意,$MyInvocation 是与上下文相关的。通常我会使用 $ScriptPath = (Get-Variable MyInvocation -Scope Script).Value.MyCommand.Path 这个语句,它可以在任何嵌套或函数中都能正常工作。 - user90843
1
作为一行代码 - $MyInvocation.MyCommand.Path | Split-Path | Push-Location - sashoalm

60
有一些回答得到了很多票数,但是当我阅读你的问题时,我认为你想知道脚本所在的目录,而不是脚本正在运行的目录。你可以使用powershell的自动变量来获取这些信息。
$PSScriptRoot     # the directory where the script exists, not the
                  # target directory the script is running in
$PSCommandPath    # the full path of the script

例如,我有一个名为$profile的脚本,它会找到一个Visual Studio解决方案文件并启动它。我想要在启动解决方案文件后保存完整路径。但是我想要将文件保存在原始脚本所在的位置。所以我使用了$PsScriptRoot

2
我想要的答案。其他人似乎在解决比直接问题更多的问题。 - Arvindh Mani

42
如果您要调用本地应用程序,则需要关注[Environment]::CurrentDirectory而不是PowerShell中的$PWD当前目录。由于各种原因,PowerShell在Set-Location或Push-Location时不会设置进程的当前工作目录,因此,如果您运行期望其设置为当前目录的应用程序(或cmdlet),则需要确保您进行设置。
在脚本中,您可以这样做:
$CWD = [Environment]::CurrentDirectory

Push-Location $MyInvocation.MyCommand.Path
[Environment]::CurrentDirectory = $PWD
##  Your script code calling a native executable
Pop-Location

# Consider whether you really want to set it back:
# What if another runspace has set it in-between calls?
[Environment]::CurrentDirectory = $CWD

没有绝对可靠的替代方法。我们中的许多人在提示函数中放置一条线来设置 [Environment]::CurrentDirectory ... 但是当您在脚本内更改位置时,这并不能帮助您。

关于 PowerShell 自动不设置它的原因,有两点需要注意:

  1. PowerShell 可以是多线程的。您可以在单个进程中同时运行多个 Runspace(请参见 RunspacePool 和 PSThreadJob 模块)。每个 runspace 都有自己的 $PWD 当前工作目录,但只有一个进程和一个环境。
  2. 即使您是单线程的,$PWD 也不总是合法的 CurrentDirectory(例如,您可能会 CD 进入注册表提供程序)。

如果您想将它放到提示符中(仅在主运行空间,单线程中运行),则需要使用:

[Environment]::CurrentDirectory = Get-Location -PSProvider FileSystem

4
由于路径提供程序的存在,它不会改变处理工作目录的过程:您可能正在将 CD 命令用于注册表、SQL 数据库或 IIS Hive 等等。 - piers7
1
是的,我知道,这就是上一篇“最后说明”的重点。他们本可以(就像我在我的提示函数中所做的那样)将其更新到当前位置的FileSystem提供程序,就像世界上其他所有shell一样。 - Jaykul
2
神圣的脚本之神啊,我太失望了 .\_/. —— 因为这浪费了我半天的时间!人们,认真点?真的?.. - ulidtko
2
现在大多数情况下都不需要了,更现代的PowerShell版本在启动二进制应用程序时会设置工作目录。 - Jaykul
1
该方法无法恢复原始的 [Environment]::CurrentDirectory,当它与 $PWD 不同时。正确的做法是将其存储到变量 $origEnvDir = [Environment]::CurrentDirectory 中,然后稍后恢复 [Environment]::CurrentDirectory = $origEnvDir - Strom
显示剩余4条评论

18

这将很好地发挥作用。

Push-Location $PSScriptRoot

Write-Host CurrentDirectory $CurDir

5
别忘了在结尾处调用Pop-Location,以将路径恢复到其原始状态。 - Lam Le

12

我经常使用以下代码来导入与运行脚本相同目录下的模块。它将首先获取 PowerShell 正在运行的目录:

$currentPath=Split-Path ((Get-Variable MyInvocation -Scope 0).Value).MyCommand.Path

import-module "$currentPath\sqlps.ps1"


你想将-Scope设置为Script。 - user90843

5

我将@ JohnL的解决方案压缩成了一行代码:

$MyInvocation.MyCommand.Path | Split-Path | Push-Location

2

我曾经寻找了一段时间的解决方案,想要在CLI中实现而不需要任何脚本。这是我的做法 xD:

  • 导航到你想要运行脚本的文件夹(重要的是你可以使用制表符补全)

    ..\..\dir

  • 现在将位置用双引号括起来,在引号内添加cd,这样我们就可以调用另一个powershell实例。

    "cd ..\..\dir"

  • 添加另一个命令来运行脚本,用;分隔,它是powershell中的命令分隔符

    "cd ..\..\dir\; script.ps1"

  • 最后用另一个powershell实例运行它

    start powershell "cd..\..\dir\; script.ps1"

这将打开一个新的powershell窗口,进入..\..\dir,运行script.ps1并关闭窗口。


请注意,分号“;”只是分隔命令的符号,就像您一个接一个地输入它们一样,如果第一个失败了,第二个将运行,然后是下一个,再下一个... 如果您想保持新的powershell窗口打开,则在传递的命令中添加-noexit。请注意,我首先导航到所需的文件夹,以便我可以使用制表符完成(您无法在双引号中使用)。
使用双引号"",以便您可以传递名称中带有空格的目录,例如, start powershell "-noexit cd '..\..\my dir'; script.ps1"

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