1

The below code is checking for new files in a specified directory $folderCompleted.

Currently, when a small file is placed into that directory (~1MB) the Move-Item command and other file reading checks complete successfully.

However when a large file is moved into this directory, the Object event is called before the file is completely moved (copied over) into that directory. This causing the file checks and the Move-Item command to fail since the file is still in use.

# File Watcher
$filter = '*.*'
$fsw = New-Object IO.FileSystemWatcher $folderCompleted, $filter -Property @{
    IncludeSubdirectories = $true
    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"

    # File Checks

    # Move File
    Move-Item $path -Destination $destinationPath -Verbose
}

How can I place a check to see if the file is not still being copied over?

2
  • You need to add code to monitor for completion. Try/catch, etc... Commented Jun 13, 2020 at 2:41
  • Yes, the question is "what would that code look like?" Commented Jun 13, 2020 at 3:36

3 Answers 3

2

Solved this shortly after posting my comment above. Other answers may be similar but ended up adding this function towards the top of the script.

# function Test-FileLock 
function Test-FileLock {
  param ([parameter(Mandatory=$true)][string]$Path)

  $oFile = New-Object System.IO.FileInfo $Path

  if ((Test-Path -Path $Path) -eq $false)
  {
    return $false
  }

  try
  {
      $oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
      if ($oStream)
      {
        $oStream.Close()
      }
      $false
  }
  catch
  {
    # file is locked by a process.
    return $true
  }
}

Added this code after the variables section in the $onCreated section.

# Test File Lock
Do {
    $filetest = Test-FileLock -Path $path
    sleep -Seconds 5
} While ($filetest -eq $true)
Sign up to request clarification or add additional context in comments.

Comments

1

Try this. (had a similar concern previously).

The event trigger is collecting all affected items in a synchronized hashtable and a additional scriptblock processes all items, but files only when they are ready to read and not locked. If a file is locked, you'll see it in the console. Just try to copy a file > 1 GB and watch the output.

The script block $fSItemEventProcessingJob which processes the items (like copying to backup files) was initially created to use it for "Start-Job". But you are not able to access and modify the hashtable from the session in that background job. Therefore it's a simple scriptblock execution.

To stop everything, just press CTRL + C. This will still execute the "finally" block and unregisters and disposes everything.

PS: The script was just a test and was only tested locally on my PC.

Clear-Host
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop


$fileSystemWatcherDirPath = 'C:\temp'
$fileSystemWatcherFilter = '*.*'

$fileSystemWatcher = [System.IO.FileSystemWatcher]::new($fileSystemWatcherDirPath , $fileSystemWatcherFilter)
$fileSystemWatcher.IncludeSubdirectories = $true
$fileSystemWatcher.EnableRaisingEvents = $true
$fileSystemWatcher.NotifyFilter = [System.IO.NotifyFilters]::FileName -bor [System.IO.NotifyFilters]::DirectoryName -bor [System.IO.NotifyFilters]::LastWrite  # [System.Linq.Enumerable]::Sum([System.IO.NotifyFilters].GetEnumValues())

# Create syncronized hashtable
$syncdFsItemEventHashT = [hashtable]::Synchronized([hashtable]::new())

$fileSystemWatcherAction = {
    try {
        $fsItemEvent = [pscustomobject]@{
            EventIdentifier  = $Event.EventIdentifier
            SourceIdentifier = $Event.SourceIdentifier
            TimeStamp        = $Event.TimeGenerated
            FullPath         = $Event.SourceEventArgs.FullPath
            ChangeType       = $Event.SourceEventArgs.ChangeType
        }

        # Collecting event in synchronized hashtable (overrides existing keys so that only the latest event details are available)
        $syncdFsItemEventHashT[$fsItemEvent.FullPath] = $fsItemEvent
    } catch {
        Write-Host ($_ | Format-List * | Out-String ) -ForegroundColor red
    }
}


