确定当前 PowerShell 脚本位置的最佳方法是啥?

Posted

技术标签:

【中文标题】确定当前 PowerShell 脚本位置的最佳方法是啥?【英文标题】:What's the best way to determine the location of the current PowerShell script?确定当前 PowerShell 脚本位置的最佳方法是什么? 【发布时间】:2011-07-24 21:24:46 【问题描述】:

每当我需要引用一个通用模块或脚本时,我喜欢使用相对于当前脚本文件的路径。这样,我的脚本总能在库中找到其他脚本。

那么,确定当前脚本目录的最佳标准方法是什么?目前,我正在做:

$MyDir = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition)

我知道在模块 (.psm1) 中您可以使用 $PSScriptRoot 来获取此信息,但这不会在常规脚本(即 .ps1 文件)中设置。

获取当前 PowerShell 脚本文件位置的规范方法是什么?

【问题讨论】:

How can i get the file system location of a powershell script?的可能重复 您的解决方案 $PSScriptRoot 正确的,无需进一步阅读。 【参考方案1】:

PowerShell 3+

# This is an automatic variable set to the current file's/module's directory
$PSScriptRoot

PowerShell 2

在 PowerShell 3 之前,没有比查询 MyInvocation.MyCommand.Definition 一般脚本的属性。我在我拥有的每个 PowerShell 脚本的顶部都有以下行:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition

【讨论】:

Split-Path 在这里是做什么用的? Split-Path-Parent 参数一起使用,以返回当前目录而不包含当前正在执行的脚本的名称。 注意:在 Linux/macOS 上使用 PowerShell,您的脚本必须具有 .ps1 扩展名,以便填充 PSScriptRoot/MyInvocation 等。在此处查看错误报告:github.com/PowerShell/PowerShell/issues/4217 还有一个可能有趣的问题:$PSScriptRoot 更接近的 v2 近似值是(Split-Path -Parent 应用于)$MyInvocation.MyCommand.Path,而不是 $MyInvocation.MyCommand.Definition,尽管在 a脚本它们的行为相同(这是为此目的唯一合理的调用位置)。在函数脚本块中调用时,前者返回空字符串,而后者返回函数体的/脚本块的定义作为字符串(一段 PowerShell 源代码)。 Split-Path 文档说“父参数是默认的分割位置参数。”和 IME -Parent 确实没有必要。【参考方案2】:

如果您正在创建 V2 模块,您可以使用名为 $PSScriptRoot.

来自 PS > 帮助自动变量

$PSScriptRoot 包含正在执行脚本模块的目录。 此变量允许脚本使用模块路径访问其他 资源。

【讨论】:

这就是你在 PS 3.0 中所需要的:$PSCommandPath Contains the full path and file name of the script that is being run. This variable is valid in all scripts. 刚刚测试了 $PSScriptRoot 并按预期工作。但是,如果您在命令行运行它,它会给您一个空字符串。如果在脚本中使用并执行脚本,它只会给你结果。这就是它的意义...... 我很困惑。这个答案说要使用 PSScriptRoot for V2。另一个答案说 PSScriptRoot 适用于 V3+,并为 v2 使用不同的东西。 @user $PSScriptRoot v2 仅适用于 modules,如果您正在编写不在模块中的“普通”脚本,则需要 $MyInvocation.MyCommand.Definition,请参阅顶部回答。 @Lofful 我在“v2”中说过它只是为模块定义的。您是说它是在 v3 中的模块外部定义的。我想我们说的是同一件事。 :)【参考方案3】:

对于 PowerShell 3.0

$PSCommandPath
    Contains the full path and file name of the script that is being run. 
    This variable is valid in all scripts.

那么函数是:

function Get-ScriptDirectory 
    Split-Path -Parent $PSCommandPath

【讨论】:

更好的是,使用 $PSScriptRoot。它是当前文件/模块的目录。 这个命令包含了脚本的文件名,这让我大吃一惊,直到我意识到这一点。当您需要路径时,您可能也不希望其中包含脚本名称。至少,我想不出你想要那个的理由。 $PSScriptRoot 不包括文件名(从其他答案中收集)。 $PSScriptRoot 在常规 PS1 脚本中为空。不过,$PSCommandPath 有效。根据其他帖子中给出的描述,预计两者的行为。也可以只使用 [IO.Path]::GetDirectoryName($PSCommandPath) 来获取没有文件名的脚本目录。【参考方案4】:

