Browse Source

fix: write no Get-FileHash2 result for a file that could not be read

Found while fixing defect 3, in the same loop: the hash variable lived
outside the loop, and after a GetHashError the cmdlet still wrote a
result for the file, with the hash of the previous file. Each path now
starts without a hash, and a failed read writes only the error.

Tests/FileHash.Tests.ps1: 1 test with a file opened without sharing;
like the other Get-FileHash2 tests, it skips in PowerShell 7 until the
RIPEMD160 reference goes (decision D5).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/102/head
Raimund Andree 4 weeks ago
parent
commit
ad899940ea
  1. 2
      CHANGELOG.md
  2. 4
      Docs/Cmdlets/Get-FileHash2.md
  3. 8
      NTFSSecurity/MiscCmdlets/GetFileHash2.cs
  4. 4
      NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
  5. 20
      Tests/FileHash.Tests.ps1

2
CHANGELOG.md

@ -126,5 +126,7 @@ The format is based on
([#74](https://github.com/raandree/NTFSSecurity/issues/74)) ([#74](https://github.com/raandree/NTFSSecurity/issues/74))
- Fix the error of `New-NTFSHardLink` for a missing `-Target`, which said - Fix the error of `New-NTFSHardLink` for a missing `-Target`, which said
that the target path existed 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 [Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD

4
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. 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. 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. `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 ## RELATED LINKS

8
NTFSSecurity/MiscCmdlets/GetFileHash2.cs

@ -39,11 +39,11 @@ namespace NTFSSecurity
protected override void ProcessRecord() protected override void ProcessRecord()
{ {
string hash = string.Empty;
FileSystemInfo item = null;
foreach (var path in paths) foreach (var path in paths)
{ {
string hash = null;
FileSystemInfo item = null;
try try
{ {
item = GetFileSystemInfo2(path) as FileInfo; item = GetFileSystemInfo2(path) as FileInfo;
@ -80,11 +80,13 @@ namespace NTFSSecurity
catch (Exception ex2) catch (Exception ex2)
{ {
WriteError(new ErrorRecord(ex2, "GetHashError", ErrorCategory.WriteError, path)); WriteError(new ErrorRecord(ex2, "GetHashError", ErrorCategory.WriteError, path));
continue;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
WriteError(new ErrorRecord(ex, "GetHashError", ErrorCategory.WriteError, path)); WriteError(new ErrorRecord(ex, "GetHashError", ErrorCategory.WriteError, path));
continue;
} }
var result = new PSObject(item); var result = new PSObject(item);

4
NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml

@ -4150,10 +4150,10 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:alertSet> <maml:alertSet>
<maml:alert> <maml:alert>
<maml:para>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.</maml:para> <maml:para>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.</maml:para>
<maml:para>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.</maml:para> <maml:para>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.</maml:para>
<maml:para>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.</maml:para> <maml:para>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.</maml:para>
<maml:para>`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.</maml:para> <maml:para>`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.</maml:para>
<maml:para>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.</maml:para> <maml:para>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.</maml:para>
</maml:alert> </maml:alert>
</maml:alertSet> </maml:alertSet>
<command:examples> <command:examples>

20
Tests/FileHash.Tests.ps1

@ -42,4 +42,24 @@ Describe 'Get-FileHash2' {
$results[1].Hash | Should -BeExactly (Get-FileHash -LiteralPath $second -Algorithm SHA256).Hash $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')
}
}
} }

Loading…
Cancel
Save