有一种方法可以让powershell模块进入其调用者的范围吗?

时间:2017-10-02 15:04:52

标签: powershell powershell-module

我有一系列实用程序函数和其他代码,我将这些代码写入我编写的每个PowerShell文件中。由于影响它的调用者范围变量,我开始考虑将其更改为PowerShell模块。

我遇到了一些我在其中做的特殊事情的问题,其中我确实想要在范围之间进行一些交互。我想知道是否还有“了解”模块调用者的范围,以便在转移到PowerShell模块时保留此功能?

如果没有,那么我是将这些更专业的东西保存在点源文件中并将更传统的实用功能移动到模块中的最佳途径吗?以下是不容易转移到模块的内容:

  • 设置严格模式和错误操作首选项以保持理智,例如:

    Set-StrictMode -Version Latest
    $ErrorActionPreference = "Stop"
    $PSDefaultParameterValues['*:ErrorAction']='Stop'
    

    当从.psm1 powershell模块运行代码时,这(如预期的那样)对调用者的环境没有影响。有没有办法从psm1范围跨越到调用者范围来进行这些更改?

  • 打印出有关顶级脚本调用的信息,例如:

    $immediateCallerPath = Get-Variable -scope 1 -name PSCommandPath -ValueOnly
    Write-Host "Starting script at $immediateCallerPath"
    $boundParameters = Get-Variable -scope 1 -name PSBoundParameters -ValueOnly
    Write-Host "Bound parameters are:"
    foreach($psbp in $boundParameters.GetEnumerator())
    {
            "({0},{1})" -f $psbp.Key,$psbp.Value | Write-Host
    }
    

    同样,这些命令一旦置于.psm1文件中就无法再看到最顶层的调用范围

2 个答案:

答案 0 :(得分:3)

$PSCmdlet.SessionState似乎在脚本模块内部提供了一个函数访问调用站点的变量,前提是调用站点位于模块外部。 (如果呼叫站点位于模块内,您可以使用Get-Set-Variable -Scope。)以下是使用SessionState的示例:

New-Module {
    function Get-CallerVariable {
        param([Parameter(Position=1)][string]$Name)
        $PSCmdlet.SessionState.PSVariable.GetValue($Name)
    }
    function Set-CallerVariable {
        param(
            [Parameter(ValueFromPipeline)][string]$Value,
            [Parameter(Position=1)]$Name
        )
        process { $PSCmdlet.SessionState.PSVariable.Set($Name,$Value)}
    }
} | Import-Module

$l = 'original value'
Get-CallerVariable l
'new value' | Set-CallerVariable l
$l

输出

original value
new value

我不确定SessionState是否打算以这种方式使用。对于它的价值,这与Get-CallerPreference.ps1中使用的技术相同。还有一些测试用例here,它们传递PowerShell版本2到5.1。

答案 1 :(得分:0)

不确定我是否完全理解你的目标。我相信您想知道调用模块的cmdlet实现的代码在哪里。甚至可能更进一步。

如果我正确,那么您可以使用Get-PSCallStack来检索堆栈跟踪。例如,来自未保存的脚本,它看起来像这样

PS C:\Users\asarafian> Get-PSCallStack

Command       Arguments Location 
-------       --------- -------- 
<ScriptBlock> {}        <No file>

如果文件已保存,那么它将如下所示

PS C:\Users\asarafian> Get-PSCallStack

Command       Arguments Location 
-------       --------- -------- 
File1.ps1           <No file>

根据您想要实现的目标,我不清楚,您需要从[0]开始执行列表,这是执行Get-PSCallStack的代码到[x]

构建XWrite时,我还想弄清楚堆栈中的条目是否是脚本文件,模块的cmdlet部分或<ScriptBlock>之类的未知。

我的实现在Get-XCommandSource.ps1中,它遵循以下逻辑来表示来自stacktrace的命令值

  1. 如果以.ps1结尾,那么它就是一个脚本文件。
  2. 如果是<ScriptBlock>那么它就是一个脚本块。
  3. 如果命令可以加载Get-Command那么
    1. 如果它有模块,那么它就是模块中的cmdlet。
    2. 如果没有,那么它是以.\cmdlet.ps1模式导入的cmdlet /函数。
  4. 以下是实施:

    function Get-XCommandSource
    {
        [CmdletBinding(SupportsShouldProcess=$true)]
        Param(
            [Parameter(Mandatory=$true)]
            [AllowEmptyString()]
            [AllowNull()]
            [string]$Command
        )
        begin {
    
        }
    
        process {
            if(-not $Command)
            {
                "Unknown"
            }
            elseif($Command.EndsWith(".ps1"))
            {
                "Script"
            }
            elseif($Command -eq "<scriptblock>")
            {
                "Unknown"
            }
            else
            {
                $cmdlet=Get-Command -Name $command -ErrorAction SilentlyContinue
                if($cmdlet)
                {
                    $moduleName=$cmdlet|Select-Object -ExpandProperty ModuleName
    
                    if($moduleName)
                    {
                        $moduleName
                    }
                    else
                    {
                        "Function"
                    }
                }
                else
                {
                    "Unknown"
                }
            }
        }
    
        end {
    
        }
    }