Browse Source

fix: skip folders in Get-FileHash2 and hash the remaining paths

Defect 3. Get-FileHash2 left ProcessRecord at the first folder in -Path,
so the files that followed the folder in the same array were not hashed.
It now skips the folder, like Get-FileHash, and continues.

Tests/FileHash.Tests.ps1 (new): 1 test. It skips in PowerShell 7, where
every Get-FileHash2 call fails until the RIPEMD160 reference goes
(decision D5, later in this run).

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

2
CHANGELOG.md

@ -57,5 +57,7 @@ The format is based on
omitted; an omitted parameter now leaves its section unchanged omitted; an omitted parameter now leaves its section unchanged
- Fix `Get-ChildItem2`, which stopped with an `InvalidCastException` when - Fix `Get-ChildItem2`, which stopped with an `InvalidCastException` when
`-Path` pointed to a file; it now returns the file, like `Get-ChildItem` `-Path` pointed to a file; it now returns the file, like `Get-ChildItem`
- Fix `Get-FileHash2`, which stopped at a folder in `-Path` and didn't hash
the files that followed it; folders are now skipped
[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

@ -23,7 +23,7 @@ The `Get-FileHash2` cmdlet calculates the hash value of each file that `-Path` p
`-Algorithm` selects the hash algorithm and accepts `SHA1`, `SHA256`, `SHA384`, `SHA512`, `MACTripleDES`, `MD5`, and `RIPEMD160`. The default is `SHA256`. `-Algorithm` selects the hash algorithm and accepts `SHA1`, `SHA256`, `SHA384`, `SHA512`, `MACTripleDES`, `MD5`, and `RIPEMD160`. The default is `SHA256`.
The cmdlet hashes files only. A path that points to a folder is skipped, and because the cmdlet stops processing the current input when it meets one, a folder in the middle of a `-Path` array suppresses the results of the paths that follow it in the same array. Pass only file paths, or filter folders out before you pipe items into the cmdlet. A path that does not exist produces a non-terminating `ReadFileError`. The cmdlet hashes files only and skips paths that point to folders. A path that does not exist produces a non-terminating `ReadFileError`.
`-Path` accepts pipeline input by value and by property name through its `FullName` alias, so you can pipe the output of `Get-ChildItem2`, `Get-Item2`, or `Get-ChildItem` into the cmdlet; folders that arrive through the pipeline are skipped individually. Because the cmdlet reads files through the AlphaFS library, it also hashes files whose path exceeds the 260-character `MAX_PATH` limit. Relative paths are resolved against the current location. `-Path` accepts pipeline input by value and by property name through its `FullName` alias, so you can pipe the output of `Get-ChildItem2`, `Get-Item2`, or `Get-ChildItem` into the cmdlet; folders that arrive through the pipeline are skipped individually. Because the cmdlet reads files through the AlphaFS library, it also hashes files whose path exceeds the 260-character `MAX_PATH` limit. Relative paths are resolved against the current location.
@ -125,6 +125,8 @@ The hash is returned as an uppercase hexadecimal string without separators, whic
`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.
## RELATED LINKS ## RELATED LINKS
[Get-ChildItem2](Get-ChildItem2.md) [Get-ChildItem2](Get-ChildItem2.md)

6
NTFSSecurity/MiscCmdlets/GetFileHash2.cs

@ -48,7 +48,11 @@ namespace NTFSSecurity
{ {
item = GetFileSystemInfo2(path) as FileInfo; item = GetFileSystemInfo2(path) as FileInfo;
if (item == null) if (item == null)
return; {
// Like Get-FileHash, skip folders and continue with the next path.
WriteVerbose(string.Format("Skipping '{0}', which is a folder", path));
continue;
}
} }
catch (Exception ex) catch (Exception ex)
{ {

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

@ -4042,7 +4042,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<maml:description> <maml:description>
<maml:para>The `Get-FileHash2` cmdlet calculates the hash value of each file that `-Path` points to and returns the file object with the result attached. The returned object is the file object of the file, extended with a `Hash` property that holds the hash as an uppercase hexadecimal string and an `Algorithm` property that names the algorithm that was used. The module's formatting data displays those objects as a table with the `Algorithm`, `Hash`, and `FullName` columns.</maml:para> <maml:para>The `Get-FileHash2` cmdlet calculates the hash value of each file that `-Path` points to and returns the file object with the result attached. The returned object is the file object of the file, extended with a `Hash` property that holds the hash as an uppercase hexadecimal string and an `Algorithm` property that names the algorithm that was used. The module's formatting data displays those objects as a table with the `Algorithm`, `Hash`, and `FullName` columns.</maml:para>
<maml:para>`-Algorithm` selects the hash algorithm and accepts `SHA1`, `SHA256`, `SHA384`, `SHA512`, `MACTripleDES`, `MD5`, and `RIPEMD160`. The default is `SHA256`.</maml:para> <maml:para>`-Algorithm` selects the hash algorithm and accepts `SHA1`, `SHA256`, `SHA384`, `SHA512`, `MACTripleDES`, `MD5`, and `RIPEMD160`. The default is `SHA256`.</maml:para>
<maml:para>The cmdlet hashes files only. A path that points to a folder is skipped, and because the cmdlet stops processing the current input when it meets one, a folder in the middle of a `-Path` array suppresses the results of the paths that follow it in the same array. Pass only file paths, or filter folders out before you pipe items into the cmdlet. A path that does not exist produces a non-terminating `ReadFileError`.</maml:para> <maml:para>The cmdlet hashes files only and skips paths that point to folders. A path that does not exist produces a non-terminating `ReadFileError`.</maml:para>
<maml:para>`-Path` accepts pipeline input by value and by property name through its `FullName` alias, so you can pipe the output of `Get-ChildItem2`, `Get-Item2`, or `Get-ChildItem` into the cmdlet; folders that arrive through the pipeline are skipped individually. Because the cmdlet reads files through the AlphaFS library, it also hashes files whose path exceeds the 260-character `MAX_PATH` limit. Relative paths are resolved against the current location.</maml:para> <maml:para>`-Path` accepts pipeline input by value and by property name through its `FullName` alias, so you can pipe the output of `Get-ChildItem2`, `Get-Item2`, or `Get-ChildItem` into the cmdlet; folders that arrive through the pipeline are skipped individually. Because the cmdlet reads files through the AlphaFS library, it also hashes files whose path exceeds the 260-character `MAX_PATH` limit. Relative paths are resolved against the current location.</maml:para>
</maml:description> </maml:description>
<command:syntax> <command:syntax>
@ -4143,6 +4143,7 @@ PS C:\&gt; Disable-Privileges</dev:code>
<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.</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:alert> </maml:alert>
</maml:alertSet> </maml:alertSet>
<command:examples> <command:examples>

45
Tests/FileHash.Tests.ps1

@ -0,0 +1,45 @@
<#
Tests Get-FileHash2 of the module built in NTFSSecurity\bin\Release on files in a sandbox folder.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
)]
param ()
BeforeAll {
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\NTFSSecurity\bin\Release\NTFSSecurity.psd1'
Import-Module -Name $modulePath -Force -ErrorAction Stop
$sandbox = New-TestSandbox -Name 'FileHash'
Push-Location -LiteralPath $sandbox
$folder = Join-Path -Path $sandbox -ChildPath 'Folder'
$first = Join-Path -Path $sandbox -ChildPath 'One.txt'
$second = Join-Path -Path $sandbox -ChildPath 'Two.txt'
Assert-TestSandboxPath -Sandbox $sandbox -Path $folder, $first, $second
New-Item -ItemType Directory -Path $folder | Out-Null
Set-Content -LiteralPath $first -Value 'One'
Set-Content -LiteralPath $second -Value 'Two'
}
AfterAll {
Pop-Location
Remove-TestSandbox -Sandbox $sandbox
Remove-Module -Name NTFSSecurity -Force -ErrorAction SilentlyContinue
}
Describe 'Get-FileHash2' {
Context 'When -Path contains a folder' {
It 'Should skip the folder and hash the files that follow it' {
if ($PSVersionTable.PSEdition -eq 'Core') {
Set-ItResult -Skipped -Because 'Get-FileHash2 fails in PowerShell 7 until it no longer references RIPEMD160'
}
$results = @(Get-FileHash2 -Path $first, $folder, $second -ErrorVariable hashErrors -ErrorAction SilentlyContinue)
$hashErrors | Should -BeNullOrEmpty
$results.Name | Should -Be @('One.txt', 'Two.txt')
$results[1].Hash | Should -BeExactly (Get-FileHash -LiteralPath $second -Algorithm SHA256).Hash
}
}
}
Loading…
Cancel
Save