Reputation: 512
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?
Upvotes: 1
Views: 3197
Reputation: 7087
The Create method returns a FileStream object. Since StreamReader is derived from Stream, My Solution was to recast as astream reader. Almost a one-liner...:
$PSFileName = 'c:\temp\testfile.txt'
$Stream = [System.IO.StreamReader][System.IO.File]::Create($PSFileName)
OR, Suggestion From Jeroen Mostert :
$PSFileName = 'c:\temp\testfile.txt'
$Stream = [System.IO.StreamReader]::New( [System.IO.File]::Create($PSFileName) )
You don't have to worry about Garbage Collection with this approach because the resulting object is referenced to the variable...
Honestly I'm not too sure about this, I believe the FileStream object can be leveraged directly to read & write, but I'm less familiar than I am with StreamReader & Writer objects, so if it were me I'd do the re-cast so I can move on, but research further later.
Also, if you use another approach I would use .CLose() instead of .Dispose(). My understanding based on the .Net documentation is close is more thorough, and calls Dispose internally anyhow...
Upvotes: 1
Reputation: 1782
You have simply to close the file handle. Try:
$fh = [System.IO.File]::Create($PSFileName)
[void]$fh.Close()
[void]$fh.Dispose()
$ReadPS = New-Object System.IO.StreamReader($PSFileName)
Upvotes: 1
Reputation: 10333
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.
Upvotes: 2