Unregister a registered filewatcher event does not work - powershell

I want to watch a folder with powershell and I am a PS beginner.
That script works ONE time when I start the script.
But when I have to restart the script again because I changed some script code I get this error message:
Cannot subscribe to the specified event. A subscriber with the source identifier 'FileChanged' already exists.
I tried:
this at the top of the script:
Unregister-Event -SourceIdentifier FileChanged
does not work.
How do I correctly unregister the event so I can run my script as often I want and the previously registered event is disposed?
CODE
$folder = "C:\temp"
$Watcher = New-Object IO.FileSystemWatcher $folder -Property #{
IncludeSubdirectories = $true
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
$onChanged = Register-ObjectEvent $Watcher Changed -SourceIdentifier FileChanged -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp"
Write-Host $path
#Move-Item $path -Destination $destination -Force -Verbose
}

Ok, looking at what your trying to achieve... to answer your original question, you need to do the following to unregistered the event.
Get-EventSubscriber -SourceIdentifier "filechanged" | Unregister-Event
I have to ask why are you having to make 1000 adjustments to the code. If you are trying to register 1000 different events to be monitored it would make more sense to loop and increment a variable using the ++ modifier.
I have achieved this already if this is what your tying to accomplish and can share some code if you need it.

Related

Problems writing to file inside cmdlet?

I only dabble in PS so I'm probably missing something obvious, but this seems like it should work lol
I've got a filewatcher, looks for a specific file in a specific folder, when found it triggers an action. The action first writes to screen, then to logfile then triggers a follow-up script.
Writing to screen works, writing to logfile doesn't, triggering follow up script works too. I actually tried the writing to logfile a few different ways, even building a function before this part and calling it, like a Write-Log instead of Write-Host but it doesn't work either.
Is there a special means to write to log in the manner I'm trying?
$folder = 'D:\InputFiles\'
$filter = 'data.csv'
$LogFile = "D:\APIRead\logs\master.log"
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier PaymentsMKFileCreated -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$Output = "The file '$name' was $changeType at $timeStamp"
Write-Host $Output
Out-File -FilePath $LogFile -InputObject $Output -Append
#Invoke-Item 'D:\APIRead\scripts\process.bat'
}
$LogFile = "D:\APIRead\logs\master.log"
Try moving this line inside of your Event -Action
It could be trying to expand $LogFile, but it is not defined in the Action's scope.
Grok42's helpful answer offers a pragmatic solution to the problem: defining $LogFile inside the -Action script block by definition makes it available there (but only there).
Indeed, a script block { ... } passed to the -Action parameter of the Register-ObjectEvent cmdlet does not generally see variables defined in the caller's scope (unless those variables happen to be defined in the global scope), because such a script block runs in a dynamic module.
However, you may still want to declare your $LogFile variable in the caller's scope while also allowing it to be referenced in the -Action script block, so that both scopes can act on it.
To that end, you can use Register-ObjectEvent's -MessageData parameter to pass a value to the -Action script block, which it can reference as $Event.MessageData, via the automatic $Event variable:
$folder = 'D:\InputFiles\'
$filter = 'data.csv'
$LogFile = "D:\APIRead\logs\master.log"
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite' }
# Note the use of -MessageData
Register-ObjectEvent $fsw Created -SourceIdentifier PaymentsMKFileCreated -MessageData $LogFile -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$logFile = $Event.MessageData # Get the log file path from the -MessageData argument
$Output = "The file '$name' was $changeType at $timeStamp"
Write-Host $Output
Out-File -FilePath $logFile -InputObject $Output -Append
}
Note:
For an example of passing multiple values from the caller's scope to the -Action script block, using a hashtable, see this answer.
It's even possible to pass the caller's entire state to -MessageData, via $ExecutionContext.SessionState, which allows the -Action block to retrieve any variable from the caller's state via $Event.MessageData.PSVariable.GetValue('varName')
removed the ** after append and make sure you have permissions to the directory you are writing to.
Adding -Force makes it create the file if it doesn't exist.
Out-File -FilePath $LogFile -InputObject $Output -Append -Force

FileSystemWatcher not firing events till PowerShell closes

