2

I am using Boe Prox's script to print a list of all the files and folders in a directory. I need Prox's script (as opposed to other windows print directory commands) because it uses robocopy to print filepaths longer than 260 characters.

My problem is, I also want the filehash to be printed alongside the file name. I have modified the script to obtain hashes (please see below) and it generally works except when the filepath is longer than 260 characters. Long filepaths get a blank in the hash column of the final output.

Research I have done:

Attempts to fix the problem:

  • I have also tried to modify the syntax of the filehash to make it more in line with the rest of the script ie. Hash = $matches.Hash (this returns all blanks in place of the filehashs)
  • I tried taking off the part of the regex that seems to specify an item rather than the content of the item ie:If ($_.Trim() -match "^(?<Children>\d+)\s+") { (this leads to the error code WARNING: Cannot bind argument to parameter 'Path' because it is null.)

I'm pretty hopeful that this can happen though: comments in Boe's original script includes the line: "Version 1.1 -Added ability to calculate file hashes"

Here's is my (partially working script):

Function Get-FolderItem {

    [cmdletbinding(DefaultParameterSetName='Filter')]
    Param (
        [parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [Alias('FullName')]
        [string[]]$Path = $PWD,
        [parameter(ParameterSetName='Filter')]
        [string[]]$Filter = '*.*',    
        [parameter(ParameterSetName='Exclude')]
        [string[]]$ExcludeFile,              
        [parameter()]
        [int]$MaxAge,
        [parameter()]
        [int]$MinAge
    )
    Begin {
        $params = New-Object System.Collections.Arraylist
        $params.AddRange(@("/L","/E","/NJH","/BYTES","/FP","/NC","/XJ","/R:0","/W:0","T:W"))
        If ($PSBoundParameters['MaxAge']) {
            $params.Add("/MaxAge:$MaxAge") | Out-Null
        }
        If ($PSBoundParameters['MinAge']) {
            $params.Add("/MinAge:$MinAge") | Out-Null
        }
    }
    Process {
        ForEach ($item in $Path) {
            Try {
                $item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
                If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop)) {
                    Write-Warning ("{0} is not a directory and will be skipped" -f $item)
                    Return
                }
                If ($PSBoundParameters['ExcludeFile']) {
                    $Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile  -join ',')"
                } Else {
                    $Script = "robocopy `"$item`" NULL $Filter $params"
                }
                Write-Verbose ("Scanning {0}" -f $item)
                Invoke-Expression $Script | ForEach {
                    Try {
                        If ($_.Trim() -match "^(?<Children>\d+)\s+(?<FullName>.*)") {
                           $object = New-Object PSObject -Property @{
                                FullName = $matches.FullName
                                Extension = $matches.fullname -replace '.*\.(.*)','$1'
                                FullPathLength = [int] $matches.FullName.Length
                                Length = [int64]$matches.Size
                                FileHash = (Get-FileHash -Path $matches.FullName).Hash
                                Created = (Get-Item $matches.FullName).creationtime
                                LastWriteTime = (Get-Item $matches.FullName).LastWriteTime
                            } 
                            $object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
                            Write-Output $object
                        } Else {
                            Write-Verbose ("Not matched: {0}" -f $_)
                        }
                    } Catch {
                        Write-Warning ("{0}" -f $_.Exception.Message)
                        Return
                    }
                }
            } Catch {
                Write-Warning ("{0}" -f $_.Exception.Message)
                Return
            }
        }
    }
}


Get-FolderItem "C:\TestingFileFolders" 

oymonk
  • 427
  • 9
  • 27
  • 3
    Have you [enabled long paths](https://superuser.com/q/1119883) on your system? – zett42 Dec 18 '20 at 22:25
  • Try `Get-FileHash -Path "\\?\$($matches.FullName)"` – Mathias R. Jessen Dec 18 '20 at 22:30
  • Script works for me (Win10, long paths enabled) with a 795 character long file path. There is just another error that it tries to calculate hash for directories too. – zett42 Dec 18 '20 at 22:32
  • @zett42 I had no idea that there was such a capacity, and I will be bringing it up at my next meeting with the IT department. Thank you! – oymonk Dec 18 '20 at 22:36
  • @MathiasR.Jessen : your modification worked! Thank you very much. I think I need to strip some of the extra data that comes with the hash, but otherwise, it solves my problem perfectly. If you post, I'll upvote. – oymonk Dec 18 '20 at 22:38
  • 3
    BTW, with long paths enabled, `Get-FileHash` works with paths >260 characters both in PS5 and PS7.1. Strangely enough, in PS5 your script doesn't report an error for directories, as it does in PS7.1. – zett42 Dec 18 '20 at 22:42

1 Answers1

3

In Windows PowerShell, you can prepend \\?\ to the full path and read the file:

Get-FileHash -Path "\\?\$($matches.FullName)"
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • 4
    Indeed. Note that _in PowerShell [Core] v6+_ this prefix is not only never _needed_, but actually _causes problems_: see [GitHub issue #10805](https://github.com/PowerShell/PowerShell/issues/10805) – mklement0 Dec 18 '20 at 23:19
  • I'm not sure if this should be a new question, but how do I get the output to include only the 64-digit hash? The regex that works for me is `('Hash=(\w{64})')` but I can't figure out how to add it to `Get-FileHash -Path "\\?\$($matches.FullName)"` – oymonk Dec 22 '20 at 01:52
  • 1
    `Get-FileHash -Path "\\?\$($matches.FullName)" |Select -Expand Hash` – Mathias R. Jessen Dec 22 '20 at 02:38
  • I really appreciate it - it probably would have taken me a long time to figure that out. – oymonk Dec 22 '20 at 17:15