3

To open and read files in powershell I use one of two methods:

Get-Content $path

or

[System.IO.File]::OpenRead($path)

While reading a log file that is in use by another process Get-Content doesn't seem to have any issues with it. Then again the powershell cmdlet is slow and uses more memory compared to .NET method. When I try to use .NET method however, I get the following error:

"The process cannot access the file 'XYZ' because it is being used by another process."

Q1: Why can't .net method access the file whilst powershell cmdlet can?

Q2: And how could I read the file with .net method? Since Get-Content is too slow for log files around 80 MB. I usually read just the last line with:

$line = ""
$lineBreak = Get-Date -UFormat "%d.%m.%Y "
$bufferSize = 30
$buffer = New-Object Byte[] $bufferSize
$fs = [System.IO.File]::OpenRead($logCopy)
while (([regex]::Matches($line, $lineBreak)).Count -lt $n) {
    $fs.Seek(-($line.Length + $bufferSize), [System.IO.SeekOrigin]::End) | Out-Null
    $fs.Read($buffer, 0, $bufferSize) | Out-Null
    $line = [System.Text.Encoding]::UTF8.GetString($buffer) + $line
}
$fs.Close()

    ($line -split $lineBreak) | Select -Last $n
}

Author to original code on StackOverflow

Any help greatly appreciated !

PS! I'm using powershell 2.0 and can't kill the process that is using the file. Also I do not have write access to the file, just read.

Ajama
  • 98
  • 9

2 Answers2

4

PetSerAl, as usual, has provided a terse comment that provides an effective solution and implies an explanation:

To prevent the "The process cannot access the file 'XYZ' because it is being used by another process." error, you must open the file with sharing mode FileShare.ReadWrite, so that other processes that want to write to the file aren't denied access.

This is what Get-Content (invariably) does behind the scenes, which explains why the problem doesn't surface when you use it.

By contrast, [System.IO.File]::OpenRead() defaults to sharing mode FileShare.Read, meaning that other processes can read from, but not write to the same file.

Therefore, use [System.IO.File]::Open() instead, which allows you to specify the sharing mode explicitly:

$fs = [IO.File]::Open($path, 
                  [IO.FileMode]::Open, 
                  [IO.FileAccess]::Read, 
                  [IO.FileShare]::ReadWrite)
# ...
$fs.Close()

Note that I've omitted the System. component from the type names above; this component is always optional in PowerShell.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thank you for the great explanation! Can You perhaps direct me where I could get more info about those .net methods in powershell? Ones I've found are for .net itself and therefore use different syntax and not everything is supported in PS. – Ajama Aug 01 '18 at 19:23
  • @Ajama: My pleasure. The only documentation I'm aware of is [`Get-Help about_Methods`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_methods?view=powershell-6). There are some pitfalls and restrictions, but you should be able to call most .NET methods from PowerShell. Unfortunately, the .NET documentation doesn't offer switching to PowerShell syntax (you can only choose among C#, VB, F#, and C++), so you'll figure out it yourself based on knowing PowerShell's syntax. – mklement0 Aug 01 '18 at 20:35
1

If you could move to more later version of PowerShell (at least v3.0), then Get-Content -Tail is a good option. We use it widely and performance is good for our scenarios.

Official docs

Gets the specified number of lines from the end of a file or other item.
This parameter is introduced in Windows PowerShell 3.0.
You can use the "Tail" parameter name or its alias, "Last".

Subbu
  • 2,130
  • 1
  • 19
  • 28