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:

CmdletInput property name accepted on pipelineOutput property name on object sent to pipeline
Get-Item, Get-ChildItem, Set-ItemPathFullName
Join-Path, Split-Path, Test-PathPathn/a
Resolve-Path, Select-StringPathPath*
New-ItemNameFullName

* 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!

2 Responses to “Fixing consistency issues with Powershell’s filename properties”

  1. BUGBUG: poor title » Blog Archive » Easily manage files in Powershell ISE CTP3 Says:

    [...] BUGBUG: poor title …the same thing we do every night, Pinky… « Fixing consistency issues with Powershell’s filename properties [...]

  2. BUGBUG: poor title » Blog Archive » Invoke-UltraEdit.ps1: integrate your favorite text editor with the Powershell pipeline Says:

    [...] you can probably tell, you’ll need some other code for this to compilebe interpreted [...]

Leave a Reply