One thing that's missing from PowerShell is the ability to import foreign namespaces into the current context.  That leads to a lot of typing at the interactive prompt and bloated hard-to-read lines in your scripts.  For example, even if you've loaded the TFS client assemblies, you still have to write out fully-qualified type names like this:

RICBERG470> $itemSpec = new-object Microsoft.TeamFoundation.VersionControl.Client.ItemSpec ("$/foo", [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::None)

Ick! 

What can we do about it?  Well, the first parameter to new-object is actually a string, so we could store the string "Microsoft.TeamFoundation.VersionControl.Client" somewhere convenient and build from there.

RICBERG470> $vcc = "Microsoft.TeamFoundation.VersionControl.Client"
RICBERG470> $itemSpec = new-object "$vcc.itemspec" ("$/foo", [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::None)

Better, but far from perfect.

  • It has to be a real variable -- properties and other expressions won't work. 

    RICBERG470> $tfs = get-tfs jpresto-test
    RICBERG470> $tfs | add-member noteproperty vcc "Microsoft.TeamFoundation.VersionControl.Client"
    RICBERG470> new-object "$tfs.vcc.itemspec" ("$/foo", [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::None)
    New-Object : Cannot find type [jpresto-test.vcc.itemspec]...

  • It won't work when using [] to reference a type.  That will bite us when we want to access a static member/method, or when using enums as in our example.
  • I thought PowerShell was supposed to get us away from string processing, right?

Right!  Namespaces aren't objects in PowerShell, so we can't manipulate them directly, but types are.  (They're objects of type RuntimeType, to be exact.)  Objects are PowerShell's bread & butter -- importing a ton of them sounds clunkier than "using <namespace>" in C#, but it's actually a cinch.  I give you add-types.ps1:

param(
[string] $assemblyName = $(throw 'assemblyName is required'),
[object] $object
)

process {
if ($_) {
$object = $_
}

if (! $object) {
throw 'must pass an -object parameter or pipe one in'
}

# load the required dll
$assembly = [System.Reflection.Assembly]::LoadWithPartialName($assemblyName)

# add each type as a member property
$assembly.GetTypes() |
where {$_.ispublic -and !$_.IsSubclassOf( [Exception] ) -and $_.name -notmatch "event"} |
foreach {
# avoid error messages in case it already exists
if (! ($object | get-member $_.name)) {
add-member noteproperty $_.name $_ -inputobject $object
}
}
}

Usage example:

RICBERG470> $tfs | add-types "Microsoft.TeamFoundation.VersionControl.Client"
RICBERG470> $itemSpec = new-object $tfs.itemspec("$/foo", $tfs.RecursionType::none)

Much better.  Not only does it work in the [] case, we don't even need the brackets anymore!  Best of all, it works with tab completion.  "RecursionType" above only took 3 keystrokes (and no Shift key).  That's why I filter out exceptions & events, BTW -- they pollute the autocomplete list with zillions of types I'd never use in a script, much less interactively.  If you disagree, feel free to yank those conditions.

TFS readers: if you're with me so far, you'll probably want to edit your copy of get-tfs.ps1.  Just add this line to the end of the foreach block:

$tfs | add-types $entry[1]
 
edit 5/15/07: used IsSubClassOf() instead of -notmatch to filter out exceptions.  Thanks to Jay Bazuzi for the suggestion.