Reputation: 71
I want to use Get-FileHash
to populate a set of hash for certain directories. Here is the code :
dir "C:\" -Recurse | Get-FileHash -Algorithm MD5
But it show below error :
Get-FileHash : The file 'C:\Intel\Logs\IntelCPHS.log' cannot be read: The process cannot access the file 'C:\Intel\Logs\IntelCPHS.log' because it is being used by another process. At :2 char:22 + dir "C:\" -Recurse | Get-FileHash -Algorithm MD5| Export-Csv -Path "C ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ReadError: (C:\Intel\Logs\IntelCPHS.log:PSObject) [Write-Error], WriteErrorException + FullyQualifiedErrorId : FileReadError,Get-FileHash
Kindly help on this or is there any other alternative for populating hashes?
Upvotes: 1
Views: 3027
Reputation: 27756
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.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.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.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.Upvotes: 2
Reputation: 27408
With Sysinternals process explorer, you can search for the file handle and see which process is locking it.
Upvotes: 0
Reputation: 3
As @Alex_P mentions, turn off the process, I believe it's IntelCpHeciSvc.exe. If you run into a lot of these, you could also try to run the CMDlet while booting Windows in "Safe Mode", by doing that you can get rid of a lot of background processes that may trigger this error.
Feel free to ask further questions if you need some additional help.
Upvotes: 0