0

I want to know if its bad form to use try blocks to test if a file is locked. Here's the background. I need to send text output of an application to two serial printers simultaneously. My solution was to use MportMon, and a Powershell script. The way it's supposed to work is the application default prints to the MportMon virtual printer port, which actually makes a uniquely named file in a "dropbox" folder. The powershell script uses a filesystemwatcher to monitor the folder and when a new file is created, it takes the textual content and pushes it out two serial printers, then deletes the file, so as not to fill up the folder. I was having a problem when trying to read the text from the file that the virtual printer created. I found that I was getting errors becasue the file was still locked. To fixed the problem, I used a FSM to impliment the logic and instead of checking for a lock everytime before attempting to get the content from the file, I used a try block that attempts to read content from the file, if it fails, the catch block just reaffirms the state that the FSM is in, and the process is repeated until successful. It seems to work fine, but I've read somewhere that its bad practice. Is there any danger in this method, or is it safe and reliable? Below is my code.

$fsw = New-Object system.io.filesystemwatcher
$q = New-Object system.collections.queue
$path = "c:\DropBox"
$fsw.path = $path
$state = "waitforQ"
[string]$tempPath = $null

    Register-ObjectEvent -InputObject $fsw -EventName created -Action {
    $q.enqueue( $event.sourceeventargs.fullpath )
    }

    while($true) {
    switch($state)
    {
        "waitforQ" {
                    echo "waitforQ"
                    if ($q.count -gt 0 ) {$state = "retrievefromQ"}
                    }
        "retrievefromQ"  {
                    echo "retrievefromQ"
                    $tempPath = $q.dequeue()
                    $state = "servicefile"
                    }
        "servicefile"  {
                        echo " in servicefile "

                    try
                        {
                        $text = Get-Content -ErrorAction stop $tempPath 
                        #echo "in try"
                        $text | out-printer db1
                        $text | out-printer db2
                         echo " $text "
                        $state = "waitforQ"
                        rm $tempPath
                        }
                    catch
                        {
                        #echo "in catch" 
                        $state = "servicefile"
                        }
                    }

           Default {    $state = "waitforQ"  }
    }
    }
Frank Jay
  • 85
  • 5
  • Do you want to link to where you read that it's bad practice, so that we can look over the purported reasoning? – briantist Sep 09 '16 at 20:24
  • There is some other discussion of PowerShell and locked files here: http://stackoverflow.com/questions/17204204/check-for-locked-files-in-directory-and-find-locking-applicaiton and http://stackoverflow.com/questions/958123/powershell-script-to-check-an-application-thats-locking-a-file – TessellatingHeckler Sep 09 '16 at 20:50
  • The SO questions you linked to in the comments are for users looking to find out which application is locking the file, something you don't seem to care about, so that's a different scenario – Frode F. Sep 10 '16 at 09:44
  • @FrodeF. I didn't say it was a duplicate of those questions, only that they have relevant discussion - ways to see if a file is locked without trying to open it and catching an exception. – TessellatingHeckler Sep 10 '16 at 19:11
  • To be honest I actually thought it was OP who wrote the comment as an answer to briantist. Didn't notice the name in the mobile app. Makes more sense now. :-) – Frode F. Sep 10 '16 at 20:35

1 Answers1

1

I wouldn't say it's bad practice to test a file to see if it's locked, but it's not as clean as checking the handles used by other processes. Personally I'd test the file like you do, but I adjust a few parts to make it safer/better.

  • That switch-statement looks way to complicated (for me), I'd replace it with a simple if-test. "If files in queue, proceed, if not, wait".
  • You need to slow down.. You will try to read the file as many times as possible while it's locked. This is a waste of resources since it will take some time for the current application to let it go and save the data to a HDD. Add some pauses. You won't notice them, but your CPU will love them. The same applies when there are no files in the queue.
  • You might benefit from adding a timeout, like max 50 attempts to read the file, to avoid the script getting stuck if one specific file is never released.

Try:

$fsw = New-Object system.io.filesystemwatcher
$q = New-Object system.collections.queue
$path = "c:\DropBox"
$fsw.path = $path
$MaxTries = 50  #50times * 0,2s sleep = 10sec timeout
[string]$tempPath = $null

Register-ObjectEvent -InputObject $fsw -EventName created -Action {
    $q.enqueue( $event.sourceeventargs.fullpath )
}

while($true) {
    if($q.Count -gt 0) {

        #Get next file in queue
        $tempPath = $q.dequeue()

        #Read file
        $text = $null
        $i = 0
        while($text -eq $null) {

            #If locked, wait and try again
            try {
                $text = Get-Content -Path $tempPath -ErrorAction Stop
            } catch {
                $i++
                if($i -eq $MaxTries) {
                    #Max attempts reached. Stops script
                    Write-Error -Message "Script is stuck on locked file '$tempPath'" -ErrorAction Stop
                } else {                
                    #Wait
                    Start-Sleep -Milliseconds 200
                }

            }
        }

        #Print file
        $text | Out-Printer db1
        $text | Out-Printer db2
        echo " $text "

        #Remove temp-file
        Remove-Item $tempPath

    }

    #Relax..
    Start-Sleep -Milliseconds 500

}
Frode F.
  • 52,376
  • 9
  • 98
  • 114
  • I don't understand where do you break the (endless) `while($true)` loop? – JosefZ Sep 10 '16 at 10:11
  • 1
    Same place as the original answer, never. He uses the script as a service and needs to kill/cancel it to stop. That's why it's so important to add sleep-statement to avoid that it locks the system. – Frode F. Sep 10 '16 at 10:13
  • Added the break (and not just the comment) now, but it might not be what he wants. If not, he can read the revision history to find the endless-loop :-) – Frode F. Sep 10 '16 at 10:25