Powershell - 如何将变量传递到脚本块

Powershell - How to pass variable into scriptblock

我正在尝试了解并弄清楚如何将变量传递到脚本块中。在我下面的示例脚本中,当一个新文件被放入受监视的文件夹中时,它会执行 $action 脚本块。但是 $test1 变量只是显示为空白。使它工作的唯一方法是将其设为全局变量,但我真的不想那样做。

我已经对此进行了一些研究,但比开始时更加困惑。任何人都可以帮助我或指出正确的方向来理解这一点吗?

$PathToMonitor = "\path\to\folder"

$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path  = $PathToMonitor
$FileSystemWatcher.Filter  = "*.*"
$FileSystemWatcher.IncludeSubdirectories = $false

$FileSystemWatcher.EnableRaisingEvents = $true

$test1 = "Test variable"

$Action = {
    Write-Host "$test1"
}

$handlers = . {
    Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Created -Action $Action -SourceIdentifier FSCreateConsumer
}

try {
    do {
        Wait-Event -Timeout 5
    } while ($true)
}
finally {
    Unregister-Event -SourceIdentifier FSCreateConsumer
    
    $handlers | Remove-Job
    
    $FileSystemWatcher.EnableRaisingEvents = $false
    $FileSystemWatcher.Dispose()
}

事件操作块在后台线程上运行,无法在调度时解析 $test1

一种解决方法是显式读取和写入全局范围的变量(例如 Write-Host $global:test1),但更好的解决方案是确保 $Action 块“记住”的值$test1 稍后 - 我们可以用 a closure.

来完成

我们需要为此稍微重组代码,所以首先用同步哈希表替换 $test1 字符串文字:

$test1 = [hashtable]::Synchronized(@{
  Value = "Test variable"
})

这将使我们能够做两件事:

  • 我们可以修改字符串值而不改变存储在$test1,
  • 中的对象的身份
  • 字符串值可以被多个后台线程修改而不会出现任何竞争条件

现在我们只需要从 $Action 块创建闭包:

$Action = {
    Write-Host $test1.Value
}.GetNewClosure()

这会将 $test1 的值(对我们刚刚在上面一行创建的同步哈希表的引用)绑定到 $Action 块,因此它将“记住” $test1 解析为哈希表,而不是尝试(并失败)在运行时解析它。

隐藏在 Register-ObjectEvent, way down in the -Action 参数描述的文档中的是这个小花絮:

The value of the Action parameter can include the $Event, $EventSubscriber, $Sender, $EventArgs, and $Args automatic variables. These variables provide information about the event to the Action script block. For more information, see about_Automatic_Variables.

这意味着 PowerShell 会自动创建一些您可以在事件处理程序脚本块中使用的变量,并在触发事件时填充它们 - 例如:

$Action = {
    write-host ($Sender | format-list * | out-string)
    write-host ($EventArgs | format-list * | out-string)
}

当您在监视文件夹中创建文件时,您会看到如下输出:


NotifyFilter          : FileName, DirectoryName, LastWrite
Filters               : {*}
EnableRaisingEvents   : True
Filter                : *
IncludeSubdirectories : False
InternalBufferSize    : 8192
Path                  : c:\temp\scratch
Site                  :
SynchronizingObject   :
Container             :




ChangeType : Created
FullPath   : c:\temp\scratch\New Text Document (3).txt
Name       : New Text Document (3).txt

如果这些包含您想要的信息,那么您实际上不需要自己将任何参数传递到脚本块中:-)。

更新

如果您仍然需要将自己的变量传递到事件中,您可以使用 Register-ObjectEvent-MessageData 参数,以便能够在事件脚本块中以 $Event.MessageData 的形式访问它 -例如:

$Action = {

    write-host ($EventArgs | format-list * | out-string)

    write-host "messagedata before = "
    write-host ($Event.MessageData | ConvertTo-Json)

    $Event.MessageData.Add($EventArgs.FullPath, $true)

    write-host "messagedata after = "
    write-host ($Event.MessageData | ConvertTo-Json)

}

$messageData = @{ };
$handlers = . {
    # note the -MessageData parameter
    Register-ObjectEvent `
        -InputObject      $FileSystemWatcher `
        -EventName        Created `
        -Action           $Action `
        -MessageData      $messageData `
        -SourceIdentifier FSCreateConsumer
}

事件触发时会输出如下内容:

ChangeType : Created
FullPath   : c:\temp\scratch\New Text Document (16).txt
Name       : New Text Document (16).txt



messagedata before =
{}
messagedata after =
{
  "c:\temp\scratch\New Text Document (16).txt": true
}

$messageData 在技术上仍然是一个全局变量,但您的 $Action 不再需要知道它,因为它从 $Event.

中获取引用

请注意,如果您想保留更改,您需要使用 mutable 数据结构 - 您不能只为 $Event.MessageData 分配一个新值,并且它可能也需要线程安全。