Reproducible on 6.0.2
When a file in a directory is passed to ForEach-Object, it loses its subpath
Setup:
New-Item -ItemType Directory -Name "Subdir"
"File content" > "Subdir/myFile.txt"
Now for the bug:
dir . -Recurse -File | % {Get-Content $_ }
Should see the content of myFile.txt:
File content
The results should be identical to dir . -Recurse -File | Get-Content
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.
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
@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.
.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.
@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.