对于 PowerShell 3+

function Get-ScriptDirectory 
    if ($psise) 
        Split-Path $psise.CurrentFile.FullPath
    
    else 
        $global:PSScriptRoot
    

我已将此功能放在我的个人资料中。它也可以在 ISE 中使用 F8/Run Selection。

【讨论】:

【参考方案5】:

也许我在这里遗漏了一些东西......但如果你想要当前的工作目录,你可以使用这个:(Get-Location).Path 用于字符串,或Get-Location 用于对象。

除非你指的是这样的东西,我在再次阅读问题后理解。

function Get-Script-Directory

    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path

【讨论】:

获取当前位置用户运行脚本的位置。不是脚本文件本身的位置。 function Get-Script-Directory $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value return Split-Path $scriptInvocation.MyCommand.Path $hello = "hello" Write-Host (Get-Script-Directory) Write-Host $hello 保存并从不同的目录运行它。您将显示脚本的路径。 这是一个很好的功能,可以满足我的需求,但是我如何共享它并在我的所有脚本中使用它呢?这是一个先有鸡还是先有蛋的问题:我想使用一个函数来找出我当前的位置,但我需要我的位置来加载该函数。 注意:此函数的调用必须在脚本的顶层,如果它嵌套在另一个函数中,则必须更改“-Scope”参数以指定在你是调用堆栈。【参考方案6】:

与已经发布的答案非常相似,但管道似乎更像 PowerShell:

$PSCommandPath | Split-Path -Parent

【讨论】:

【参考方案7】:

