Search code examples
powershellfile-handling

Powershell StreamReader - how to wait for a new file to be readable


My script generally assumes the existence of a *.txt file with settings to help it function better. However, if the script doesn't exist, it creates a local file to hold these settings. I realise there's no logical need to then read this file, but I'd like to understand why I can't.

[void][System.IO.File]::Create($PSFileName)
$ReadPS = New-Object System.IO.StreamReader($PSFileName)

Immediately after the script may (rarely) create the file, it attempts to read it, which generates the following error: New-Object : Exception calling ".ctor" with "1" argument(s): "The process cannot access the file 'C:\Temp\MyFile.txt' because it is being used by another process."

So I have to wait for the file to be available, right? Yet a simple start-sleep for 5s doesn't work. But if I wrap it in a loop with a try-catch, it works within a fraction of a second every time:

[void][System.IO.File]::Create($PSFileName)
$RCount = 0                                                             # if new file created, sometimes it takes a while for the lock to be released. 
Do{
    try{
        $ReadPS = New-Object System.IO.StreamReader($PSFileName)
        $RCount+=100
    }catch{                                                             # if error encountered whilst setting up StreamReader, try again up to 100 times.
        $RCount++
        Start-Sleep -Milliseconds 1                                     # Wait long enough for the process to complete. 50ms seems to be the sweet spot for fewest loops at fastest performance
    }
}Until($RCount -ge 100)
$ReadPS.Close()
$ReadPS.Dispose()

This is overly convoluted. Why does the file stay locked for an arbitrary length of time that seems to increase the more I wait for it? Is there anything I can adjust or add between the file creation and the StreamReader to ensure the file is available?


Solution

  • As it was already mentioned in the comments, the method you are using does create a lock on the file, which stays until you call the close / dispose method or the powershell session end.

    That's why the more you wait for it, the longer your session stays open and the longer the lock on the file is maintained.

    I'd recommend you to just use New-Item instead which is the Powershell native way to do it.

    Since you are creating a StreamReader object though, don't forget to close / dispose the object once you are over.

    New-Item -Path $PSFileName -ItemType File
    $ReadPS = New-Object System.IO.StreamReader($PSFileName)
    
    #Stuff
    
    $ReadPS.Close()
    $ReadPS.Dispose()
    

    Finally, if for some reason you still wanted to use [System.IO.File]::Create($PSFileName), you will also need to call the close method to free the lock.