powershellscriptblock

Powershell - How to pass variable into scriptblock


I'm trying to understand and figure out how I can pass a variable into a scriptblock. In my below example script, when a new file is dropped into the monitored folder it executes the $action script block. But the $test1 variable just shows up blank. Only way I can make it work is by making it a global variable, but I don't really want to do that.

I've looked into this some and I'm more confused than when I started. Can anyone help me out or point me in the right direction to understand this?

$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()
}

Solution

  • Hidden away in the documentation for Register-ObjectEvent, way down in the -Action parameter description is this little tidbit:

    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.

    What this means is PowerShell automatically creates some variables that you can use inside the event handler scriptblock and it populates them when the event is triggered - for example:

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

    When you create a file in the watched folder you'll see some output like this:

    
    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
    

    If these contain the information you're after then you don't actually need to pass any parameters into the scriptblock yourself :-).

    Update

    If you still need to pass your own variables into the event you can use the -MessageData parameter of Register-ObjectEvent to be able to access it as $Event.MessageData inside your event scriptblock - for example:

    $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
    }
    

    which will output something like this when the event triggers:

    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 is technically still a global variable but your $Action doesn't need to know about it anymore as it takes a reference from the $Event.

    Note you'll need to use a mutable data structure if you want to persist changes - you can't just assign a new value to $Event.MessageData, and it'll possibly need to be thread-safe as well.