2

I've often used a construct similar to this (using aliases for brevity):

gci -ad | %{$_ | gci}

which works fine. But when trying to help another user on this forum, I found that the following doesn't work:

gci -ad | %{$_.Parent | gci}

throws the following error for each iteration:

gci : Cannot find path 'C:\Users\keith\Documents\Documents' because it does not exist.
At line:1 char:25
+ gci -ad | %{$_.Parent | gci}
+                         ~~~
    + CategoryInfo          : ObjectNotFound: (C:\Users\keith\Documents\Documents:Stri
   ng) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemC
   ommand

even though:

gci -ad | %{$_.GetType() -eq $_.Parent.GetType()}

produces a scrennfull of True.

I'm pretty sure it has something to do with parameter binding, but would like to understand the apparent inconsistancy....

Keith Miller
  • 702
  • 5
  • 13
  • 1
    Not really inconsistent if you're passing it the parent directory of the listed objects, `C:\Users\keith\Documents`. So, it's coercing the name, since it wasn't successful in coercing the object into `-LiteralPath`, into a string to append to the path you're executing from. `$_` would be the listed items from `gci` whereas `$_.Parent` would just be -*as the name implies*- the parent folder: `Documents`. Hence "`'C:\Users\keith\Documents\Documents' `" – Abraham Zinala Feb 17 '23 at 20:36
  • 1
    change `%{$_.Parent | gci}` to `gci -ad | %{$_.Parent.fullname | gci}` // unfortunate for windows pwsh as per usual. works fine in newer pwsh – Santiago Squarzon Feb 17 '23 at 20:42
  • @SantiagoSquarzon, that would just be relisting `.\Documents` over and over again, unless that's the intentions of the OP; *per folder listed*. Otherwise, `gci -ad | gci` should suffice. – Abraham Zinala Feb 17 '23 at 20:50
  • 1
    @AbrahamZinala pretty much what the code is doing is relisting the contents of the parent folder so what I posted is how it would work in windows powershell that is failing for OP. – Santiago Squarzon Feb 17 '23 at 20:54
  • 1
    @SantiagoSquarzon, ahhh, I see lol I was going based of his working (*first*) example. Looks like you were just correcting what would work for the second example. – Abraham Zinala Feb 17 '23 at 20:57

1 Answers1

2

Easiest way to see what's happening is to emulate the binding with a test function, but the issue is, as you might know in .NET Framework when a DirectoryInfo instance is coerced into a string the result will be it's .Name property value as opposed to .NET where the coercion results in the .FullName property.

Since the object returned by calling the .Parent property does not have the ETS Property .PSPath, the input object will be bound the the Path Parameter and coerced to a string.

Assuming you have both versions of PowerShell, you can try the following to see the difference:

