Expanding Wildcards in PowerShell

Yesterday I thought I’d jump into the deep end of PowerShell and try to make a provider; this lets you treat some data store as a drive in PowerShell.  It didn’t work out very well, mainly because I had no plan and just kind of hacked my way around it.  I didn’t want to do an AccessDB provider like the MS one and came up with the idea of a provider that treated each line of a text file like an item.  I kind of got overwhelmed with new information and decided to put it off for a bit.

So I switched to cmdlets today, since there’s a lot less planning involved in making some kind of cmdlet that does something.  The one I’m making takes Path and LineNumber parameters, then tries to retrieve the line of text corresponding to LineNumber from the indicated files.  I wanted to be fancy and accept wildcards in the Path parameter, and found that most people talking about wildcards are using a different approach to them than what I needed.

What You’ll Find

It seems like most people who talk about wildcards on the internet are excited about the WildcardPattern class.  I can see why.  Suppose I have a list of things I want to process and the user has given me a wildcard pattern to specify what items are important; if you don’t mind LINQ this is all it takes to prune your list:

var options = WildcardOptions.Compiled | WildcardOptions.IgnoreCase;
var pattern = new WildcardPattern(userPattern, options);

var matchedItems = from item in itemList
                   where pattern.IsMatch(item)
                   select item;

That’s awesome; I don’t have to bother processing wildcards.  However, it’s only useful if you have the list of items in the first place.  In my cmdlet, the user can pass me any arbitrary path with wildcards and I need to fill in the blanks.  The internet led me to believe this was the only wildcard processing in place, so I was looking at having to traverse the file system to make the list, then apply filtering.  Yuck.

What I Needed

One of the things I like about PowerShell is I can tell MS really stands behind it and wants it to become the new command-line.  The Cmdlet Development Guidelines are already up; compare this to other exciting technologies like WPF which get guidelines way after they’re released (I haven’t found any WPF ones, sadly).  Anyway, I was looking over these guidelines and found what I needed under the section "Support Windows PowerShell Paths".  The SessionState.Path property is a PathIntrinsics instance that has several useful methods that do the wildcard expansion for me.  For example, assuming I just happen to know the path is a PowerShell path, I can write this code:

ProviderInfo pInfo;
var resolvedPaths = SessionState.Path.GetResolvedProviderPathFromPSPath(FilePath, out pInfo);

Now, resolvedPaths is a Collection<string> of every path name that matches the wildcards in the parameter.

One thing I’m curious about is what to do with this.  My Path parameter is an array, as recommended by the design guidelines.  My plan was to loop over all of the parameters, get the expanded paths, then get the indicated line numbers from each item.  But I’m curious: is it wrong to do this expansion in BeginProcessing and replace my Path property with an array of fully-expanded items, then loop over that in ProcessRecord?  It seems wrong to change the value of the user-provided parameters, but for some reason I don’t like the nested for loops that doing it all in ProcessRecord would require.  I wonder if there’s a better way?

2 thoughts on “Expanding Wildcards in PowerShell

  1. I came across this several years after the fact, trying to figure out how to expand a parameter passed to a function with wild cards in it.

    This code seems to work: passed a single string $pattern, it will fill a character array $files with all the files that match the pattern in the current directory:

    function expand-arg
    {
    [CmdletBinding()]
    param([string] $pattern)
    $loc = Get-Location
    $provider = $loc.Provider
    $files = @()
    $longfiles = $pscmdlet.GetResolvedProviderPathFromPSPath($pattern, [ref] $provider)
    $longfiles | % { $file = gci $_ ; $files += $file.Name }

    # your logic here
    }

    There are several things here that weren’t entirely obvious to me. First, I used the CmdletBinding() attribute so that I could get at a pscmdlet instance (which implements the GetResolvedProviderPathFromPSPath() method). Second, I fished a ProviderInfo instance out of the current location (this should work inside any provider) — you need one of those to pass as the second argument of GetResolvedProviderPathFromPSPath()). And then third, I used gci to strip the filename off the complete names returned to me by GetResolvedProviderPathFromPSPath(), and to discard the current directory (which is inexplicably returned by GetResolvedProviderPathFromPSPath() even when it doesn’t match the wildcards in the pattern).

  2. Pingback: Powershell wildcards | Hyfresh

Comments are closed.