如何使用 powershell v2+ 检测脚本是否是点源脚本或作为模块的一部分加载?

Eri*_*ris 5 powershell powershell-2.0 powershell-3.0

给定一个 ps1 文件作为模块的一部分,代码如下:

function Get-Greeting {
    'Hello {0}' -f $Env:Username
}
Export-ModuleMember -Function:Get-Greeting
Run Code Online (Sandbox Code Playgroud)

当作为模块的一部分加载时,一切都很好。如果我对脚本进行点源,我会得到

Export-ModuleMember : The Export-ModuleMember cmdlet can only be called from inside a module.
Run Code Online (Sandbox Code Playgroud)

我知道我可以在 Export-ModuleMember 上添加 -ErrorAction:Ignore,但这不是重点。我想让脚本以不同的方式运行,无论它是导入的还是点源的。

在版本 2 中,人们可能可以围绕 编写一个 hack $PSScriptRoot,但这只是一个 hack,并且在版本 3 中不起作用,在版本 3 中它们“固定”$PSScriptRoot为永不为空。我尝试查看 中的各种项目$MyInvocation,但要么我错过了一些东西,要么它没有任何有用的东西。

我还尝试Get-Variable在模块内部和外部运行,但再次发现没有差异。

Import-Module当运行 as vs时我错过了什么不同的地方. myscript.ps1

Eri*_*ris 3

看来我找到了这个问题的答案。我遗漏了一些东西$MyInvocation,因为我在错误的范围内寻找。给定以下文件:

# .\moduleDetection\moduleDetection.ps1
$ErrorActionPreference = 'SilentlyContinue'; 
'=== Parent Invocation.MyCommand: [{0}]' -f (Get-Variable -Name:MyInvocation -Scope:1 -ValueOnly | Select -Expand MyCommand) | Out-Host;
Run Code Online (Sandbox Code Playgroud)

# .\moduleDotSource\moduleDotSource.psm1
. "$PSScriptRoot\..\moduleDetection\moduleDetection.ps1"
Run Code Online (Sandbox Code Playgroud)

# .\module-test.ps1
$Error.Clear()

Write-Host "Powershell Version 3:"
Write-Host "Powershell -Command Import-Module (direct)"
powershell -nologo -noprofile -Command { Import-Module .\moduledetection }

Write-Host "Powershell -Command Import-Module (dot-source)"
powershell -nologo -noprofile -Command { Import-Module .\moduleDotSource }

Write-Host "Powershell -File ...moduledetection.ps1"
powershell -nologo -noprofile -File .\moduledetection\moduleDetection.ps1

Write-Host "Powershell Dot-Source"
powershell -nologo -noprofile -Command { . .\moduledetection\moduleDetection.ps1 }

Write-Host ""
Write-Host "Powershell Version 2:"
Write-Host "Powershell -Version 2 -Command Import-Module"
powershell -version 2.0 -nologo -noprofile -Command { Import-Module .\moduledetection }

Write-Host "Powershell -Version 2 -Command Import-Module (dot-source)"
powershell -version 2.0 -nologo -noprofile -Command { Import-Module .\moduleDotSource }

Write-Host "Powershell -Version 2 -File ...moduledetection.ps1"
powershell -version 2.0 -nologo -noprofile -File .\moduledetection\moduleDetection.ps1

Write-Host "Powershell -Version 2 Dot-Source"
powershell -version 2.0 -nologo -noprofile -Command { . .\moduledetection\moduleDetection.ps1 }
Run Code Online (Sandbox Code Playgroud)

最后,使用正确的名称从原始 ps1 到 psm1 建立符号链接以作为直接模块加载。

cmd /c mklink .\moduledetection\moduleDetection.ps1 .\moduledetection\moduleDetection.psm1
Run Code Online (Sandbox Code Playgroud)

输出显示父作用域具有密钥。

输出:

Powershell Version 3:
Powershell -Command Import-Module (direct)
=== Parent Invocation.MyCommand: [ Import-Module .\moduledetection ]
Powershell -Command Import-Module (dot-source)
=== Parent Invocation.MyCommand: [ Import-Module .\moduleDotSource ]
Powershell -File ...moduledetection.ps1
Powershell Dot-Source

Powershell Version 2:
Powershell -Version 2 -Command Import-Module
=== Parent Invocation.MyCommand: [ Import-Module .\moduledetection ]
Powershell -Version 2 -Command Import-Module (dot-source)
=== Parent Invocation.MyCommand: [ Import-Module .\moduleDotSource ]
Powershell -Version 2 -File ...moduledetection.ps1
Powershell -Version 2 Dot-Source
Run Code Online (Sandbox Code Playgroud)

正如我们从显示的输出(在 Server 2008 R2 上运行)中看到的,父作用域$MyInvocation.MyCommand包含导入模块语句。我还没有测试它,但我从中推断,如果它是通过链式点源的多重间接,我们可以继续获取父作用域,直到我们得到 Null 或导入模块。

现在我们知道如何检测我们是否在模块中,并且通过其他资源也可以了解我们是否是点源、直接执行和/或通过模块加载。