Powershell 跨模块访问调用方范围变量

Powershell access caller scope variables across modules

我在名为 Common.psm1 的模块中声明了以下方法:

function Test-Any ([ScriptBlock]$FilterScript = $null)
{    
    begin {
        $done = $false
    }
    process { 
        if (!$done)
        {
            if (!$FilterScript -or ($FilterScript | Invoke-Expression)){
                $done = $true
            }
        }
    }
    end {
        $done
    }
}
Set-Alias any Test-Any -Scope Global

现在在另一个模块中,我有以下验证:

    $id = 1
    if($notifications | any { $_.Id -eq $id })
    {
        # do stuff here
        return
    }

我收到以下错误:

Invoke-Expression : The variable '$id' cannot be retrieved because it has not been set.

有趣的是,如果我将 Test-Any 定义移动到调用模块,它就像一个魅力。

如何在不将 Test-Any 复制到我的其他模块并且不更改此语法的情况下完成这项工作:

if($notifications | any { $_.Id -eq $id })

编辑 1: 关于我的代码 是否应该 似乎存在一些争论。欢迎在您自己的机器上尝试:

function Test-Any ([ScriptBlock]$FilterScript = $null)
{    
    begin {
        $done = $false
    }
    process { 
        if (!$done)
        {
            if (!$FilterScript -or ($FilterScript | Invoke-Expression)){
                $done = $true
            }
        }
    }
    end {
        $done
    }
}
Set-Alias any Test-Any -Scope Global

$id = 3

$myArray = @(
    @{Id = 1},
    @{Id = 2},
    @{Id = 3},
    @{Id = 4},
    @{Id = 5},
    @{Id = 6},
    @{Id = 7},
    @{Id = 8}
)
$myEmptyArray = @()

$myArray | any #returns true
$myArray | any {$_.Id -eq $id} #returns true
$myEmptyArray | any #returns false
$myEmptyArray | any {$_.Id -eq $id} #returns false

编辑 2: 我刚刚发现您只会遇到此问题,当 Test-Any 驻留在一个加载的模块中并且调用代码驻留在使用 Set-StrictMode -Version Latest 的第二个模块中时。如果您关闭 StrictMode,您不会收到错误,但它也不起作用。

编辑 3: 不用说,这工作得很好:

    $sb = [Scriptblock]::Create("{ `$_.Id -eq $id }")
    if($notifications | any $sb)

但严重偏离了我试图获得的简单性和直观性

Invoke-Expression (which, when possible, ) 隐式地 重新创建 调用者的 范围传递的脚本块,通过它的 字符串representation,在 module 的上下文中,它使脚本块代码中对调用者状态的任何引用无效(因为模块通常看不到外部调用者的状态, global 范围除外).

解决方案是按原样执行脚本块,但提供传递给模块函数的管道输入:

# Note: New-Module creates a *dynamic* (in-memory only) module,
#       but the behavior applies equally to regular, persisted modules.
$null = New-Module {
  function Test-Any ([ScriptBlock] $FilterScript)
  {    
      begin {
          $done = $false
      }
      process { 
          if (!$done)
          {
              # Note the use of $_ | ... to provide pipeline input
              # and the use of ForEach-Object to evaluate the script block.
              if (!$FilterScript -or ($_ | ForEach-Object $FilterScript)) {
                  $done = $true
              }
          }
      }
      end {
          $done
      }
  }
  
}

# Sample call. Should yield $true
$id = 1
@{ Id = 2 }, @{ Id = 1 } | Test-Any { $_.Id -eq $id }

注:this answer uses a similar approach, but tries to optimize processing by stopping further pipeline processing - which, however, comes at the expense of incurring an on-demand compilation penalty the first time the function is called in the session, because - as of PowerShell 7.2 - you cannot (directly) stop a pipeline on demand from user code - see GitHub issue #3821中的Test-Any函数。