Is there any reason that when using Get-Content -path file.txt, the CreateFileW API parameters are:
dwDesiredAccess GENERIC_READ
dwShareMode FILE_SHARE_READ | FILE_SHARE_WRITE
lpSecurityAttributes NULL
dwCreationDisposition OPEN_EXISTING
dwFlagsAndAttributes FILE_FLAG_OPEN_NO_RECALL
If I use: [System.IO.File]::ReadAllLines("file.txt")
dwDesiredAccess GENERIC_READ
dwShareMode FILE_SHARE_READ
lpSecurityAttributes NULL
dwCreationDisposition OPEN_EXISTING
dwFlagsAndAttributes FILE_FLAG_OPEN_NO_RECALL | FILE_FLAG_SEQUENTIAL_SCAN
Why is FILE_SHARE_WRITE set for a read? Any ideas? Thanks.
CodePudding user response:
The share mode parameter is used to indicate which types of shared concurrent accesses are acceptable to the caller - Get-Content
is using FILE_SHARE_READ | FILE_SHARE_WRITE
to tell the operating system "I don't care if anyone else wants to read or write to the file while I hold this file handle".
This is important when continously tailing a log file for example:
# imagine this is a log file
$tmpFile = New-TemporaryFile
$backgroundJob = Start-Job {
param([string]$LiteralPath)
1..10 |ForEach-Object {
"Log entry $_" |Add-Content @PSBoundParameters
Start-Sleep -Milliseconds 500
}
} -ArgumentList $tmpFile.FullName |Out-Null
# Tail the file and read the 10 log message as they roll in
$tmpFile |Get-Content -Wait |Select -First 10
$tmpFile |Remove-Item
If you run the code above, you'll observe that Get-Content -Wait
is able to continuously read from the file even though we open it and write to it from the background job 10 times.
This would not have been possible had the underlying file handle not been opened with FILE_SHARE_WRITE
.
To observe this, let's take the same example and read from a file handle opened with only FILE_SHARE_READ
:
$tmpFile = New-TemporaryFile
$backgroundJob = Start-Job {
param([string]$LiteralPath)
1..10 |ForEach-Object {
"Log entry $_" |Add-Content @PSBoundParameters
Start-Sleep -Milliseconds 500
}
} -ArgumentList $tmpFile.FullName
try {
# OpenRead() will open the file with SHARE_READ
$rfs = [System.IO.StreamReader]::new($tmpFile.OpenRead())
# Attempt to read the log lines, time out after 10 x 500ms
$c = 10
while(--$c){
$line = $rfs.ReadLine()
if($line -is [string]){
Write-Host $line
}
else{
# wait a little longer
Start-Sleep -Milliseconds 500
}
}
}
finally {
# clean up file reader
$rfs |ForEach-Object Dispose
$tmpFile |Remove-Item
}
# Fetch background job output, observe file access errors
$backgroundJob |Receive-Job -AutoRemoveJob -Wait
Instead of the log messages being displayed on the screen, you should expect to see the background job failing to log the messages to the file:
The process cannot access the file 'C:\Users\irm\AppData\Local\Temp\tmpECB9.tmp' because it is being used by another
process.
CategoryInfo : WriteError: (C:\Users\irm\Ap...emp\tmpECB9.tmp:String) [Add-Content], IOException
FullyQualifiedErrorId : GetContentWriterIOError,Microsoft.PowerShell.Commands.AddContentCommand
PSComputerName : localhost