I have a script (below) that watches a folder that the .ps1 script sits in.
When a file is created it fires a .bat file to do a job.
Initially it would run and close immediately.
So I added '''Start-Sleep -s 50'''
It works but it only triggers the .bat launch when the PowerShell window closes.
(As I don't know how long it will be till a file turns up in the folder, this is kind of useless).
Ideally I could do with the .bat file launching as soon as the new file is created, which in turn then closes the PowerShell window
$configFilePath = $PSScriptRoot
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $configFilePath, $filter -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp" -fore green
Out-File -FilePath c:\temp\log\Filelog.txt -Append -InputObject "The file '$name' was $changeType at $timeStamp"
Set-Location "$PSScriptRoot"
Start-Process "$PSScriptRoot\PS_Run.bat"
}
Start-Sleep -s 50
You can replace Start-Sleep with:
Wait-Event -SourceIdentifier FileCreated
Then you need to add an exit command to your watcher like this:
Start-Process "$PSScriptRoot\PS_Run.bat"
exit
}
Since you cannot exit the console from the filewatcher, you can do this instead:
$configFilePath = $PSScriptRoot
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $configFilePath, $filter -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Write-Host "The file '$name' was $changeType at $timeStamp" -fore green
Out-File -FilePath c:\temp\log\Filelog.txt -Append -InputObject "The file '$name' was $changeType at $timeStamp"
Set-Location "$PSScriptRoot"
Start-Process "$PSScriptRoot\PS_Run.bat"
}
Wait-Event -SourceIdentifier FileCreated -Timeout 50 # or no of seconds before file shows up.
When run as a scheduled task, this will execute the bat file as soon as a new file is created and close the console when the timeout is reached.

External powershell scripts only run once when action called

I am trying to call an outside ps script to run every time files are created. The monitoring and logging of files work well, but the Invoke-Expression of running the external script only runs one time even if more files are created. How do I run the external script with every time a new file is created.
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "c:\mypath"
$watcher.Filter = "*.txt*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = { $path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
Add-content "C:\mypath\log.txt" -value $logline
Invoke-Expression (start powershell ("C:\MyOtherScript.ps1")) ###### This only runs one time even if file is changes and logged
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
while ($true) {sleep 5}
EDIT: This got it working incase anyone find themselves here looking to solve the porblem
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "c:\mypath"
$watcher.Filter = "*.txt*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER AN EVENT IS DETECTED
$action = { $path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
Add-content "C:\mypath\log.txt" -value $logline
Start-Process powershell -argument "C:\MyOtherScript.ps1"
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED
Register-ObjectEvent $watcher "Created" -Action $action
#while ($true) {sleep 5}
Invoke-Expression is not recommend over using & due to security concerns. In your example, however, I'd suggest Start-Process:
Start-Process -FilePath 'powershell' -ArgumentList #('-File','"C:\MyOtherScript.ps1"')

FileSystemWatcher works in the PowerShell ISE but not when run

I want to monitor a folder and move files that match certain criteria, so I'm trying to use the FileSystemWatcher.
I have a function that will be called with each new file:
function ProcessFile()
{
param ([string]$filename)
Write-Host "Processing file '$filename' to $destination"
}
And then I set up a FSW:
Write-Host "Watching $source for new files..."
$fsw = New-Object IO.FileSystemWatcher $source, $filter -Property #{IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action
{
ProcessFile $Event.SourceEventArgs.FullPath
}
That works fine when I run it from the ISE, and any files I drop into the watched folder are correctly tracked, but if I start a PowerShell window and run the script with .\FileWatch.ps1 then nothing happens.
I see the "watching ..." message, but never see a "processing..." message
Here's the full script that works in the ISE but not in a shell...
$source = 'D:\Dev\PowerShell\FileWatch\Test\Source'
$filter = '*.*'
$destination = 'D:\Dev\PowerShell\FileWatch\Test\Found\'
function ProcessFile()
{
param ([string]$filename)
Write-Host "Processing file '$filename' to $destination"
}
Write-Host "Watching $source for new files..."
$fsw = New-Object IO.FileSystemWatcher $source, $filter -Property #{IncludeSubdirectories = $false; NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
ProcessFile $Event.SourceEventArgs.FullPath
}
The problem is that your function ProcessFile isn't loaded in powershell session.
Try loading you script in this way:
. .\myscript.ps1
In this way your code in my system works!
Read about Dot Sourcing a script in powershell.

Trigger script when new folder has been added to a location

I'm automating a process and already made a powershell script for that. Now I need to make something which will call that script everytime a new folder has been added to a specific location i.e. a new build is dropped.
What should I use for this. Is WCF too much? And if not, got any leads for that? Any useful links.
Or is another powershell script better for that?
Keep in mind I need to check subfolders too.
Thanks.
Personnaly I'ld use System.IO.FileSystemWatcher
$folder = 'c:\myfoldertowatch'
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $folder, $filter
$fsw.IncludeSubdirectories = $true
$fsw.NotifyFilter = [IO.NotifyFilters]'DirectoryName' # just notify directory name events
$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action { ... do my stuff here } # and only when is created
Use this to stop watching event
Unregister-Event -SourceIdentifier FileCreated
Try this:
$fsw = New-Object System.IO.FileSystemWatcher -Property #{
Path = "d:\temp"
IncludeSubdirectories = $true #monitor subdirectories within the specified path
}
$event = Register-ObjectEvent -InputObject $fsw –EventName Created -SourceIdentifier fsw -Action {
#test if the created object is a directory
if(Test-Path -Path $EventArgs.FullPath -PathType Container)
{
Write-Host "New directory created: $($EventArgs.FullPath)"
# run your code/script here
}
}