Powershell: ForEach-Object breaks paths in FileInfo

Created on 15 Jun 2018  路  4Comments  路  Source: PowerShell/PowerShell

Reproducible on 6.0.2

When a file in a directory is passed to ForEach-Object, it loses its subpath

Steps to reproduce

Setup:

New-Item -ItemType Directory -Name "Subdir"
"File content" > "Subdir/myFile.txt"

Now for the bug:

dir . -Recurse -File | % {Get-Content $_ }

Expected behavior

Should see the content of myFile.txt:

File content

The results should be identical to dir . -Recurse -File | Get-Content

Actual behavior

Error:

Get-Content : Cannot find path '/var/tmp/pstest/File.txt' because it does not exist.
At line:1 char:26
+ dir -Recurse -File  | % {Get-Content $_ }
+                          ~~~~~~~~~~~~~~
+ CategoryInfo          : ObjectNotFound: (/var/tmp/pstest/File.txt:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

The Subdir subdirectory seems to be ommitted from the path of the file that Get-Content tries to find. This does not happen when not using ForEach-Object.

Environment data

Name                           Value                                                                                                  
----                           -----                                                                                                  
PSVersion                      6.0.2                                                                                                  
PSEdition                      Core                                                                                                   
GitCommitId                    v6.0.2                                                                                                 
OS                             Darwin 17.6.0 Darwin Kernel Version 17.6.0: Tue May  8 15:22:16 PDT 2018; root:xnu-4570.61.1~1/RELEA...
Platform                       Unix                                                                                                   
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                                
PSRemotingProtocolVersion      2.3                                                                                                    
SerializationVersion           1.1.0.1                                                                                                
WSManStackVersion              3.0                                                                                                    


Area-Cmdlets Issue-Question Resolution-Answered

All 4 comments

@yevster Get-Content -Path takes a string argument so the FileInfo object in $_ gets converted to a string when it's passed to the cmdlet. Now the .ToString() of a FileInfo object is just the filename, not the full path so it won't work for files not in the current directory. If you use the FullName property on the object as in

 dir . -Recurse -File | % {Get-Content $_.FullName }

it will work as expected and you'll see the content of the file.

@yevster:

  • As @BrucePay states, -Path (and -LiteralPath) are bound as _strings_ when values are passed as _arguments_, and whether a System.IO.DirectoryInfo or System.IO.FileInfo instance stringifies to a mere name (as in your case) or a full path _varies_ - it depends on the specifics of the Get-ChildItem / Get-Item command.

    • By contrast, when such instances are passed _via the pipeline_, their .PSPath property is bound to -LiteralPath, which works reliably.
  • This problematic behavior is discussed in #6057, along with a potential solution.

@chuanjiao10: _Piping_ System.IO.DirectoryInfo and System.IO.FileInfo instances already works as expected (at least if emitted by PS provider cmdlets): their .PSPath property binds to -LiteralPath, and .PSPath is like .FullName, but with a provider-name prefix.

The problem is with passing such instances as _arguments_, where stringification is applied, and whether that stringification returns the full path varies by command and can therefore not be relied upon.

Using $_.FullName is an effective _workaround_, but it shouldn't be necessary and the need for it regularly trips users up.

6057 suggests applying the pipeline's by-property-value binding logic to direct argument passing as well, which would solve the problem.

@BrucePay it's a little bit more insidious than that: ToString() returns whatever was passed to the FileInfo ctor; sometimes a name, sometimes a full path.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aragula12 picture aragula12  路  3Comments

garegin16 picture garegin16  路  3Comments

alx9r picture alx9r  路  3Comments

SteveL-MSFT picture SteveL-MSFT  路  3Comments

rudolfvesely picture rudolfvesely  路  3Comments