# Script block which processes collected events and do further actions like copying for backup, etc...
# That scriptblock was initially used to test "Start-Job". Unfortunately it's not possible to access and modify the synchronized hashtable created within this scope.
$fSItemEventProcessingJob = {
    $keys = [string[]]$syncdFsItemEventHashT.psbase.Keys

    foreach ($key in $keys) {
        $fsEvent = $syncdFsItemEventHashT[$key]

        try {
            # in case changetype eq DELETED or the item can't be found on the filesystem by the script -> remove the item from hashtable without any further actions.
            # This affects temporary files from applications. BUT: Could also affect files with file permission issues.
            if (($fsEvent.ChangeType -eq [System.IO.WatcherChangeTypes]::Deleted) -or (! (Test-Path -LiteralPath $fsEvent.FullPath)) ) {
                $syncdFsItemEventHashT.Remove($key )
                Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' removed from hashtable without any further actions!") -ForegroundColor Blue
                continue
            }

            # get filesystem object
            $fsItem = Get-Item -LiteralPath $fsEvent.FullPath -Force

            if ($fsItem -is [System.IO.FileInfo]) {
                # file processing

                try {
                    # Check whether the file is still locked / in use by another process
                    [System.IO.FileStream]$fileStream = [System.IO.File]::Open( $fsEvent.FullPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
                    $fileStream.Close()
                } catch [System.IO.IOException] {
                    Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' is still in use and can't be read!") -ForegroundColor Yellow
                    continue
                }
            } elseIf ($fsItem -is [System.IO.DirectoryInfo]) {
                # directory processing
            }

            $syncdFsItemEventHashT.Remove($key )
            Write-Host ("==> Item '$key' with changetype '$($fsEvent.ChangeType)' has been processed and removed from hashtable.") -ForegroundColor Blue

        } catch {
            Write-Host ($_ | Format-List * | Out-String ) -ForegroundColor red
        }
    }
}

[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Created' -SourceIdentifier 'FSCreated' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Changed' -SourceIdentifier 'FSChanged' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Renamed' -SourceIdentifier 'FSRenamed' -Action $fileSystemWatcherAction)
[void] (Register-ObjectEvent -InputObject $fileSystemWatcher -EventName 'Deleted' -SourceIdentifier 'FSDeleted' -Action $fileSystemWatcherAction)



Write-Host "Watching for changes in '$fileSystemWatcherDirPath'.`r`nPress CTRL+C to exit!"
try {
    do {
        Wait-Event -Timeout 1

        if ($syncdFsItemEventHashT.Count -gt 0) {
            Write-Host "`r`n"
            Write-Host ('-' * 50) -ForegroundColor Green
            Write-Host "Collected events in hashtable queue:" -ForegroundColor Green
            $syncdFsItemEventHashT.Values | Format-Table | Out-String
        }

        # Process hashtable items and do something with them (like copying, ..)
        .$fSItemEventProcessingJob

        # Garbage collector
        [GC]::Collect()

    } while ($true)

} finally {
    # unregister
    Unregister-Event -SourceIdentifier 'FSChanged'
    Unregister-Event -SourceIdentifier 'FSCreated'
    Unregister-Event -SourceIdentifier 'FSDeleted'
    Unregister-Event -SourceIdentifier 'FSRenamed'

    # dispose
    $FileSystemWatcher.Dispose()
    Write-Host "`r`nEvent Handler removed."
}

Comments

0

You need to build in a test to see if the file is locked (still being copied) or not. For that, you can use this function:

function Test-LockedFile {
    param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName', 'FilePath')]
        [ValidateScript({Test-Path $_ -PathType Leaf})]
        [string]$Path
    )
    $file = [System.IO.FileInfo]::new($Path)
    # old PowerShell versions use:
    # $file = New-Object System.IO.FileInfo $Path

    try {
        $stream = $file.Open([System.IO.FileMode]::Open,
                             [System.IO.FileAccess]::ReadWrite,
                             [System.IO.FileShare]::None)
        if ($stream) { $stream.Close() }
        return $false
    }
    catch {
        return $true
    }
}

Having that in place, somewhere above your current code, you can do:

# File Checks
while (Test-LockedFile $path) {
    Start-Sleep -Seconds 1
}

# Move File
Move-Item $path -Destination $destinationPath -Verbose

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.