Why can't I dispose my source FileSystemWatcher object? - powershell

I have a FileSystemWatcher Script which does create a FileSystemWatcher for each Subfolder that matches a regex. This works like a charm.
Here's my script
$Folders = gci $Dir -Dir | ? Name -match '^\d{5}$' | % {
$source = $_.FullName
$watcher = [System.IO.FileSystemWatcher]::new()
$watcher.IncludeSubdirectories = $false
$watcher.Path = $source
$watcher.Filter = "*.csv"
$watcher.EnableRaisingEvents = $true
New-Variable -Name $_.Name -Value $watcher
}
$action = { stuff }
Get-Variable | ? Name -match '^\d{5}$' | % {
Register-ObjectEvent $_.Value Created -Action $action -SourceIdentifier $_.Name > $null
}
This leaves me with some FileSystemWatchers in different Variables like $70447, $78900, $13450 etc.
but I also have one watcher in $watcher, but this variable is only used to pass my FileSystemWatcher Object to the "productive" 5-digit variables
I wanted to get rid of $watcher by doing a $watcher.dispose() after the New-Variable Command. But when I do this, all of my watchers throw an error.
Why does this happen? $watcher is not in use anymore since the object is already passed on to a new variable. Can someone explain this?
Thanks!
Edit:
Register-ObjectEvent : Cannot access thrown Object.
Objektname: "FileSystemWatcher".
In Z:\Powershell-Scripts\FSW.ps1:43 Zeichen:5
+ Register-ObjectEvent $_.Value Created -Action $action -SourceIden ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.IO.FileSystemWatcher:FileSystemWatcher) [Register-ObjectEvent], ObjectDisposedException
+ FullyQualifiedErrorId : INVALID_REGISTRATION,Microsoft.PowerShell.Commands.RegisterObjectEventCommand

If I had to guess, the references are getting tied to the variables somehow and disposing is causing some kind of scope issue. I'm unable to replicate your problem, but consider not having in-between variable assignments and see if it resolves your issue:
Get-ChildItem -Path $dir -Directory | ? Name -match '^\d{5}$' | % {
New-Variable -Name $_.Name -Value (New-Object -TypeName System.IO.FileSystemWatcher -Property #{
IncludeSubdirectories = $false
Path = $_.FullName
Filter = '*.csv'
EnableRaisingEvents = $true
})
}
$action = { stuff }
Get-Variable | ? Name -match '^\d{5}$' | % {
$null = $_.Value | Register-ObjectEvent -EventName Created -Action $action -SourceIdentifier $_.Name
}

Related

Powershell Change Watcher only Triggers on directories

