Powershell get-members of $EventArgs automatic variable - powershell

I want to list all available properties of the variable $EventArgs by piping it to get-member. but I am having trouble to get the members of the automatic variable $EventArgs.
In the example I can see that they get the property. FullPath from the automatic variable $EventArgs. I want a way to list all the properties that maybe are useful.
Any ideas of how to get the members of $EventArgs automatic variable.
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
$watcher.Site
$arrary = #()
$watcher.Filter = ""
Register-ObjectEvent $watcher "Created" -Action {
write-host "Created: $($eventArgs.FullPath)"
$arrary+= $EventArgs|gm
$arrary+= $EventArgs
write-host $EventArgs|gm
$EventArgs|gm
}

One quick way to see all of the properties (there are only three) is to do this:
Register-ObjectEvent $watcher "Created" -Action {
$eventArgs | Select-Object * | Write-Host
}
However, you'll get much more useful information if you do this:
Register-ObjectEvent $watcher "Created" -Action {
Write-Host $eventArgs.GetType()
}
and then look up the resultant type on MSDN. When you do that, you'll see that $EventArgs is actually an object of type System.IO.FileSystemEventArgs, which is fully documented here. Not only will you see the three properties, you'll see what they mean. For example, the first property ChangeType is really an enumeration of the type System.IO.WatcherFileTypes, and you can learn all of the different values it can have.

Related

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()
}
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.
The event action block runs on a background thread and can't resolve $test1 when dispatched.
One workaround is to explicitly read from and write to a globally-scoped variable (eg. Write-Host $global:test1), but a better solution is to ensure the $Action block "remembers" the value of $test1 for later - something we can accomplish with a closure.
We'll need to reorganize the code slightly for this, so start by replacing the $test1 string literal with a synchronized hashtable:
$test1 = [hashtable]::Synchronized(#{
Value = "Test variable"
})
This will allow us to do 2 things:
we can modify the string value without changing the identity of the object stored in $test1,
string value can be modified by multiple background threads without any race conditions occuring
Now we just need to create the closure from the $Action block:
$Action = {
Write-Host $test1.Value
}.GetNewClosure()
This will bind the value of $test1 (the reference to the synchronized hashtable we just created on the line above) to the $Action block, and it will therefore "remember" that $test1 resolves to the hashtable rather than attempt (and fail) to resolve it at runtime.

How to provide options to event registration?

I don't understand why the following code generates error messages. Powershell seems difficult to learn.
$fsw = New-Object IO.FileSystemWatcher ...
$Action = {Param($option)
if ($option -eq "Copy")
{Write-Host "Copy was specified"}
}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action $Action
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action $Action -ArgumentList Copy
(This is an updated version of question How to provide options to script blocks?)
When in doubt, read the documentation. The Register-ObjectEvent cmdlet does not have a parameter -ArgumentList, so getting an error when trying to call the cmdlet with a parameter that it doesn't have is hardly surprising. And as I already told you in my comment to your previous question, passing arguments to an event action the way you're trying to do does not make much sense in the first place.
If your actions are so similar that defining different action script blocks would yield too much boilerplate you can easily discriminate by event inside the scriptblock, since that information is passed to the action automatically (via the variable $Event).
$Action = {
$name = $Event.SourceEventArgs.Name
$type = $Event.SourceEventArgs.ChangeType
switch ($type) {
'Created' { Write-Host "file ${file} was created" }
'Changed' { Write-Host "file ${file} was changed" }
}
}

How to find the file that triggered a FileSystemWatcher event

I'm using a FileSystemWatcher to check for changed files in a target directory. However it does not seem like I can access the information on what file triggered the event, or I do simply not know how.
$Action = {
# Output name of trigger file here.
}
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher $TargetDirectory
Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Changed -Action $Action
As I'm waiting for events in multiple target directories, the alternative of using synchronized waiting is no option for me.
Am I doing something wrong?
When using this code:
$Action = {
# Output name of trigger file here.
Write-Host $Event.SourceEventArgs.FullPath
}
$TargetDirectory = "c:\temp\fsw"
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher $TargetDirectory
Register-ObjectEvent -InputObject $FileSystemWatcher -EventName Changed -Action $Action
I get the full path of the file being changed.

Monitoring files in Powershell and using ArrayList

I'm new to PowerShell. I would like to add a file path to an ArrayList every time it changes. However, this PowerShell script fails somehow. Any hints what might I be doing wrong?
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Mydir"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
$jobs = New-Object System.Collections.ArrayList
$changed = Register-ObjectEvent $watcher "Changed" -Action {
$changedFile = $($eventArgs.FullPath)
$jobs.Add($changedFile)
}
It's a scope issue. Add the global scope modifier:
$global:jobs.Add($changedFile)
see about_Scopes for more help.

How can I reload modules from one open work environment to affect another working environment

I have my PowerShell project broken into modules. But because they are modules I have to reload them every time I change them. So I wrote a loop that has a FileSystemWatcher and if one of the .psm1 file changes it will either reload or import that module.
The problem is that the above loop isn't going to let me run other scripts in its working environment, so a new environment will not have the same modules loaded/reloaded for it. I need to keep these modules out of the primary default PowerShell modules folder(s). Is there a way to run the script that reloads the modules when they change in the same environment or affect a certain environment?
UPDATE
So I run the following Module-Loader.ps1 script. The code block associated with the 'FileChanged' event does fire when I 'save' a *.psm1 file after having been modified. However two issues occure:
1) it fires twice when I save
2a) If the module is not loaded, it will run Import-Module $PWD\ModuleName, but it won't have actually loaded at least in the environment (if I run the same code in the environment it will load)
2b) if it is loaded, and it tries to remove the module, it will error that none exists.
# create a FileSystemWatcher on the currect directory
$filter = '*.psm1'
$folder = $PWD
$watcher = New-object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $false; EnableRaisingEvents = $true; NotifyFilter = [IO.NotifyFilters]'LastWrite'}
Register-ObjectEvent $watcher Changed -SourceIdentifier FileChanged -Action {
$name = $Event.SourceEventArgs.Name
$filename = $name.Remove($name.IndexOf('.'), 5)
$loadedModule = Get-Module | ? { $_.Name -eq $filename }
write-host $filename
if ($loadedModule) {
write-host "Reloading Module $folder\$($filename)"
Reload-Module $folder\$filename
} else {
write-host "Importing Module $folder\$($filename)"
Import-Module $folder\$filename
}
}
I am of the opinion that though this is being ran in a session, the code block in the event is not associated with this specific environment.
Here is an example from some code I have that copies a folder to a shared folder any time something has changed in it. It's kinda my little dropbox implementation :-)
Any time one of the file system watcher event types such as Changed occurs, the code specified in the -Action parameter of the Register-ObjectEvent cmdlet will fire.
In your -Action code you would call Import-Module with the -Force parameter to overwrite the current one in
memory.
function Backup-Folder {
& robocopy.exe "c:\folder" "\\server\share" /MIR /W:10 /R:10
}
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "c:\folder"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
Register-ObjectEvent $watcher "Changed" -Action { Backup-Folder }
Register-ObjectEvent $watcher "Created" -Action { Backup-Folder }
Register-ObjectEvent $watcher "Deleted" -Action { Backup-Folder }
Register-ObjectEvent $watcher "Renamed" -Action { Backup-Folder }