function Test-Binding {
    [CmdletBinding(DefaultParameterSetName='Items')]
    param(
        [Parameter(ParameterSetName='Items', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [string[]]
        ${Path},

        [Parameter(ParameterSetName='LiteralItems', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [Alias('PSPath')]
        [string[]]
        ${LiteralPath}
    )

    process {
        $PSBoundParameters
        $PSBoundParameters.Clear()
    }
}

(Get-Item .), (Get-Item .).Parent | Test-Binding
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • 1
    Nice; just a quibble: `.Parent` indeed _always_ stringifies by name only in WinPS, but for `DirectoryInfo` instances in general it depends on the specifics of the `Get-ChildItem` call (see [this answer](https://stackoverflow.com/a/53400031/45375)). – mklement0 Feb 17 '23 at 21:46
  • 2
    @mklement0 the behavior in .NET Framework is quite enigmatic, not sure what were they thinking. The fact that `[string] [System.IO.DirectoryInfo] $pwd.Path` outputs the FullName and `[string] ([System.IO.DirectoryInfo] $pwd.Path).Parent` outputs the Name is absurd really – Santiago Squarzon Feb 17 '23 at 22:01
  • 1
    Didn't even give me time to market this as "Answer"? I'm insulted! :D The one thing I was missing is that `$_.Parent` lacks the `PSpath` property. Thanks. – Keith Miller Feb 17 '23 at 22:13
  • 1
    @SantiagoSquarzon, agreed. I _think_ it came down to the name / path that was passed to the _constructor_ of the instance, and in the case of `.Parent` the instance is seemingly constructed by name only. – mklement0 Feb 17 '23 at 22:59
  • 1
    @KeithMiller, see [GitHub issue #4347](https://github.com/PowerShell/PowerShell/issues/4347) for background information. – mklement0 Feb 17 '23 at 23:04
  • 1
    I get it now. Ran into similar confusion a few years ago with registry keys returned from `Get-ChildItem` vs. those returned from the `RegistryKey.OpenSubkey()` method... – Keith Miller Feb 17 '23 at 23:17
  • @mklement0 if the instance of `.Parent` was constructed by name only then `.FullName` would resolve to the value of `[IO.Directory]::GetCurrentDirectory()` instead of the correct absolute path, which is not the case. `.FullName` of a `.Parent` instance does resolve properly, so it is not constructed by Name in my opinion. – Santiago Squarzon Feb 17 '23 at 23:29
  • We're talking about _stringification_ (`.ToString()` return value), not _property values_ (`.Name` and `.FullName` _do_ work properly, always, even in WinPS). – mklement0 Feb 17 '23 at 23:35
  • I see what you meam, I'm just confused what you meant by "and in the case of `.Parent` the instance is seemingly constructed by name only." @mklement0 if it was constructed by name then calling `.FullName` on that instance shouldn't resolve properly unless I'm missing something – Santiago Squarzon Feb 17 '23 at 23:40
  • 1
    Yeah, with `.Parent` it is basically an _implementation detail_ as to how that instance is constructed _behind the scenes_, and in WinPS / .NET Framework this implementation details leaks out in the `.ToString()` behavior. To be clear: It was always a bad idea to base `.ToString()` behavior on the instance's construction details, and, fortunately, PS Core / .NET (Core) did away with that. – mklement0 Feb 17 '23 at 23:44
  • 2
    @SantiagoSquarzon: Please forgive my earlier comment about "... marking as Answer...". I answer frequently but ask infrequently. When I saw the gray checknark, I didn't pay attention to the color and assumed your answer had already been marked as Answer. I've marked and upvoted now. – Keith Miller Feb 17 '23 at 23:46
  • 1
    no worries @KeithMiller happy the answer was helpful – Santiago Squarzon Feb 17 '23 at 23:47
  • Just saw your update: the intent and implementation was always to _internally_ resolve to to a full path (even though no existence check is made); e.g., `([io.directoryinfo] 'imaginary').fullname` (but note that resolving is performed against the _process_'s working directory, not PowerShell's). – mklement0 Feb 17 '23 at 23:49
  • @mklement0 yes, it's the same as `[io.path]::Combine([io.directory]::GetCurrentDirectory(), 'imaginary')` which had me confused, not sure how did the make for `.Parent` to resolve properly if it was constructed by name: `[io.directoryinfo] ([io.directoryinfo] $pwd.Path).Parent.Name` then the `.FullName` property of that instance would depend on `[io.directory]::GetCurrentDirectory()` at the moment it was instantiated however that's not the case – Santiago Squarzon Feb 17 '23 at 23:56
  • 1
    It is `[io.directoryinfo] $pwd.Path)` that implies the `.FullName` value of that instance, and the `.Parent` property instance is constructed _relative to it_ (irrespective of the process working dir.). (If `$pwd.Path` is based on a _PowerShell-only_ drive, however, problems can ensue.) – mklement0 Feb 18 '23 at 00:07
  • 1
    But at its not, isn't it about binding and a"pure" .NET object and the"enhanced" PS version with its "PS..." NoteProperties? The default parameterSet wants to bind `-LireralParh` to `$_.PSPath`, and when that doesn't exist , things go south... – Keith Miller Feb 18 '23 at 00:10
  • 1
    @KeithMiller, that is _also_ a problem, but a _separate_ one. It (typically) wouldn't be a problem if the `.ToString()` value of `.Parent` yielded its `.FullName` property value, as it does in PowerShell (Core). That said, as the answer notes, the absence of a `.PSPath` property causes _string_-based binding to the `-Path` parameter, making the input paths subject to interpretation as [wildcard expressions](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_Wildcards). – mklement0 Feb 18 '23 at 00:50