ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

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

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

您的解决方案 $PSScriptRoot 是正确的,无需进一步阅读。

P
Peter Mortensen

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,尽管在脚本的顶级范围内它们的行为相同(即为此目的唯一明智的打电话的地方)。在函数脚本块中调用时,前者返回空字符串,而后者返回函数体的/脚本块的定义作为字符串(一段 PowerShell 源代码)。
拆分路径文档说“父参数是默认的拆分位置参数”。并且 IME -Parent 确实没有必要。
Z
Zombo

如果您要创建 V2 模块,则可以使用名为 $PSScriptRoot 的自动变量。

从 PS > 帮助自动变量

$PSScriptRoot
       Contains the directory from which the script module is being executed.
       This variable allows scripts to use the module path to access other
       resources.

这是您在 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 使用不同的东西。
v2 中的@user $PSScriptRoot 仅适用于模块,如果您正在编写不在模块中的“普通”脚本,则需要 $MyInvocation.MyCommand.Definition,请参阅最佳答案。
@Lofful 我在“v2”中说它只是为模块定义的。您是说它是在 v3 中的模块外部定义的。我想我们说的是同一件事。 :)
Z
Zombo

对于 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) 来获取没有文件名的脚本目录。
P
Peter Mortensen

适用于 PowerShell 3+

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

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


它应该是 $script:PSScriptRoot 而不是 $global:PSScriptRoot,它仅适用于 ISE,但不适用于普通 PowerShell。
P
Peter Mortensen

也许我在这里遗漏了一些东西......但是如果您想要当前的工作目录,您可以使用:(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”参数以指定您在调用堆栈中的深度。
J
Julian

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

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

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


这是唯一对我有用的,试图从 STDIN 提供 powershell。
当您在 UNC 路径上下文中时,此解决方案也可以正常工作。
这显然是在拾取工作目录 - 而不是脚本所在的位置?
@monojohnny 是的,这基本上是当前工作目录,在从另一个位置调用脚本时将不起作用。
P
Peter Mortensen

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

$PSCommandPath | Split-Path -Parent

当这个 $PSScriptRoot 已经有一个自动变量时,为什么要这样做? $PSCommandPath & $PSScriptRoot 都是在 PowerShell v3 中引入的,所以从技术上讲,这对 OP 没有任何帮助,因为他们注意到 v2,但是对于像我一样偶然发现这个问题的未来人很有帮助
P
Peter Mortensen

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

我不确定其他人,但我在使用 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 不会自动更新目录。
B
Bruce Gavin

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

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

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


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

我总是使用同样适用于 PowerShell 和 ISE 的这个小片段:

# 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
R
Randy

使用所有这些答案和评论中的片段,我为将来看到这个问题的任何人整理了这个。它涵盖了其他答案中列出的所有情况,并且我添加了另一种我发现的故障保险。

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

    # If still not found
    # I found this can happen if running an exe created using PS2EXE module
    if(-not $ScriptPath) {
        $ScriptPath = [System.AppDomain]::CurrentDomain.BaseDirectory.TrimEnd('\')
    }

    # Return result
    return $ScriptPath
}

P
Peter Mortensen

我发现这里发布的旧解决方案不适用于 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"

R
Rudi Visser

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


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

如果要从相对于脚本运行位置的路径(例如从“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.

M
Milen
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