diff --git a/CHANGELOG.md b/CHANGELOG.md
index bac6de6..1023796 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -126,5 +126,7 @@ The format is based on
([#74](https://github.com/raandree/NTFSSecurity/issues/74))
- Fix the error of `New-NTFSHardLink` for a missing `-Target`, which said
that the target path existed
+- Fix `Get-FileHash2`, which wrote a result for a file that it couldn't
+ read, with the hash of the previous file
[Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD
diff --git a/Docs/Cmdlets/Get-FileHash2.md b/Docs/Cmdlets/Get-FileHash2.md
index 3867548..6dd281e 100644
--- a/Docs/Cmdlets/Get-FileHash2.md
+++ b/Docs/Cmdlets/Get-FileHash2.md
@@ -119,13 +119,13 @@ The cmdlet does not return access rules. For every hashed file it writes the fil
The cmdlet works only in Windows PowerShell. In PowerShell 7, it fails for every algorithm with the error `Could not load type 'System.Security.Cryptography.RIPEMD160'`, because .NET no longer includes the RIPEMD-160 implementation that the cmdlet references. In PowerShell 7, use the built-in `Get-FileHash` cmdlet instead.
-If the file cannot be opened because access is denied, the cmdlet takes ownership of the file with the account that runs it, calculates the hash, and restores the previous owner afterward. That fallback fails with a `GetHashError` when the account is not allowed to change the owner of the file.
+If the file cannot be opened because access is denied, the cmdlet takes ownership of the file with the account that runs it, calculates the hash, and restores the previous owner afterward. That fallback fails with a `GetHashError` when the account is not allowed to change the owner of the file. A file that cannot be read produces a `GetHashError` and no result.
The hash is returned as an uppercase hexadecimal string without separators, which differs from the lowercase output of some other hashing tools. Compare hash values case-insensitively.
`MACTripleDES` is a keyed message authentication code that is created with a key that is generated for each call, so its result is not reproducible across invocations and is not suitable for comparing files.
-Before 5.0.0, a folder in a `-Path` array stopped the processing of that array, so the files that followed the folder were not hashed.
+Before 5.0.0, a folder in a `-Path` array stopped the processing of that array, so the files that followed the folder were not hashed, and a file that could not be read got a result with the hash of the previous file.
## RELATED LINKS
diff --git a/NTFSSecurity/MiscCmdlets/GetFileHash2.cs b/NTFSSecurity/MiscCmdlets/GetFileHash2.cs
index b717d2b..52a01b8 100644
--- a/NTFSSecurity/MiscCmdlets/GetFileHash2.cs
+++ b/NTFSSecurity/MiscCmdlets/GetFileHash2.cs
@@ -39,11 +39,11 @@ namespace NTFSSecurity
protected override void ProcessRecord()
{
- string hash = string.Empty;
- FileSystemInfo item = null;
-
foreach (var path in paths)
{
+ string hash = null;
+ FileSystemInfo item = null;
+
try
{
item = GetFileSystemInfo2(path) as FileInfo;
@@ -80,11 +80,13 @@ namespace NTFSSecurity
catch (Exception ex2)
{
WriteError(new ErrorRecord(ex2, "GetHashError", ErrorCategory.WriteError, path));
+ continue;
}
}
catch (Exception ex)
{
WriteError(new ErrorRecord(ex, "GetHashError", ErrorCategory.WriteError, path));
+ continue;
}
var result = new PSObject(item);
diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
index aa824f6..23a434b 100644
--- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
+++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
@@ -4150,10 +4150,10 @@ PS C:\> Disable-Privileges
The cmdlet works only in Windows PowerShell. In PowerShell 7, it fails for every algorithm with the error `Could not load type 'System.Security.Cryptography.RIPEMD160'`, because .NET no longer includes the RIPEMD-160 implementation that the cmdlet references. In PowerShell 7, use the built-in `Get-FileHash` cmdlet instead.
- If the file cannot be opened because access is denied, the cmdlet takes ownership of the file with the account that runs it, calculates the hash, and restores the previous owner afterward. That fallback fails with a `GetHashError` when the account is not allowed to change the owner of the file.
+ If the file cannot be opened because access is denied, the cmdlet takes ownership of the file with the account that runs it, calculates the hash, and restores the previous owner afterward. That fallback fails with a `GetHashError` when the account is not allowed to change the owner of the file. A file that cannot be read produces a `GetHashError` and no result.
The hash is returned as an uppercase hexadecimal string without separators, which differs from the lowercase output of some other hashing tools. Compare hash values case-insensitively.
`MACTripleDES` is a keyed message authentication code that is created with a key that is generated for each call, so its result is not reproducible across invocations and is not suitable for comparing files.
- Before 5.0.0, a folder in a `-Path` array stopped the processing of that array, so the files that followed the folder were not hashed.
+ Before 5.0.0, a folder in a `-Path` array stopped the processing of that array, so the files that followed the folder were not hashed, and a file that could not be read got a result with the hash of the previous file.
diff --git a/Tests/FileHash.Tests.ps1 b/Tests/FileHash.Tests.ps1
index 64d9cba..2ffb4c4 100644
--- a/Tests/FileHash.Tests.ps1
+++ b/Tests/FileHash.Tests.ps1
@@ -42,4 +42,24 @@ Describe 'Get-FileHash2' {
$results[1].Hash | Should -BeExactly (Get-FileHash -LiteralPath $second -Algorithm SHA256).Hash
}
}
+
+ Context 'When a file cannot be read' {
+ # Before 5.0.0, the cmdlet wrote a result for the file anyway, with the hash of the previous file.
+ It 'Should write an error and no result for the file' {
+ if ($PSVersionTable.PSEdition -eq 'Core') {
+ Set-ItResult -Skipped -Because 'Get-FileHash2 fails in PowerShell 7 until it no longer references RIPEMD160'
+ }
+ $locked = New-TestSandboxItem -Sandbox $sandbox -Name 'Locked'
+ $stream = [IO.File]::Open($locked, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::None)
+ try {
+ $results = @(Get-FileHash2 -Path $first, $locked -ErrorVariable hashErrors -ErrorAction SilentlyContinue)
+ }
+ finally {
+ $stream.Dispose()
+ }
+
+ $hashErrors | Should -HaveCount 1
+ $results.Name | Should -Be @('One.txt')
+ }
+ }
}