Home > Back-end >  PowerShell - Get-Content - CreateFile sets dwShareMode to be FILE_SHARE_READ | FILE_SHARE_WRITE
PowerShell - Get-Content - CreateFile sets dwShareMode to be FILE_SHARE_READ | FILE_SHARE_WRITE

Time:04-02

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
  • Related