Search code examples
powershellfilesystemwatcher

Powershell monitor multiple directories


Note: Running PowerShell v3

I've got a directory setup that is currently:

\ftproot\001\converted
\ftproot\001\inbound
\ftproot\001\pdf

\ftproot\002\converted
\ftproot\002\inbound
\ftproot\002\pdf

\ftproot\xxx\converted
\ftproot\xxx\inbound
\ftproot\xxx\pdf

Each FTP user is mapped to an inbound directory. The structure can be changed if this makes the solution easier

\ftproot\converted\001
\ftproot\converted\002
\ftproot\converted\xxx

\ftproot\inbound\001
\ftproot\inbound\002
\ftproot\inbound\xxx

\ftproot\pdf\001
\ftproot\pdf\002
\ftproot\pdf\xxx

I need to monitor each inbound directory for TIFF files and pickup new FTP users and inbound directories as they are added (following the same structure), so I'm not looking to modify the script for each new user.

The script is currently like:

$fileDirectory = "\ftproot";
$folderMatchString = "^[0-9]{3}$";
$inboundDirectory = "inbound";
$filter = "*.tif";

$directories = dir -Directory $fileDirectory | Where-Object {$_.Name -match $folderMatchString}

foreach ($directory in $directories) { 

  $sourcePath = "$fileDirectory\$directory\$inboundDirectory"

  if(Test-Path -Path $sourcePath -pathType container ) { 

        // Problem is here //
        $fsw = New-Object IO.FileSystemWatcher $sourcePath, $filter -Property @{
            IncludeSubdirectories = $false
            NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
        }

  }

}


$onCreated = Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -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"

}

I'm wrapping the New-Object IO.FileSystemWatcher in a loop so in the case of the above example, only the last directory will be monitored.

Is it possible to create an array or list or similar of IO.FileSystemWatcher? And if so, how can I code the Register-ObjectEvent for each instance? There will be over a hundred directories to monitor in the end (and slow increasing) but the same action applies to all inbound folders.

Or is there a better solution I should investigate?

The process is the same for each FTP user, the files need to say within directories that can be linked back to the user. The inbound directory will be monitored for uploaded TIFF files, when a file is detected a multi page TIFF is split into individual files and moved into the converted directory, when a new file is detected in converted another action is performed on the TIFF file, then it is converted to PDF and moved to the PDF folder.

Thank you

Solution

$result = @($directoriesOfInterest | ? { Test-Path -Path $_ } | % {

$dir = $_;

$fsw = New-Object IO.FileSystemWatcher $dir, $filter -Property @{
            IncludeSubdirectories = $false
            NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
        };

$oc = Register-ObjectEvent $fsw Created -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";

};

new-object PSObject -Property @{ Watcher = $fsw; OnCreated = $oc };

});

Solution

  • Is it possible to create an array or list or similar of IO.FileSystemWatcher?

    Yes. Making use of a pipeline would be something like:

    $fsws = $directoriesOfInterest | ? { Test-Path -Path $_ } | % {
              new-object IO.FileSystemWatcher …
            }
    

    and $fsws will be an array, if there is more than one watcher created (it will be a waster if there is only one, $null if there are none). To always get an array put the pipeline in @(…):

    $fsws = @($directoriesOfInterest | ? { Test-Path -Path $_ } | % {
              new-object IO.FileSystemWatcher …
            })
    

    if so, how can I code the Register-ObjectEvent for each instance?

    Put the object registration in the same loop, and return both the watcher and the event registration:

    $result = @($directoriesOfInterest | ? { Test-Path -Path $_ } | % {
                # $_ may have a different value in code in inner pipelines, so give it a
                # different name.
                $dir = $_;
                $fsw = new-object IO.FileSystemWatcher $dir, …;
                $oc =  Register-ObjectEvent $fsw Created …;
                new-object PSObject -props @{ Watcher = $fsw; OnCreated = $oc };
              })
    

    and $result will be an array of objects will those two properties.

    Note the -Action code passed to Register-ObjectEvent can reference $fsw and $dir and thus know which watcher has fired its event.