-1

What am I missing here? This command works as expected:

Get-ChildItem -Path .\subdir\ -Recurse -Filter *.* | Resolve-Path -Relative

and so does this:

Get-ChildItem -Path .\subdir\ -Recurse -Filter *.* | Resolve-Path -Relative | Sort

But the following one fails:

Get-ChildItem -Path .\subdir\ -Recurse -Filter *.* | Resolve-Path -Relative | Get-FileHash

Edit: What I am trying to achieve is to get relative path of files in the final output.

Thanks in advance!

==Edit#2==

I came up with this following google:

$files = Get-ChildItem -Path .\ -Recurse -File | Where-Object { $_.Name -match '^*.exe|^*.dll' } 

$output = foreach ($f in $files)
{
  "" | Select-Object @{
         Name='FileName';
         Expression={(Resolve-Path -Relative $f.FullName)}
       },

       @{
          Name='SHA1';
          Expression={(Get-FileHash $f.FullName -Algorithm SHA1).Hash}
       }
} 

echo $output
ssh
  • 943
  • 1
  • 14
  • 23
  • 1
    Regarding your edit: `"" | Select-Object @{ ... }` is a strange and cumbersome way to create an object. Much clearer way: `[PSCustomObject]@{ FileName = Resolve-Path -Relative $f.FullName; SHA1 = (Get-FileHash $f.FullName -Algorithm SHA1).Hash }` – zett42 Nov 11 '21 at 13:44

3 Answers3

2

According to the docs, in powershell 5.1, only literalpath or its alias pspath can be piped to get-filehash. And apparently also only by property name, not by value, which doesn't seem to appear in the docs anymore. Your code would work ok in powershell 7.

[pscustomobject]@{literalpath='there'} | get-filehash

Algorithm Hash                                                Path
--------- ----                                                ----
SHA256    73253E1BB32AE0BE82342FA1CEA0A653CC84097CD65D6DE9... C:\users\js\foo\there

Or since the pspath property can be piped:

get-childitem there | get-filehash
js2010
  • 23,033
  • 6
  • 64
  • 66
1

Note: This answer addresses your question as originally asked. For the solution to your actual question, see this answer.


  • Don't use -Filter *.* if your true intent is to match files only - files can lack an extension (e.g. file) and, conversely, directories may have one (e.g. dir.foo)

  • Don't use Resolve-Path -RelativePath if your intent is to pipe file-information objects to Get-FileHash.

    • Instead, pipe the System.IO.FileInfo instances output by Get-ChildItem directly to Get-FileHash.

Therefore, use the following:

Get-ChildItem -Path .\subdir\ -Recurse -File | Get-FileHash

Your intermediate Resolve-Path -Relative call does work in PowerShell (Core) 7+ - but not in Windows PowerShell - but either way there is no benefit to using it, because the .Path property of the objects output by Get-FileHash always contain the full path, irrespective of whether the input path was relative or not.
Again, see this answer for how to get relative paths in the desired output objects.


