Fixing consistency issues with Powershell’s filename properties
When I decided to bitch about select-object, I used “dir” in my examples for a reason: passing filesystem objects around the pipeline is simultaneously one of the most common yet one of the most annoying things you can do in Powershell. Let’s review some built-in cmdlets from the POV of getting the full path to a given file or folder:
| Cmdlet | Input property name accepted on pipeline | Output property name on object sent to pipeline |
| Get-Item, Get-ChildItem, Set-Item | Path | FullName |
| Join-Path, Split-Path, Test-Path | Path | n/a |
| Resolve-Path, Select-String | Path | Path* |
| New-Item | Name | FullName |
* note that the built-in documentation for Resolve-Path is wrong! as of CTP3, it’s claiming the output is a string
Plus, you may be working with 3rd party tools that obey none of the above conventions.
For example, with the TFS Power Tools, Hyung had the foresight to add string[] to the list of types we can convert to QualifiedItems behind the scenes using extra constructors, but we still don’t support the ValueFromPipelineByPropertyName idiom that’s needed to make non-trivial filesystem objects flow. Furthermore, the objects we output are native TFS types, which were never designed with Powershell in mind. Most of the time there’s little overlap between these objects and non-TFS cmdlets, but a big exception is the LocalItem property of the ExtendedItem object returned by “tfprop.” In hindsight, we should have added a NoteProperty that aliased it to Path. (…or FullName, or Name, depending on whether the PS guys make up their mind :) )
Then there are tools that have nothing at all to do with Powershell. Some sort of wrapper script is going to be required regardless, but without consistency in the kind of input your wrapper can expect, it can get needlessly complex.
My solution to all of the above is outlined below. Time to let the code do the talking:
|
001
002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 |
function get-fullpath
{ [CmdletBinding()] Param( [Parameter(ParameterSetName="ByPropertyName", Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true, ValueFromPipeline=$true)] [Alias("FullName")] [Alias("FullPath")] # [Alias("Name")] - too many false positives [Alias("Path")] [Alias("LocalItem")] [string] $file ) Process { # eliminate nonexistent files, obviously, but also random objects that were coerced from the pipeline by calling their ToString() method if (!(test-path $file)) { break } # expand relative paths (in case the immediate window's workingdir != the workingdir of powershell_ise.exe) resolve-path $file | # and also make sure UNC paths are in normal format, not Powershell format convert-path } } |
Who knew that ValueFromPipelineByPropertyName worked on aliases? I didn’t. I thought it would be nice, but since I didn’t see it documented anywhere, my initial sketch for this function used a ton of ugly ParameterSets as a workaround. Somewhere in there I decided to try Aliases; lo and behold, it works! Yay.
This function is useful on its own – if nothing else I use it as a wholesale replacement for resolve-path, whose behavior on UNC paths is bizarre in my opinion – but its real value is as a helper. For example, a couple snippets from my $profile:
|
001
002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 |
function tfedit
{ [CmdletBinding()] Param( [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)] [psobject] $file, [Parameter()] [switch] $PassThru = $false ) Process { if ($PassThru) { $file } tfpend -edit ($file | get-fullpath) } } |
|
001
002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 |
function ise
{ [CmdletBinding()] param ( [parameter(Mandatory=$True, Position=0, ValueFromPipeline=$True)] [psobject]$file, [Parameter()] [switch] $PassThru = $false ) process { if ($PassThru) { $file } $psise.CurrentOpenedRunspace.OpenedFiles.Add(($file | get-fullpath)) } } |
I think the PS parser should be smart enough that double-parens are not required in the second example, but oh well. Pretty simple overall, and super convenient. Enjoy!
March 27th, 2009 at 5:47 pm
[...] BUGBUG: poor title …the same thing we do every night, Pinky… « Fixing consistency issues with Powershell’s filename properties [...]
March 27th, 2009 at 7:19 pm
[...] you can probably tell, you’ll need some other code for this to compilebe interpreted [...]