我使用automatic variable $ExecutionContext。 它适用于 PowerShell 2 及更高版本。

 $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath('.\')

$ExecutionContext 包含一个 EngineIntrinsics 对象,该对象表示 Windows PowerShell 主机的执行上下文。你可以 使用此变量查找执行对象 可用于 cmdlet。

【讨论】:

这是唯一对我有用的,试图从 STDIN 提供 powershell。 当您在 UNC 路径上下文中时,此解决方案也可以正常工作。 这显然是在拾取工作目录 - 而不是脚本所在的位置? @monojohnny 是的,这基本上是当前的工作目录,并且在从其他位置调用脚本时不起作用。【参考方案8】:

我花了一些时间来开发一些可以接受的答案并将其转变为强大功能的东西。

我不确定其他人,但我在使用 PowerShell 版本 2 和 3 的机器的环境中工作,因此我需要同时处理这两个版本。以下函数提供了优雅的回退:

Function Get-PSScriptRoot

    $ScriptRoot = ""

    Try
    
        $ScriptRoot = Get-Variable -Name PSScriptRoot -ValueOnly -ErrorAction Stop
    
    Catch
    
        $ScriptRoot = Split-Path $script:MyInvocation.MyCommand.Path
    

    Write-Output $ScriptRoot

这也意味着该函数引用的是脚本范围,而不是 Michael Sorens in one of his blog posts 概述的父范围。

【讨论】:

谢谢! “$script:” 是我在 Windows PowerShell ISE 中使用它所需要的。 谢谢。这是唯一对我有用的。我通常必须先 CD 到脚本所在的目录,然后才能使用 Get-location 之类的东西。我很想知道为什么 PowerShell 不会自动更新目录。【参考方案9】:

我需要知道脚本名称及其执行位置。

当从主脚本和导入的 .PSM1 库文件的主行调用时,在 MyInvocation 结构中添加前缀“$global:”会返回完整路径和脚本名称。它也适用于导入库中的函数。

经过一番折腾,我决定使用 $global:MyInvocation.InvocationName。 它可以与 CMD 启动、Run With Powershell 和 ISE 一起可靠地工作。 本地和 UNC 启动都返回正确的路径。

【讨论】:

Split-Path -Path $($global:MyInvocation.MyCommand.Path) 非常感谢。其他解决方案返回调用应用程序的路径。 小提示:在 ISE 中使用 F8/Run Selection 调用此函数将触发 ParameterArgumentValidationErrorNullNotAllowed 异常。【参考方案10】:

我总是以同样的方式使用这个适用于 PowerShell 和 ISE 的小 sn-p:

# Set active path to script-location:
$path = $MyInvocation.MyCommand.Path
if (!$path) $path = $psISE.CurrentFile.Fullpath
if ($path)  $path = Split-Path $path -Parent
Set-Location $path

【讨论】:

Split-Path 部分看起来不错,但速度很慢。如果你需要它快点,然后像这样使用 RegEx: $path = [regex]::Match($path, '.+(?=\)').value【参考方案11】:

使用所有这些答案和 cmets 中的片段,我为将来看到这个问题的任何人整理了这个。它涵盖了其他答案中列出的所有情况

    # If using ISE
    if ($psISE) 
        $ScriptPath = Split-Path -Parent $psISE.CurrentFile.FullPath
    # If Using PowerShell 3 or greater
     elseif($PSVersionTable.PSVersion.Major -gt 3) 
        $ScriptPath = $PSScriptRoot
    # If using PowerShell 2 or lower
     else 
        $ScriptPath = split-path -parent $MyInvocation.MyCommand.Path
    

【讨论】:

【参考方案12】:

我发现此处发布的旧解决方案不适用于 PowerShell V5。我想出了这个:

try 
    $scriptPath = $PSScriptRoot
    if (!$scriptPath)
    
        if ($psISE)
        
            $scriptPath = Split-Path -Parent -Path $psISE.CurrentFile.FullPath
        
        else 
            Write-Host -ForegroundColor Red "Cannot resolve script file's path"
            exit 1
        
    

catch 
    Write-Host -ForegroundColor Red "Caught Exception: $($Error[0].Exception.Message)"
    exit 2


Write-Host "Path: $scriptPath"

【讨论】:

【参考方案13】:

如果任何其他方法失败,您也可以考虑split-path -parent $psISE.CurrentFile.Fullpath。特别是,如果你运行一个文件来加载一堆函数,然后在 ISE shell 中执行这些函数(或者如果你运行选择),那么上面的 Get-Script-Directory 函数似乎不起作用。

【讨论】:

$PSCommandPath 将在 ISE 中工作,只要您先保存脚本并执行整个文件。否则,您实际上并没有执行脚本;您只是将命令“粘贴”到 shell 中。 @Zenexer 我认为那是我当时的目标。虽然如果我的目标与最初的目标不匹配,除了偶尔的 Google 员工之外,这可能不会有太大帮助......【参考方案14】:

如果您想从相对于脚本运行位置的路径加载模块,例如从“lib”子文件夹”,您需要使用以下方法之一:

$PSScriptRoot 在作为脚本调用时起作用,例如通过 PowerShell 命令 $psISE.CurrentFile.FullPath 在 ISE 内运行时有效

但是,如果您不在其中,并且只是在 PowerShell shell 中输入内容,则可以使用:

pwd.Path

您可以将这三个变量之一分配给一个名为 $base 的变量,具体取决于您运行的环境,如下所示:

$base=$(if ($psISE) Split-Path -Path $psISE.CurrentFile.FullPath else $(if ($global:PSScriptRoot.Length -gt 0) $global:PSScriptRoot else $global:pwd.Path))

然后在你的脚本中,你可以像这样使用它:

Import-Module $base\lib\someConstants.psm1
Import-Module $base\lib\myCoolPsModule1.psm1
#etc.

【讨论】:

【参考方案15】:
function func1() 

   $inv = (Get-Variable MyInvocation -Scope 1).Value
   #$inv.MyCommand | Format-List *   
   $Path1 = Split-Path $inv.scriptname
   Write-Host $Path1


function Main()

    func1


Main

【讨论】:

以上是关于确定当前 PowerShell 脚本位置的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确确定当前脚本目录?

PowerShell脚本授权最佳实践

如何等到当前位置对话框回答运行方法?

在android中获取当前准确位置的最佳方法是啥?

确定移动网站用户的位置

在 PHP 中,使用 CLI 时确保当前工作目录与脚本相同的最佳方法