I haven't delved into the code of Get-FileHash but it propably opens the file with FileShare.Read flag only. This will fail if the file has already been opened with FileShare.Write or FileShare.ReadWrite. Sharing flags specified by subsequent processes must be compatible with flags used by original process that opened the file, see this QA for details.
A workaround is to explicitly create a stream using the desired flags and pass it to Get-FileHash parameter -InputStream:
Get-ChildItem 'C:\' -File -Recurse -PipelineVariable File | ForEach-Object {
$stream = try {
[IO.FileStream]::new( $File.FullName, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::Read )
}
catch {
# Fallback in case another process has opened the file with FileShare.ReadWrite flag.
[IO.FileStream]::new( $File.FullName, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite )
}
if( $stream ) {
try {
Get-FileHash -InputStream $stream -Algorithm MD5 |
Select-Object Algorithm, Hash, @{ Name = 'Path'; Expression = { $File.Fullname } }
}
finally {
$stream.Close()
}
}
}
NOTES:
-PipelineVariable File is used to avoid disambiguities of the $_ variable in the catch block and in the Expression script block of the Get-FileHash call.
$stream = try { } catch { } is a convenient way to capture both the output of the try and the catch block without having to repeat the variable name. It is equivalent to try { $stream =[IO.FileStream]::new(...)} catch { $stream =[IO.FileStream]::new(...)}. This works for many other statements like if / else, switch and for too.
- Using
Select-Object, a calculated property is added to fix the Path property. When using -InputStream the Get-FileHash cmdlet has no idea of the path, so it would output an empty Path property instead.
- It is always a good idea to explicitly close streams in
finally block, due to the indeterministic nature of the garbage collector which eventually closes the file but possibly very late. Shared resources such as files should only be kept open as long as necessary to avoid unneccessary blocking of other processes.
- On a side note,
Get-Content doesn't have a problem to read files opened with FileShare.ReadWrite. It is propably worth investigating, how Get-FileHash is implemented compared to Get-Content and possibly create an issue in the PowerShell GitHub project.