Weird Terminating Error Behavior

I’m writing a dirty hack PowerShell provider so that I can have a reasonable base of knowledge before approaching something more work-oriented.  One component of the path the provider accepts must be an integer, so I decided to throw a terminating error if the path does not follow this rule. I also decided I’d do this so I could examine how terminating errors are handled. Something that mystifies me is the terminating error isn’t behaving as I understand it.  Here’s the code I use to throw the error:

var record = new ErrorRecord(new ArgumentException("path"), "InvalidItemIndex",
                           ErrorCategory.InvalidArgument, null);
var details = new ErrorDetails("The second element of a text file path must be an integer.");
record.ErrorDetails = details;
ThrowTerminatingError(record);

I expect that I’d see the information in the ErrorDetails property in some way, based on how the documentation is written; instead it seems like this information gets lost somewhere:

PS C:\Documents and Settings\myUsername\My Documents> get-item text:\Section1\item1
Get-Item : The second component of the path must be an integer.
At line:1 char:9
+ get-item  <<<< text:\Section1\item1
PS C:\Documents and Settings\myUsername\My Documents> $error[0].ErrorDetails -eq $null
True
PS C:\Documents and Settings\myUsername\My Documents>

Maybe it’s fixed in PowerShell v.2.0, or maybe I’m doing something wrong, but this is definitely not what I expected.

Writing Objects from PowerShell Scripts

I’m going to continue writing about PowerShell because it’s fun to talk about and fun to use.

Today I decided to try and analyze some data in text files using a PowerShell script. The files were simulations of a betting strategy in a coin-toss scenario. The first line represented the final balance and every line thereafter represented the balance after a bet was completed. I was interested in the average balance over time and the average minimum balance for all files. The script itself was pretty uninteresting until I got to the output:

$files = get-childitem *.txt
$sum = 0
$count = 0
$minSum = 0

foreach ($file in $files)
{
    Write-Verbose "Processing file: $file"
    $sum += [long](get-content $file -totalCount 1)
    $count += 1
    
    $values = get-content $file
    $minimum = 0
    
    foreach ($line in $values)
    {
        $value = [int]$line
        $minimum = [Math]::Min($value, $minimum)
    }
    
    $minSum += $minimum
}

$average = $sum / $count
$averageMinimum = $minSum / $count

Now is when it gets fun. I originally just wrote the output using write-output:

write-output "Average: $average"
write-output "MinAverage: $minAverage"

This didn’t seem very PowerShell-like. I wanted to output an object that could be used by other scripts (for the heck of it at least.) I knew how to do this from a cmdlet, but had to search to find how to do it from a script. There seems to be two ways to make objects or object-like things to write.

Associative Arrays

Perl hackers love their hashes, and with good reason: this little data structure is very powerful. In the .NET world, we call them "dictionaries" for some reason. PowerShell associative arrays are declared with this syntax:

$array = @{name = value; name2 = value2}

If I wrote one of these using write-object, I got a nice list format with headers and could access the individual values via property syntax. Joy! Still, this isn’t really what I was looking for because I was really fishing for a way to output a PSObject.

PSObjects

You can create a PSObject using New-Object, but at first I couldn’t figure out how to let this object know it should have the properties I wanted. This is something you can use Add-Member for. You have to specify the type of member you are adding, and I believe the valid member types are listed in the PSMemberTypes enumeration. In this case, I needed NoteProperty since there’s no base object:

$output = New-Object PSObject   
$output | add-member NoteProperty -name Count -value $count
$output | add-member NoteProperty -name Average -value $average
$output | add-member NoteProperty -name AverageMinimum -value $averageMinimum
write-output $output

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?

Working with Enumerations in PowerShell

Two posts in one day should show you I’m excited about PowerShell.

I wanted to update the script from my last post to find my My Documents folder so if I hop to Vista I won’t get an error, and I found it slightly difficult to figure out how to use Environment.GetFolderPath because I got a lot of errors trying to use the Environment.SpecialFolder enumeration.  It turns out if you pass the string value for the enumeration value you want, you get basically what you want:

$myDocs = $myDocs = [System.Environment]::GetFolderPath("MyDocuments")
Set-Location $myDocs
Set-Variable -name home -value $myDocs -force

Apparently, you can also use flags enumerations.

Setting $home in PowerShell

Lately I’ve been getting acquainted with Windows PowerShell, which is something Windows has needed for a long time.  Finally, there’s a real shell for Windows that supports scripting in a format other than horrific batch files.

At work, for whatever reason the powers that be have set our %HOMEDRIVE% and %HOMEPATH% environment variables to a networked drive.  This would be a good idea, but the problem is this drive has a pretty low quota applied and I managed to fill it up within a week or so.  Basically, people use it as a convenient public share directory.  I mention this because changing the environment variables in this way has caused me much grief.  cmd.exe defaults to %HOMEDRIVE%\%HOMEPATH% when it starts, so every time I open a command prompt I end up in this stupid useless directory.  PowerShell is not immune to this either.

