Get-ChildItem : The specified path, file name, or both are too long. The fully q

This topic contains 16 replies, has 5 voices, and was last updated by  David Schmidtberger 3 weeks ago.

  • Author
    Posts
  • #100074

    Norm Long
    Participant

    Good afternoon all!

    Running the following one liner:
    $Results = Get-ChildItem -Path 'I:\*.*' -Recurse -Force | Where-Object {$_.length/1GB -gt 15} | Select-Object Name,Extension,Fullname,CreationTime,LastAccessTime,@{n=”Size MB”;e={$_.length/1GB}}

    Getting lots of the following error messages:
    Get-ChildItem : The specified path, file name, or both are too long. The fully qualified file name must be less than 260

    Is there any way to get around these errors? I've been researching at the university of google, tried -LiteralPath '\\?\I:\' nothing that I've found has worked.

    Any and all input would be greatly appreciated, this is over my head.

    Norm

  • #100078

    Sam Boutros
    Participant

    PowerShell leverages .NET which has the 256 path character limit. We can use something that leverages COM such as robocopy.exe

    This will not copy anything, just list the full file names under the given $FolderPath recursively..

    $FolderPath = 'D:\Sandbox'
    
    robocopy $FolderPath NULL /L /E /MT:128 /FP /NP /TEE /NJH /NJS /NC /NDL /R:0 /W:0 /XJ | foreach {
        $PSItem.Substring($PSItem.IndexOf(':') - 1 , $PSItem.Length - $PSItem.IndexOf(':') + 1)
    }
    
  • #100080

    Norm Long
    Participant

    Sam;
    Thank you for your input. Question how I tie together your code with my $Results = Get-ChildItem -Path 'I:\*.*' -Recurse -Force | Where-Object {$_.length/1GB -gt 15} | Select-Object Name,Extension,Fullname,CreationTime,LastAccessTime,@{n=”Size MB”;e={$_.length/1GB}}

    Norm

    • #100084

      Sam Boutros
      Participant

      I'll tell on one condition..
      Explain to me the 4 lines I posted above.

  • #100128

    Norm Long
    Participant

    Sam;
    Not sure I can pass your test I'm happier in the linux world with bash scripts but here goes:
    1. Your using robocopy with applicable options (no knowledge of what each option is doing but I guess I can look them up) to the contents of $FolderPath
    2. In the for each loop it appears you are using a substring method to return values in this case the path of every file contained in $FolderPath. Honestly not sure what your doing with the $PSItem.Length – $PSItem.IndexOf(':') + 1). Powershell complained a bit when I tested the code: Exception calling "Substring" with "2" argument(s): "StartIndex cannot be less than zero.
    Parameter name: startIndex"
    3. Ran the following in order to drill down into the object typename in order to attempt to figure out what you were doing
    $FolderPath | Get-Member
    $FolderPath.Substring | Get-Member

    Do I pass?

    Norm

    • #100135

      Sam Boutros
      Participant

      Yes, the whole point is to further your understanding of PowerShell.

      The use of the line

      robocopy $FolderPath NULL /L /E /MT:128 /FP /NP /TEE /NJH /NJS /NC /NDL /R:0 /W:0 /XJ /BYTES 
      

      outputs the file size in bytes, and the full file name

      This output is then piped to one or more lines to parse that text output of robocopy.exe to extract the Name,Extension,Fullname,CreationTime,LastAccessTime,FileSize properties you want.

      What's missing in the robocopy.exe output is the creationtime and lastaccesstime perperties.

      I admit that the expression

      $PSItem.Substring($PSItem.IndexOf(':') - 1 , $PSItem.Length - $PSItem.IndexOf(':') + 1)
      

      is complex and not optimal for learning. But the idea is simple. Robocopy returns a line of text for each file that looks like:

                  12345     c:\bla1\bla 2\bla.3\bla4.txt
      

      You need to parse out file name, extension, and full name. You have to account for possible multiple spaces and multiple dots (but a single colon)
      if

      $a = '            12345     c:\bla1\bla 2\bla.3\bla4.txt'
      

      then

      $a.Split(':')
      

      will return

                  12345     c
      \bla1\bla 2\bla.3\bla4.txt
      

      The colon is an attractive marker for parsing here because you can be assured that there can be only one colon in a file name
      The .Split() method will provide an array of 2 elements.
      The second one is the file full name less the colon and the drive letter which is the last letter of the first split string.
      So,

      $a.Split(':')[1]
      

      returns the 2nd element of that .Split() array of text (array elements are counted starting from 0)

      \bla1\bla 2\bla.3\bla4.txt
      

      The last letter of the first .Split() array element can be referenced as

      $a.Split(':')[0][-1]
      

      This may be a little complicated, but simply put

      $a.Split(':')[0]
      

      returns the first string of characters being the first .Split() array element

                  12345     c
      

      which is in itself an array of characters. The last array element can be refereed to by [-1]
      So, to stitch this expression together, the file full name can be parsed as:

      "$($a.Split(':')[0][-1]):$($a.Split(':')[1])"
      

      What this expression does is to concatenate the last character before the colon + the colon + the rest of the characters after the colon
      Once we have file full name as in

      $FileFullName = "$($a.Split(':')[0][-1]):$($a.Split(':')[1])"
      

      We can get the file name by using:

      $FileName = Split-Path $FileFullName -Leaf
      

      Using similar parsing techniques, we can demo parsing a line like:

      $a = '            12345     c:\bla1\bla 2\bla.3\bla4.txt'
      
      $FileFullName = "$($a.Split(':')[0][-1]):$($a.Split(':')[1])"
      
      $FileName = Split-Path $FileFullName -Leaf
      
      $Extension = $FileName.Substring($FileName.LastIndexOf('.') , $FileName.Length - $FileName.LastIndexOf('.'))
      
      [Int]$FileSize = ($a.Split(':')[0][0..($a.Split(':')[0].Length -2)] -join '').Trim()
      
      "If the string is '$a'"
      "FileFullName is  '$FileFullName'"
      "FileName is      '$FileName'"
      "Extension is     '$Extension'"
      "Size (bytes) is  '$FileSize' bytes"
      

      In the original expression $PSItem or $_ refers to the text line being passed to the pipeline from robocopy.exe

      Putting the parsed output in a more manageable PS object:

      $FolderPath = 'D:\Sandbox'
      
      $Results = robocopy $FolderPath NULL /L /E /MT:128 /FP /NP /TEE /NJH /NJS /NC /NDL /R:0 /W:0 /XJ /BYTES | foreach {
          $FileFullName = "$($PSItem.Split(':')[0][-1]):$($PSItem.Split(':')[1])"
          $FileName = Split-Path $FileFullName -Leaf
          [Int]$FileSize = ($PSItem.Split(':')[0][0..($PSItem.Split(':')[0].Length -2)] -join '').Trim()
          [PSCustomObject][Ordered]@{
              Name       = $FileName
              Extension  = $(
                  if ($FileName -match '\.') { # this if statement accounts for files with no extension
                      $FileName.Substring($FileName.LastIndexOf('.') , $FileName.Length - $FileName.LastIndexOf('.'))
                  } else {
                      'None' 
                  }
              )
              FullName   = $FileFullName
              'Size(GB)' = [Math]::Round($FileSize / 1GB , 2)
          }
      }
      
      $Results | FL
      

      What's missing here is the CreationTime and LastAccessTime, can you lookup robocopy documentation to see if there are switches to show those?

  • #100131

    Joel Sallow
    Participant

    THere is an alternative. If you grab the NTFSSecurity module from the PSGallery:

    Install-Module NTFSSecurity -Scope CurrentUser

    Then you can use the Get-ChildItem2 cmdlet, which leverages a third-party .dll to access the filesystem and correctly handle longer paths. The syntax is identical to Get-ChildItem, and it supports all the same parameters, it just works with a different library to manage the long paths:

    Get-ChildItem2 -Path $TargetPath_Long -Recurse -Force 
  • #100132

    Norm Long
    Participant

    Hi Joel, apparently the third-party dll does note like a PSDrive mount volume: New-PSDrive -Name "I" -PSProvider "FileSystem" -Root "\\jesse\users"

    When I run the following:
    $Results = Get-ChildItem2 -Path I:\ -Recurse | Where-Object {$_.length/1GB -gt 15} | Select-Object Name,Extension,Fullname,CreationTime,LastAccessTime,@{n=”Size GB”;e={$_.length/1GB}}

    The following error messages occur
    Get-ChildItem2 : Unable to find the specified file.
    At line:1 char:12
    + $Results = Get-ChildItem2 -Path I:\ -Recurse | Where-Object {$_.lengt ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (I:\:String) [Get-ChildItem2], FileNotFoundException
    + FullyQualifiedErrorId : FileNotFound,NTFSSecurity.GetChildItem2

    Don't get these messages with Get-ChildItem ran against the created psdrive.

    Any thoughts

    Thanks for input
    Norm

    • #100140

      Joel Sallow
      Participant

      Strange. You may be able to bypass the issue by using the -Persist parameter of New-PSDrive, as that causes it to make a new network drive much like windows explorer does, although you'd have to manually remove it when you're done.

      It's very possible that the third party DLL wasn't designed for lengthy network paths, I'm not sure. I know there's at least one other module that has a cmdlet for this purpose of a similar design, I think the cmdlet was called Get-LongChildItem if that helps you find the module at all. Perhaps that one works a bit differently.

  • #100143

    Sam Boutros
    Participant

    Here's a version with CreationTime and LastAccessTime and the file size filter

    $FolderPath     = 'D:\Sandbox'
    $FilterFileSize = 15GB # return files larger than this value
    
    $Results = robocopy $FolderPath NULL /L /E /MT:128 /FP /NP /TEE /NJH /NJS /NC /NDL /R:0 /W:0 /XJ /BYTES | foreach {
        if ($PSItem -match ':') {
            $FileFullName = "$($PSItem.Split(':')[0][-1]):$($PSItem.Split(':')[1])"
            $FileName = Split-Path $FileFullName -Leaf
            if ($FileName -match '\.') { # this if statement accounts for files with no extension
                $Extension = $FileName.Substring($FileName.LastIndexOf('.') , $FileName.Length - $FileName.LastIndexOf('.'))
            } else {
                $Extension = 'None' 
            }
            [Int]$FileSize = ($PSItem.Split(':')[0][0..($PSItem.Split(':')[0].Length -2)] -join '').Trim()
            if ($FileSize -ge $FilterFileSize) {
                [PSCustomObject][Ordered]@{
                    FullName       = $FileFullName
                    Name           = $FileName
                    Extension      = $Extension
                    'Size(GB)'     = [Math]::Round($FileSize / 1GB , 2)
                    CreationTime   = [IO.File]::GetCreationTime($FileFullName)
                    LastAccessTime = [IO.File]::GetLastAccessTime($FileFullName)
                }
            }
        }
    }
    
    $Results | FL
    
  • #100144

    Norm Long
    Participant

    Hello Sam;
    Thank you for the explanation and pushing me to do some digging. In running your final script came across the following error message:
    Cannot convert value "4268605440" to type "System.Int32". Error: "Value was either too large or too small for an Int32."
    At line:13 char:9
    + [Int32]$FileSize = ($PSItem.Split(':')[0][0..($PSItem.Split(' ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

    Did some digging and poking around and looking and looking at the error message came to the realization of the need to change the Int definition to Int64. Still chewing on your script explanation.

    Norm

  • #100146

    Fredrik Kacsmarck
    Participant

    Haven't checked but depending on your .Net version and OS version you may only need to change a setting in the registry.
    Just do a google/bing/duckduckgo on "windows maxpath" and you should find articles about the registry key and .net version.

    You could also try third party .dll's.
    I created a blog post about using quickio.net some time ago.
    QuickIO.Net and Powershell

    QuickIO don't seem to be developed further so YMMV.
    But in my opinion it's a "nicer" approach to get the results as objects directly rather than going through robocopy and then regex/split the output.

  • #100152

    David Schmidtberger
    Participant

    I use alphafs to get around this limitation.

    https://github.com/alphaleonis/AlphaFS/wiki/PowerShell

  • #100153

    Joel Sallow
    Participant

    Get-ChildItem2 uses AlphaFS, I'm pretty sure, and it doesn't seem to handle UNC long paths. 🙁

  • #100173

    Norm Long
    Participant

    Joel,
    Wanted to let you know the PSDrive -Persist Parameter allowed get-childitem2 to work with no directory length issues!

    Many thanks for your input

    Norm

  • #100197

    Joel Sallow
    Participant

    Interesting. I might have to go submit an issue on the github for NTFSSecurity if I can find it — seems like it's just an issue it not checking the PSDrives directly.

  • #100203

    David Schmidtberger
    Participant

    hmm thats interesting Joel, i use alpha directly on an almost daily basis to delete users home drives, and enumerating/deleting always works on the long file-paths.

You must be logged in to reply to this topic.