To put it differently:

  • Piping a - relative or absolute - path as a string to Get-FileHash should work, but - due to a limitation in Windows PowerShell - doesn't (fixed in PowerShell (Core) 7+).

  • In general, the following should apply to all file-processing cmdlets, which have both a -Path and a -LiteralPath parameter (the former potentially accepting wildcard expressions, the latter accepting literal (verbatim) paths only):

    • They accept string input via the pipeline, which binds to the -Path parameter (via that parameter's ValueFromPipeline property)

      • Notably, this means that path strings are interpreted as wildcard expressions, not as literal paths.
    • They accept System.IO.FileInfo / System.IO.DirectoryInfo input via the pipeline - as output by Get-Item and Get-ChildItem, for instance - which binds to the -LiteralPath parameter (via that parameter's ValueFromPipelineByPropertyName property, which binds to the input objects' .PSPath property, by virtue of -PSPath being a declared alias for -LiteralPath)

As an aside: Perhaps surprisingly, the pipeline-binding rules are not also applied to passing a
-LiteralPath value as a direct argument rather than via the pipeline - see GitHub issue #6057


To inspect the pipeline-relevant properties of the -Path and -LiteralPath parameters of a given cmdlet:

If you don't also need to see the alias names of these parameters,[1] only the pipeline-binding behavior with respect to their primary names, the solution is simple:

& { Get-Help $args[0] -Parameter *Path } Get-FileHash

If you do want to see the parameter alias names, it gets a bit more complicated, unfortunately (note how Get-FileHash is passed as an argument to the script block ({ ... } being invoked; you may pass any cmdlet name of interest):

& {
  (Get-Command $args[0]).ParameterSets.Parameters | Where-Object { $_.Name -like '*Path' } |
    Select-Object Name, Aliases, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterType
} Get-FileHash | Format-Table

Windows PowerShell output:

Name        Aliases  ValueFromPipeline ValueFromPipelineByPropertyName ParameterType  
----        -------  ----------------- ------------------------------- -------------  
Path        {}                   False                           False System.String[]
LiteralPath {PSPath}             False                            True System.String[]

PowerShell (Core) 7+ output:

Name        Aliases      ValueFromPipeline ValueFromPipelineByPropertyName ParameterType
----        -------      ----------------- ------------------------------- -------------
Path        {}                        True                            True System.String[]
LiteralPath {PSPath, LP}             False                            True System.String[]

The above shows that Get-FileHash:

  • In Windows PowerShell:

    • accepts no direct string input via the pipeline (no ValueFromPipeline property value set to $true).
    • only binds objects that have a .PSPath property to -LiteralPath (i.e., objects output by Get-Item and Get-ChildItem), due to ValueFromPipelineByPropertyName being $true, by that property's value.
  • In PowerShell Core 7+:

    • does accept direct string input, via the -Path parameter, and also via input objects that have a .Path property.
    • as in Windows PowerShell, binds objects that have a .PSPath property to -LiteralPath by that property's value, and, additionally, analogously objects with a .LP property, which is implied by the fact that in PowerShell (Core) -LP is now an official alias of -LiteralPath.

[1] It is unfortunate that Get-Help -Parameter output doesn't list aliases - see GitHub issue #13418. A related problem is that you cannot request parameter information via an alias - see GitHub issue #6183.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

tl;dr:

  • This question is an example of the XY problem - asking for help with the perceived solution to a problem instead of (also) stating the problem itself.

  • The true intent, stated as such only after answers were given, was to create file-hash objects containing relative paths.

  • See the bottom section for efficient solutions.


Your question turned out to be a classic XY problem:

  • Your real, originally unstated goal was to create objects that represent files by their cryptographic hash string, obtained with Get-FileHash, and relative paths; relative to the starting directory of the recursion via Get-ChildItem -Recurse, that is.

  • To that end, you tried to pipe the output from Resolve-Path -Relative to Get-FileHash under the - mistaken - assumption that doing so would result in output objects whose .Path property reflects the relative input path.

    • In reality, that property always contains a full path.

When your command failed altogether - in Windows PowerShell - you made your question about that failure, instead of formulating your actual goal.

As a result, you received answers matching the question as asked:

  • js2010's answer explains the technical reason for the failure in Windows PowerShell and notes that it technically works in PowerShell (Core) 7+

  • My original answer dives deeper into the technical problems, while also pointing out that the problem you tried to solve - getting Resolve-Path -RelativePath input to work with Get-FileHash - wasn't worth solving:

    • In Windows PowerShell, simply omitting Resolve-Path -RelativePath would have made your command work (albeit, as stated, invariably with full paths)
    • In PowerShell (Core) 7+, the Resolve-Path -RelativePath call amounts to an unnecessary no-op.

You later changed the premise of your question to reveal your true intent, and even edited an answer into it - both actions are ill-advised:

  • By changing the premise of your question later, you're invalidating previously given answers: these answers don't match the question any longer and are likely to cause confusion for future readers.

  • Solutions belong into answer posts, for a clear separation between a question and its answer. As an answer, it can also be accepted, which is an important signal to future readers as to what solution actually worked.


A solution that improves on your own attempt and works in both PowerShell editions:

# Get the full path of the start dir, using the current dir. as an example.
$startDir = Convert-Path -LiteralPath '.'

# Determine the length of the start dir's path prefix, which,
# when removed from the full paths of the files found during the enumeration,
# yields the relative paths.
$startDirPrefixLen = $startDir.Length + (1, 0)[$startDir[-1] -in '\', '/']

# Walk the start dir's subtree recursively and, for each file of interest,
# output an object with the relative path and the file's hash.
Get-ChildItem -LiteralPath $startDir -File -Recurse -Include *.exe, *.dll |
  ForEach-Object {
    [PSCustomObject] @{
      FileName = $_.FullName.Substring($startDirPrefixLen)
      SHA1 = ($_ | Get-FileHash).Hash
    }
  }

A more convenient PowerShell (Core) 7+ solution, via the new [System.IO.Path]::GetRelativePath() .NET API method:

# Get the full path of the start dir, using the current dir. as an example.
# Note: While [IO.Path]::GetRelativePath() itself supports relative input
#       paths, they wouldn't be resolved correctly, because .NET's
#       working directory typically differs from PowerShell's`.
$startDir = Convert-Path -LiteralPath '.'

# Walk the start dir's subtree recursively and, for each file of interest,
# output an object with the relative path and the file's hash.
Get-ChildItem -LiteralPath $startDir -File -Recurse -Include *.exe, *.dll |
  ForEach-Object {
    [PSCustomObject] @{
      FileName = [IO.Path]::GetRelativePath($startDir, $_.FullName)
      SHA1 = ($_ | Get-FileHash).Hash
    }
  }
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Sorry for not being very clear to begin with. I am fairly new to PowerShell. Intention was to learn enough to come up with my own solution, rather than asking for a free solution. – ssh Nov 17 '21 at 00:44
  • That is commendable, @ssh. The answers here now provide _both_: an explanation of where your original approach fell short _and_ an improved version of your later, amended solution attempt, after the XY problem became apparent - which you should never have edited into your _question_. To guide future readers, however, you should accept the answer that provides the best solution to your (later revealed as such) problem. – mklement0 Nov 17 '21 at 00:58