I noticed that my PowerShell always started here and that the $home variable seemed set to it as well.  I set up a profile to change my location to the correct place, but had trouble setting the value of the $home variable since it is read-only (or constant, according to the error message).  Luckily, there’s a way to tell PowerShell, "I know what I’m doing." and the Set-Variable cmdlet takes a -force parameter.  So in the end, my script ended up looking something like this:

Set-Location "C:\Documents and Settings\username\My Documents"
Set-Variable -name home -value "C:\Documents and Settings\username\My Documents" -force

I could probably make this more OS-agnostic by using the .NET Environment class, but that’s a project for another day.

Why doesn’t Subversion add respect svn:ignore?

I use Subversion for source control on my personal projects, and I’ve got to say it’s so easy to set up and use there’s no excuse for not using source control.  There’s one thing that really bothers me though.

When setting up a .NET project, I have a few steps I have to follow due to the small clashes with the default project templates I have.  I have to rearrange a few files after VS creates the project, then I get to adding files.  One of the first things I do is set up svn:ignore on each directory; I want it to ignore *.suo, *.user, bin, obj, and occasionally a few other files/directories.  Every time I do this, I double-check with svn status to make sure that nothing I don’t want is showing up, then I feel like it’s safe to do svn add directory.  I mean, I told svn I’d like to ignore those files, right? so it should ignore them, right?  Wrong.

So, for every project, I have to either add every file then use "svn delete –force" to remove just the files that I told it to ignore in the first place, or I have to manually add every file and directory in my project, being careful not to add anything that I’m ignoring.  This is frustrating, to say the least.  At first, I thought it was because the directory wasn’t yet under source control or the svn:ignore setting hadn’t been committed, but I’ve tried committing all directories and property changes first to no avail.

The part of it that’s most aggravating is the fact that "svn add" has a –no-ignore argument; what on Earth is it there for if the command already disregards svn:ignore?  I hope I’m just missing something stupid; I’m going dig around the internet and see what I can find.

*update* According to a blog I found, it has to do with the OS doing wildcard expansion, not Subversion.  That actually makes a lot of sense, because the OS has no idea about svn:ignore.  Apparently the default add behavior is to act as if –noignore is specified (which seems dumb to me), so when you pass it a list of files it just adds all of them.  The solution?  Set up the ignore traits on the directory, then use "svn add –force ."  I have no idea why the –force is required.  It looks like this got added to the SVN book very recently.

Inheriting from ReadOnlyObservableCollection<T> and a Lesson about Constructors

I had need of returning a bindable collection from one of my classes, but didn’t want to allow the caller to modify the collection.  This generally requires a straightforward pattern in .NET: make a SomethingCollection class that derives from Collection<T> and a ReadOnlySomethingCollection class that derives from ReadOnlyCollection<T>, then only return the read-only version.  Most examples don’t bother with declaring the derived types, but I feel like it makes everything look better and the framework classes seem to favor declaring their own collection types as well.

I was having one heck of a time getting everything to work. I tried the most basic implementation:

public class ReadOnlySomethingCollection : ReadOnlyObservableCollection
{
}

This failed miserably with a fairly archaic error message:

‘full typename’ does not contain a constructor that takes ‘0’ arguments

I struggled with this for a while. The error message was clearly correct; if you walk the inheritance tree there’s no base class that defines a parameterless constructor. Still, defining a constructor that matched the signature of the 1-parameter constructor didn’t seem to be solving the problem. I decided it’d be best to step outside my project and make a simple example project in preparation for asking forums and newsgroups what was going on. It turns out switching languages to VB .NET was a good idea, too, because its error message was much more helpful once I had the constructor in place:

First statement of this ‘Sub New’ must be a call to ‘MyBase.New’ or ‘MyClass.New’ becasue base class ‘…’ does not have an accessible ‘Sub New’ that can be called with no arguments.

I saw this, thought about it for a minute, then had a forehead-slap moment. When a class derives from another, it’s almost as if you have two objects glued together. The base class has to be initialized before the derived class can be initialized, because the derived class’s constructor must be able to assume its base members are in a good state. So, in my case, the compiler was trying to initialize a ReadOnlyObservableCollection<T>, but since there was no parameterless constructor it couldn’t. The solution was to do exactly what VB said: make sure I call some base-class constructor as my first action. I finally got it to work in C#:

public class ReadOnlySomethingCollection : ReadOnlyObservableCollection
{
    public ReadOnlySomethingCollection(ObservableCollection list) : base(list)
    {
    }
}

This isn’t the first time that it’s taken VB error messages for me to understand the solution to my problem; I wish there was more consistency between the error messages.