Search code examples
powershellfilesystemwatcher

File Monitor Archiver Powershell


I have written some PowerShell that checks for new files and when a new file is created it runs an action.

The action is to check the filename and check for a previous version, if there is a previous version move the previous version to an archive folder. The previous version is defined as such:

filename structure: xxxx-xxxx-xx.pdf

First xxxx can be any amount of characters + numbers.

Second xxxx can be any amount of characters + numbers.

Third part is only ever numerical 00 to 99 e.g. 01 02 03

So if I drop in file 85080-00-02.pdf and 85080-00-01.pdf exists it will move the latter file to the archive folder, now this is all working OK with the code I have at the moment.

This system is going to be used by standard engineering staff so I need to ensure all user error is catered for. When I run my code with characters in the 3rd part e.g. 85080-00-0t (imagine someone accidently typed a t instead of a 5, don't ask me how but I know it will happen) it will just stop working.

  1. Why don't I see any errors anywhere? If I put a Write-Host in the code, I can see it display in ISE the output but I don't see any errors for when the above happens.

  2. How do I get this to work correctly? I can tell the error is because my code is trying to -1 from a string and that’s obviously causing it problem.

So how can I say:

If $olddw = any number from 00 - 99 carry on with code, else do nothing.

# Enter the root folder you want to monitor
$folder = **FOLDER LOCATION**
$archivefolder = **ARCHIVE FOLDER LOCATION**

# Enter the log file location
$log = **LOG LOCATION**

# You can enter a wildcard filter here. 
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $folder, $filter -Property @{
    IncludeSubdirectories = $false;
    NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'
}

#New file ObjectEvent
Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action { 
    #Get new filename and create log
    $name       = $Event.SourceEventArgs.Name 
    $changeType = $Event.SourceEventArgs.ChangeType 
    $timeStamp  = $Event.TimeGenerated  
    Out-File -FilePath $log -Append -InputObject "The file '$name' was $changeType at $timeStamp"

    #Set old filename 
    $oldname = $name.Substring(0, $name.Length-4)
    $olddw = "{0:D2}" -f (($oldname.Substring($oldname.Length - 2)) - 1)
    $oldfilename = ($oldname.Substring(0, $oldname.Length-2) + $olddw + ".pdf")
    $oldfilelocation = ($folder + $oldfilename)

    #Check if old filename exists if so move it to archive folder
    $FileExists = Test-Path $oldfilelocation
    if ($FileExists -eq $true) {
        Move-Item $oldfilelocation $archivefolder
        Out-File -FilePath $log -Append -InputObject "The file '$oldfilename' was Archived at $timeStamp"
    } else {
        # do nothing
    }
}

#To view current ObjectEvent's
#Get-EventSubscriber

#To stop current ObjectEvent
#Unregister-Event FileCreated 

Solution

  • What you have there is a prime example for why paths should never be built using string concatenation. What's most likely happening is that you defined $folder without a trailing (back)slash:

    $folder = 'C:\some\folder'
    

    so that the statement

    $oldfilelocation = ($folder + $oldfilename)
    

    constructs a path like this:

    C:\some\folder85080-00-02.pdf
    #            ^^
    #             `- lack of backslash here!

    which doesn't exist, so Test-Path returns $false and nothing is moved.

    Change this line:

    $oldfilelocation = ($folder + $oldfilename)
    

    into this:

    $oldfilelocation = Join-Path $folder $oldfilename
    

    and the problem should disappear.

    An even better solution would be to work with a FileInfo object (via the FullPath argument):

    Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action { 
        $name       = $Event.SourceEventArgs.Name
        $path       = $Event.SourceEventArgs.FullPath
        $changeType = $Event.SourceEventArgs.ChangeType
        $timeStamp  = $Event.TimeGenerated
    
        Out-File -FilePath $log -Append -InputObject "The file '$name' was $changeType at $timeStamp"
    
        $f = Get-Item $path
    
        $cutoff = $f.BaseName.Length - 2
        $olddw  = '{0:D2}' -f (($f.BaseName.Substring($cutoff)) - 1)
        $oldfilename = ($f.BaseName.Substring(0, $cutoff) + $olddw + $f.Extension)
        $oldfilelocation = Join-Path $f.Directory.FullName $oldfilename
    
        if (Test-Path $oldfilelocation) {
            Move-Item $oldfilelocation $archivefolder
            Out-File -FilePath $log -Append -InputObject "The file '$oldfilename' was Archived at $timeStamp"
        }
    }
    

    If you also want to handle typos in the 2-digit index do something like this:

    Register-ObjectEvent $fsw Created -SourceIdentifier FileCreated -Action { 
        $name       = $Event.SourceEventArgs.Name
        $path       = $Event.SourceEventArgs.FullPath
        $changeType = $Event.SourceEventArgs.ChangeType
        $timeStamp  = $Event.TimeGenerated
    
        Out-File -FilePath $log -Append -InputObject "The file '$name' was $changeType at $timeStamp"
    
        $f = Get-Item $path
    
        $cutoff = $f.BaseName.Length - 2
        $index  = $f.BaseName.Substring($cutoff)
        if ($index -like '[0-9][0-9]') {
            $oldindex = '{0:D2}' -f ([int]$index - 1)
            $oldfilename = ($f.BaseName.Substring(0, $cutoff) + $oldindex + $f.Extension)
            $oldfilelocation = Join-Path $f.Directory.FullName $oldfilename
    
            if (Test-Path $oldfilelocation) {
                Move-Item $oldfilelocation $archivefolder
                Out-File -FilePath $log -Append -InputObject "The file '$oldfilename' was Archived at $timeStamp"
            }
        }
    }