3

I am experiencing the following behavior in PowerShell that I cannot explain and find cumbersome.

I am working in an arbitrary directory and the path of the directory is shown in the prompt:

PS C:\Users\Rene\AppData\Local\Temp>

Also, get-location reports the "correct" path:

PS C:\Users\Rene\AppData\Local\Temp> get-location

Path
----
C:\Users\Rene\AppData\Local\Temp

I then type mkdir xyz | cd in order to create a directory and change the working directory into this new directory:

PS C:\Users\Rene\AppData\Local\Temp> mkdir xyz | cd

All of a sudden, the path in the prompt is prefixed with Microsoft.PowerShell.Core\FileSystem:::

PS Microsoft.PowerShell.Core\FileSystem::C:\Users\Rene\AppData\Local\Temp\xyz>

This change is also reflected with get-location:

PS Microsoft.PowerShell.Core\FileSystem::C:\Users\Rene\AppData\Local\Temp\xyz> get-location

Path
----
Microsoft.PowerShell.Core\FileSystem::C:\Users\Rene\AppData\Local\Temp\xyz

What is going on here and how can I turn that prefix off?

1
  • That's because of how pipeline binding of file system items work. What you're seeing is "the real path" as seen by PowerShell. You can avoid this by doing mkdir xyz |cd -Path {$_.FullName} Commented Mar 26, 2020 at 15:53

2 Answers 2

5

In reverse order:

How can I turn that prefix off?

Easy, use explicit pipeline binding!

mkdir xyz |cd -Path {$_.FullName}

What is going on here?

Great question! What you see here is a side-effect of how the provider cmdlets (Get-ChildItem, Get-Item, Set-Location etc.) implements pipeline binding.

When you call New-Item (which is what mkdir does) against the FileSystem provider, it returns an object (corresponding to the newly created file or directory) that has a bunch of hidden properties that PowerShell uses to keep track of items across providers - these can be discovered with Get-Member -Force:

PS C:\> Get-Item .|Get-Member PS* -MemberType NoteProperty -Force


   TypeName: System.IO.DirectoryInfo

Name          MemberType   Definition
----          ----------   ----------
PSChildName   NoteProperty string PSChildName=C:\
PSDrive       NoteProperty PSDriveInfo PSDrive=C
PSIsContainer NoteProperty bool PSIsContainer=True
PSParentPath  NoteProperty string PSParentPath=
PSPath        NoteProperty string PSPath=Microsoft.PowerShell.Core\FileSystem::C:\
PSProvider    NoteProperty ProviderInfo PSProvider=Microsoft.PowerShell.Core\FileSystem

When you construct a pipeline statement with a provider cmdlet (such as Set-Location/cd for example) downstream, it uses the provider-qualified PSPath value to figure bind the input object.

This can be observed with Trace-Command:

PS C:\> Trace-Command -Expression {Get-Item .|Set-Location} -Name ParameterBinding,MemberResolution -PSHost

Which results in (I've removed the details for Get-Item for brevity):

DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Set-Location]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Set-Location]
DEBUG: ParameterBinding Information: 0 : BIND cmd line args to DYNAMIC parameters.
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Set-Location]
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Set-Location]
DEBUG: ParameterBinding Information: 0 :     PIPELINE object TYPE = [System.IO.DirectoryInfo]
DEBUG: ParameterBinding Information: 0 :     RESTORING pipeline parameter's original values
DEBUG: ParameterBinding Information: 0 :     Parameter [Path] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 :     BIND arg [C:\\] to parameter [Path]
DEBUG: ParameterBinding Information: 0 :         BIND arg [C:\\] to param [Path] SKIPPED
DEBUG: ParameterBinding Information: 0 :     Parameter [Path] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: MemberResolution Information: 0 :     Lookup
DEBUG: MemberResolution Information: 0 :         "Path" NOT present in type table.
DEBUG: MemberResolution Information: 0 :         Adapted member: not found.
DEBUG: ParameterBinding Information: 0 :     Parameter [StackName] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: MemberResolution Information: 0 :     Lookup
DEBUG: MemberResolution Information: 0 :         "StackName" NOT present in type table.
DEBUG: MemberResolution Information: 0 :         Adapted member: not found.
DEBUG: ParameterBinding Information: 0 :     Parameter [LiteralPath] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: MemberResolution Information: 0 :     Lookup
DEBUG: MemberResolution Information: 0 :         "LiteralPath" NOT present in type table.
DEBUG: MemberResolution Information: 0 :         Adapted member: not found.
DEBUG: MemberResolution Information: 0 :     Lookup
DEBUG: MemberResolution Information: 0 :         Found PSObject instance member: PSPath.
DEBUG: ParameterBinding Information: 0 :     BIND arg [Microsoft.PowerShell.Core\FileSystem::C:\] to parameter [LiteralPath]
DEBUG: ParameterBinding Information: 0 :         BIND arg [Microsoft.PowerShell.Core\FileSystem::C:\] to param [LiteralPath] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Set-Location]
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing

As you can see, PowerShell foregoes binding C:\ to Set-Locations -Path parameter, as somehow binding the PSPath property value to -LiteralPath is more appropriate?!

The reason for that is that the -LiteralPath parameter is aliased to PSPath, as can be seen by digging a bit with Get-Command:

PS C:\> (Get-Command Set-Location).Parameters['LiteralPath'] |Select Aliases

Aliases
-------
{PSPath}

The real reason why pipeline bindings for provider cmdlets is implemented this way, is twofold:

  1. Binding a "provider-native" string representation directly to Path might have unintended consequences related to globbing
    • Get-Item -Path 'a[bcd]' and Get-Item -LiteralPath 'a[bcd]' are two wildly different queries for example
  2. Binding on the provider-qualified PSPath value means that we can switch location to a different provider without losing automatic binding:
    • ie. PS Cert:\> $aFile |Get-Content just works
Sign up to request clarification or add additional context in comments.

1 Comment

Especially your last example (PS Cert:\> $aFile |Get-Content) explains the behavior. Thanks for the answer.
0

Building upon what Mathias posted you can use a very simple filter to save some typing and accomplish the same thing. Put this in a script and run it, run it directly, or drop it in your profile:

filter slp(){
    Set-Location -Path $_.FullName
}

You can then use:

mkdir | slp

Which will work identically to:

mkdir | sl

You can of course make the filter anything you want. "cdp" instead of "slp" for example.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.