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 1 week 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))
- 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

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.
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

8
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);

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

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