I am building a file watching script in powershell. I have been manipulating code from: https://gallery.technet.microsoft.com/scriptcenter/Powershell-FileSystemWatche-dfd7084b
Everything seemed to work fine, but when I did some actual testing I noticed the change event was only triggering on the parent directory and not the actual file itself. Below is the code I have poached and updated:
$folder = 'C:\extended_attributes\testing\DesignerVistaReorganize' # Enter the root path you want to monitor.
$filter = '*.*' # You can enter a wildcard filter here.
# In the following line, you can change 'IncludeSubdirectories to $true if required.
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
# Here, all three events are registerd. You need only subscribe to events that you need:
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$filePath = -join($folder,"\",$name) | Out-String
Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore green
Out-File -FilePath C:\extended_attributes\testing\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"}
Register-ObjectEvent $fsw Deleted -SourceIdentifier FileDeleted -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$filePath = -join($folder,"\",$name) | Out-String
Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore red
Out-File -FilePath C:\extended_attributes\testing\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"}
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$filePath = -join($folder,"\",$name) | Out-String
Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore white
Out-File -FilePath c:\scripts\filechange\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"}
The delete and create events are working as one would expect (as shown below):
File Creation:
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Cart_Warnings\newFile.txt' Created at 10/01/2018 14:55:35
File Change:
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Cart_Warnings'
was Changed at 10/01/2018 15:01:18
File Deletion:
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Cart_Warnings\newFile.txt' Deleted at 10/01/2018 14:56:47
Another thing I noticed is that the change event is only firing on the first file change under the root directory. The other two events fire whenever a file is created or deleted. Has anyone run into this issue before and if so can you please provide some insight on a work around?
Work Around (not a valid answer though)
I have tried a bunch of differnt things trying to make this event fire consistently, and only one seems to work. I have two scripts for file change events. They are pretty much identical with the exception that they call each other after the event has completed. The key is not ensure that you kill the watcher by unregistering the event before the second script is called.
Script 1
$folder = 'C:\extended_attributes\testing\DesignerVistaReorganize' # Enter the root path you want to monitor.
$filter = '*.*' # You can enter a wildcard filter here.
# In the following line, you can change 'IncludeSubdirectories to $true if required.
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
echo "Script 1"
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$filePath = -join($folder,"\",$name) | Out-String
Unregister-Event FileChanged
Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore white
Out-File -FilePath C:\extended_attributes\testing\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"
.\change_watcher_2.ps1
}
Script 2
$folder = 'C:\extended_attributes\testing\DesignerVistaReorganize' # Enter the root path you want to monitor.
$filter = '*.*' # You can enter a wildcard filter here.
# In the following line, you can change 'IncludeSubdirectories to $true if required.
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property #{IncludeSubdirectories = $true;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $fsw Changed -SourceIdentifier FileChanged -Action {
echo "Script 2"
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
$filePath = -join($folder,"\",$name) | Out-String
Unregister-Event FileChanged
Write-Host "The file '$filePath' was $changeType at $timeStamp" -fore white
Out-File -FilePath C:\extended_attributes\testing\outlog.txt -Append -InputObject "The file '$filePath' was $changeType at $timeStamp"
.\change_watcher_1.ps1
}
output
PS C:\extended_attributes\testing> C:\extended_attributes\testing\change_watcher_1.ps1
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
2 FileChanged NotStarted False ...
PS C:\extended_attributes\testing> The file 'C:\extended_attributes\testing\DesignerVistaReorganize\BusinessPrinting
' was Changed at 10/02/2018 10:46:43
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Editor_Warnings
' was Changed at 10/02/2018 10:47:09
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile\Mobile_Control_Panel_2015_05_14.dvf
' was Changed at 10/02/2018 10:49:50
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile
' was Changed at 10/02/2018 10:49:58
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile\Gradients_Panels_Colors_Menus_2015_03_01.dvf
' was Changed at 10/02/2018 10:51:49
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile\Tablet_Editor_2015_03_31.dvf
' was Changed at 10/02/2018 10:52:46
The file 'C:\extended_attributes\testing\DesignerVistaReorganize\Mobile
' was Changed at 10/02/2018 10:52:57
I would really like a better solution than this as I can foresee many problems arising from this approach.
I found a much better solution than I had posted in the update of my original questions. If you use the attribute to EnableRaisingEvents as shown below. The event will fire everytime a change has been made to the file system:
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = 'C:\extended_attributes\testing\DesignerVistaReorganize'
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
$changed = Register-ObjectEvent $watcher "Changed" -Action {
write-host "Changed: $($eventArgs.FullPath)"
$creation_time = gi -Path $eventArgs.FullPath | get-itemproperty | select -ExpandProperty CreationTime | Get-date -Format s
$file_extension = gi -Path $eventArgs.FullPath | get-itemproperty | select -ExpandProperty Extension
$file_base_name = gi -Path $eventArgs.FullPath | get-itemproperty | select -ExpandProperty BaseName
$last_access_time = gi -Path $eventArgs.FullPath | get-itemproperty | select -ExpandProperty LastAccessTime | Get-date -Format s
$last_write_time = gi -Path $eventArgs.FullPath | get-itemproperty | select -ExpandProperty LastWriteTime | Get-date -Format s
Write-Host "Creation time: $($creation_time)"
Write-Host "ex: $($file_extension)"
Write-Host "Name: $($file_base_name)"
Write-Host "access: $($last_access_time)"
Write-Host "write: $($last_write_time)"
$object = New-Object System.Object
$object | Add-Member -type NoteProperty –Name Extension –Value $file_extension
$object | Add-Member -type NoteProperty –Name BaseName –Value $file_base_name
$object | Add-Member -type NoteProperty –Name Createtime –Value $creation_time
$object | Add-Member -type NoteProperty –Name Accesstime –Value $last_write_time
$object | Add-Member -type NoteProperty –Name Writetime –Value $last_access_time
Write-Host "After Object"
# Load the module
Import-Module Mdbc
# Connect the new collection test.test
Connect-Mdbc . test test -NewCollection
# add the objects to the collection
$object | Add-MdbcData
# Get all data as custom objects and show them in a table
Get-MdbcData -As PS | Format-Table -AutoSize | Out-String
}

Powershell Scipt Runs in ISE only

I have researched this question quite a bit and have found others with similar issues, but no fix that works for us.
We have built a power shell script to monitor a folder for new images, check their color profile with exiftool and convert when needed with image magick. All of this functionality works well enough when the script is run in ISE, but will not work if the script is executed from a .exe or ran manually.
I have read that FileSystemWatcher requires the script be ran in STA which I have attempted to do a few times by including -STA in the target for the .exe shortcut. Doing so proves unsuccessful.
#----------------------------------------------------------------------------
Unregister-Event -SourceIdentifier FileCreated
Clear
#----------------------------------------------------------------------------
$description = Get-WmiObject -Class Win32_OperatingSystem |Select Description
$description = $description -Replace "#{Description=",""
$description = $description -Replace "}",""
$assetsFolder = "E:\" + $description + "\Capture One Session\Output"
$conversionStage = "C:\Profile_Pro\conversionStage"
#----------------------------------------------------------------------------
New-Item -ItemType directory -Force -Path "$assetsFolder" | Out-Null
New-Item -ItemType directory -Force -Path "$assetsFolder" | Out-Null
#----------------------------------------------------------------------------
$filter = "*.*"
$srgb = "sRGB IEC61966-2.1"
$isTif = "tif"
#----------------------------------------------------------------------------
$monitor = New-Object IO.FileSystemWatcher $assetsFolder, $filter -Property #{
IncludeSubdirectories = $true
NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}
#----------------------------------------------------------------------------
$onCreated = Register-ObjectEvent $monitor Created -SourceIdentifier FileCreated -Action {
#----------------------------------------------------------------------------
Add-Type -AssemblyName System.Windows.Forms
#----------------------------------------------------------------------------
$path = $event.SourceEventArgs.FullPath
$name = $event.SourceEventArgs.Name
Write-Host $path
Write-Host $name
If ($name -Match $isTif)
{
$BOX = [System.Windows.Forms.MessageBox]::Show('TIFF FILE DETECTED. PLEASE CHECK RECIPE.', 'Warning', 'ok', 'Warning')
$path = $path -Replace ".tif.tmp",".tif"
Start-Sleep -s 2
Remove-Item "$path" -Force
}
Else
{
$colorSpace = C:\Profile_Pro\tools\exiftool.exe -T -ProfileDescription $path
$profileConvert = 'C:\Profile_Pro\tools\sRGB_Color_Space_Profile.icm'
If ($colorSpace -Match $srgb)
{
}
Else
{
Start-Sleep -s 3
$name = $name -Replace ".jpg.tmp",".jpg"
$path = $path -Replace ".jpg.tmp",".jpg"
Move-Item -Path $path -Destination $conversionStage -Force
convert $conversionStage\$name -profile $profileConvert $path
Start-Sleep -s 1
Remove-Item "$conversionStage\$name" -Force
#----------------------------------------------------------------------------
$BOX = [System.Windows.Forms.MessageBox]::Show('COLOR PROFILE ERROR. PLEASE CHECK RECIPE.', 'Warning', 'ok', 'Warning')
#----------------------------------------------------------------------------
$TO = $env:USERNAME + "#biz.com"
$Outlook = New-Object -ComObject Outlook.Application
$Mail = $Outlook.CreateItem(0)
$Mail.To = $TO
$Mail.Subject = "COLOR PROFILE DEFECT: " + $name
$Mail.importance = 2
$Mail.Body = "A COLOR PROFILE DEFECT HAS BEEN DETECTED WITH FILE:`r`n`r`n$name`r`n`r`nTHIS FILE IS FIXED.`r`n`r`nPLEASE CHECK YOUR PROCESS RECIPE."
$Mail.Send()
}
}
}
#----------------------------------------------------------------------------
#Unregister-Event -SourceIdentifier FileCreated
#----------------------------------------------------------------------------
Any ideas to keep this script running as a .exe or outside of ISE would be greatly appreciated.

FileSystemWatcher detect when file is moved to folder

I'm having trouble with a script that's monitoring a folder.
FileSystemWatcher only seems to detect when a file is being copied to the folder, not when it's just being moved to it from the same drive.
Is there a way to detect this?
$desFolder = "H:\Media"
$ExFolder = "H:\Monitor"
$Filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $ExFolder, $Filter
$fsw.IncludeSubdirectories = $true
$fswOnChange = Register-ObjectEvent -InputObject $fsw -EventName Changed -SourceIdentifier FileUpdated -Action {
$File = Get-Item $EventArgs.FullPath
if($File.Name -imatch '\.(?:mp4|mkv)$' -and $file.Name -match '.*?S\d+E+'){
Start-Sleep -s 1
if(Test-FileReady $File.FullName){
Move-Files $File
}
}
}
function global:Test-FileReady {
Param([parameter(Mandatory=$true)][string]$Path)
if (Test-Path -LiteralPath $Path) {
trap {
return $false
}
$stream = New-Object system.IO.StreamReader $Path
if ($stream) {
$stream.Close()
return $true
}
}
}
function global:Move-Files{
Param([parameter(Mandatory=$true)][System.IO.FileInfo]$File)
Write-Host $File.Name
}
Try using Renamed and Created events as well.
BTW, the IO.FileSystemWatcher docs say:
The component will not watch the specified directory until the Path is set, and EnableRaisingEvents is true
$fsw.EnableRaisingEvents = $true

Powershell variable getting "lost"

I'm working on a script that monitors an error folder and sends e-mail when an error occurs. For each error we get two files in the folder, one text file and one xml. In the e-mail I want to include the entire content of the text file and part of the content of the xml file.
In the following script I get only the content of the text file. (Lines for sending the email are commented out for testing purposes). I have read about variables and foreach and I guess this may be the problem, but I'm still not sure how to fix this in my script. Or maybe there is a much easier way to do this?
$Foldertowatch = 'C:\lpos\invalidArts'
$FilterXML = '*.xml'
$FilterTXT = '*.txt'
Unregister-Event -SourceIdentifier CreatedEventXML
$fswXML = New-Object System.IO.FileSystemWatcher $Foldertowatch, $FilterXML
$fswXML.EnableRaisingEvents=$true
$fswXML.IncludeSubdirectories=$false
Register-ObjectEvent -InputObject $fswXML -EventName Created -SourceIdentifier CreatedEventXML -Action {
$Filename = $Event.SourceEventArgs.Name
[XML]$XMLFile = (Get-Content $Foldertowatch\$Filename)
$XMLtmp = foreach ($user in $XMLFile.POSLog.POSLogTransaction)
{
$user.RetailStoreID
$user.WorkStationID
$user.OperatorID
$user.SequenceNumber
}
}
Unregister-Event -SourceIdentifier CreatedEventTXT
$fswTXT = New-Object System.IO.FileSystemWatcher $Foldertowatch, $FilterTXT
$fswTXT.EnableRaisingEvents=$true
$fswTXT.IncludeSubdirectories=$false
Register-ObjectEvent -InputObject $fswTXT -EventName Created -SourceIdentifier CreatedEventTXT -Action {
$FilenameTXT = $Event.SourceEventArgs.Name
$ContentTXT = (Get-Content $Foldertowatch\$FilenameTXT) -join "`n"
write-host $user.retailstoreid
Write-Host $contenttxt
<#$From = "from#mail.com"
$To = "to#mail.com"
$SMTPServer = "mailserver.com"
$subject = $env:COMPUTERNAME + " contains invalid ARTSXML"#>
<#Write-Host "Store:" + $ContentXML.RetailStoreID + "`n" + "Workstation:" + $ContentXML.WorkStationID + "`n" +"Operator:" + $ContentXML.OperatorID + "`n" +"Receiptnumber:" + $ContentXML.sequencenumber + "`n" + "`n" + "Filelocation: $Foldertowatch\$FilenameTXT" + "`n" + "`n" + <#$ContentTXT##>
<#$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer);
$smtp.Send($From, $To, $subject, $body);#>
}
The $ContentXML is from an early try, it should be $user now.
I manage to get content from XML if I run first part of script like this:
$Foldertowatch = 'C:\lpos\invalidArts' <# Folder to watch #>
$FilterXML = '*.xml' <# Use *.xml #>
$FilterTXT = '*.txt' <# Use *.txt for poslog2arts, use *.log for POS Reporting #>
Unregister-Event -SourceIdentifier CreatedEventXML
$fswXML = New-Object System.IO.FileSystemWatcher $Foldertowatch, $FilterXML
$fswXML.EnableRaisingEvents=$true
$fswXML.IncludeSubdirectories=$false
Register-ObjectEvent -InputObject $fswXML -EventName Created -SourceIdentifier CreatedEventXML -Action {
$Filename = $Event.SourceEventArgs.Name
[XML]$XMLFile = (Get-Content $Foldertowatch\$Filename)
$XMLtmp = foreach ($user in $XMLFile.POSLog.POSLogTransaction)
{
write-host $user.RetailStoreID
write-host $user.WorkStationID
write-host $user.OperatorID
write-hosts $user.SequenceNumber
}
}
I'm not sure of your intent, but I see a possible solutions. First, this code:
$XMLtmp = foreach ($user in $XMLFile.POSLog.POSLogTransaction)
{
$user.RetailStoreID
$user.WorkStationID
$user.OperatorID
$user.SequenceNumber
}
You never seem to use $XMLtmp again. Also, it's just going to contain an array of strings, not something that has properties like $user.RetailStoreID
What you can do is this:
$users = #()
Register-ObjectEvent -InputObject $fswXML -EventName Created -SourceIdentifier CreatedEventXML -Action {
$Filename = $Event.SourceEventArgs.Name
[XML]$XMLFile = (Get-Content $Foldertowatch\$Filename)
foreach ($user in $XMLFile.POSLog.POSLogTransaction) {
$users += $user
}
}
Then later you can index that array. Are there more than one user? Do you want to send more than one email?
At that point you can do the following:
$users | % { Write-Host $_.RetailStoreId }
The $user variable you create in the foreach () {} goes out of scope (aka "disappears") when the } is hit. The $XMLtmp is unnecessary. Declaring $users outside of the scope of the Action and foreach ensures it is global to the entire script.

FileSystemWatcher cmdlet in Powershell not monitoring system32 folder and subfolders

I noticed that when using PowerShell's FileSystemWatcher cmdlet that it doesnt seem to monitor System32 or it's subfolders in my Windows 7 computer. A script Í have that works just fine when monitoring subfolders of My Documents (i/e "C:\Users\W\Documents\Fichiers PowerShell" is a fplder path that works) but doesn't work when I substitute a folder path in System32 (i/e C:\Windows\System32\Recovery is a path that doesn't work)
Here is the script i'm working with but System32 paths haven't worked in other FileSystemWatcher scripts. Any advice as to a workaround would be appreciated . I must monitor C:\Windows\System32\Recovery. Thank You.
function FileSystemWatcher([string]$log = "C:\Users\W\Documents\Fichiers PowerShell\newfiles.txt",
[string]$folder = "C:\Windows\System32\Recovery",
[string]$filter = "*ps1",
[char]$timeout = 1000
){
$FileSystemWatcher = New-object System.IO.FileSystemWatcher $folder, $filter
Write-Host "Press any key to abort monitoring $folder"
do {
$result = $FileSystemWatcher.WaitForChanged("created", $timeout)
if ($result.TimedOut -eq $false) {
$result.Name |
Out-File $log -Append
Write-Warning ("Detected new file: " + $result.name)
$filename = (gc $log -ea 0)
ii "C:\Program Files (x86)\Mozilla Firefox\firefox.exe"
remove-item "C:\Users\W\Documents\Fichiers PowerShell\newfiles.txt"
}
} until ( [System.Console]::KeyAvailable )
Write-Host "Monitoring aborted."
Invoke-Item $log}
FileSystemWatcher
Give this a try:
$fsw = New-Object System.IO.FileSystemWatcher C:\Windows\System32 -Property #{
IncludeSubdirectories = $true
NotifyFilter = [System.IO.NotifyFilters]::DirectoryName
}
$event = Register-ObjectEvent $fsw Created -SourceIdentifier DirectoryCreated -Action {
Write-Host "$($event.SourceArgs | fl * | Out-String)"
}
md C:\Windows\System32\temp2