add-types.ps1 - poor man's "using" for PowerShell
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.
Comments
- Anonymous
February 21, 2007
A recent request : "I want to be able to add a label to all files that were modified/added/whatever as