PowerShell 2.0 CTP3: Modules, in Practice - Closures

After some hints from Ibrahim on the Microsoft PowerShell team, I realised it was possible to rewrite the dynamic module body generation from the Get-Interface function in my last post without using string literals and nested here-string tricks. I was able to increase the brevity by using a new [to PowerShell] v2 feature called “Closures.” Ibrahim talks a bit about the technique over on the official PowerShell Blog. It’s a common feature in so-called Functional languages and there are plenty of other tidbits to read about it, both academic and practical if you search online.

A closure in PowerShell, in short, lets you take a snapshot of a ScriptBlock by calling its GetNewClosure() method. The snapshot is taken of its variables and will let you pass it around so it is no longer directly tied to the particular scope chain it was declared in. As some are fond of saying though, Thar Be Dragons. Although closing around a ScriptBlock will make copies of the PSVariables that are within, it is still subject to the whims of .NET – particularly ByRef and ByValue semantics. If a ScriptBlock contains a PSVariable pointing to an integer, it’s safe to say that integer will be copied and is frozen as that value as long as you remain within the domain of the current SessionState context (more about this later).

.NET intervenes: ByRef and ByVal

Closing around a ScriptBlock that contains PSVariables that point to reference types, however - like Arrays for example – will only duplicate the reference, not the instance itself. If elements in the array are changed via the PSVariable from the originating scope, the corresponding array element in the closure will also change. After all – you got a copy of the pointer, not the value itself. This is the very essence of ByRef and ByVal.

Let’s take a look at the revised Get-Interface script:

function Get-Interface {

#.Synopsis
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#.Description
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#
#   As of v2.0, PowerShell cannot cast .NET instances to a particular interface. This makes it
#   impossible (shy of reflection) to call methods on explicitly implemented interfaces.   
#.Parameter Object
#   An instance of a .NET class from which you want to get a reference to a particular interface it defines.
#.Parameter InterfaceType
#   An interface type, e.g. [idisposable], implemented by the target object.
#.Example
#   // a class with explicitly implemented interface   
#   public class MyObj : IDisposable {
#      void IDisposable.Dispose()
#   }
#   
#   ps> $o = new-object MyObj
#   ps> $i = get-interface $o ([idisposable])
#   ps> $i.Dispose()      
#.ReturnValue
#   A PSCustomObject with ScriptMethods and ScriptProperties representing methods and properties on the target interface.
#.Notes
# AUTHOR:    Oisin Grehan http://www.nivot.org/
# LASTEDIT:  2009-03-28 18:37:23
# REVISION:  0.2

    [CmdletBinding()]
    param(
        [ValidateNotNull()]
        $Object,
        
        [ValidateScript( { $_.IsInterface } )]
        [type]$InterfaceType
    )

    $script:t  = $Object.GetType()
    
    try {
        
        $script:m  = $t.GetInterfaceMap($InterfaceType)

    } catch [argumentexception] {
        
        throw "Interface $($InterfaceType.Name) not found on ${t}!"
    }

    $script:im = $m.InterfaceMethods
    $script:tm = $m.TargetMethods
    
    # TODO: use param blocks in functions instead of $args
    #       so method signatures are visible via get-member
    
    $body = {
         param($o, $i) 
         
         $script:t  = $o.GetType()
         $script:m  = $t.GetInterfaceMap($i)
         $script:im = $m.InterfaceMethods
         $script:tm = $m.TargetMethods
                  
         for ($ix = 0; $ix -lt $im.Count; $ix++) {
            
            $mb = $im[$ix]

            # for the function body, we close over $ix to capture the index
            # so even on the next iteration of this loop, the $ix value will
            # be frozen within the function's scriptblock body
            set-item -path function:script:$($mb.Name) -value {

                # call corresponding target method
                $tm[$ix].Invoke($o, $args)

            }.GetNewClosure() -verbose -force

            if (!$mb.IsSpecialName) {
                # only export the function if it is not a getter or setter.
                Export-ModuleMember $mb.Name -verbose
            }
         }
    }

    write-verbose $body.tostring()    
    
    # create dynamic module
    $module = new-module -ScriptBlock $body -Args $Object, $InterfaceType -Verbose
    
    # generate method proxies - all exported members become scriptmethods
    # however, we are careful not to export getters and setters.
    $custom = $module.AsCustomObject()
    
    # add property proxies - need to use scriptproperties here.
    # modules cannot expose true properties, only variables and 
    # we cannot intercept variables get/set.
    
    $InterfaceType.GetProperties() | % {

        $propName = $_.Name
        $getter   = $null
        $setter   = $null
       
        if ($_.CanRead) {
            
            # where is the getter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetGetMethod())
            
            # bind the getter scriptblock to our module's scope
            # and generate script to call target method
            #
            # NOTE: we cannot use a closure here because sessionstate
            #       is rebound to the module's, and $ix would be lost
            $getter = $module.NewBoundScriptBlock(
                [scriptblock]::create("`$tm[{0}].Invoke(`$o, @())" -f $ix))
        }
        
        if ($_.CanWrite) {
            
            # where is the setter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetSetMethod())
            
            # bind the setter scriptblock to our module's scope
            # and generate script to call target method
            #
            # NOTE: we cannot use a closure here because sessionstate
            #       is rebound to the module's, and $ix would be lost
            $setter = $module.NewBoundScriptBlock(
                [scriptblock]::create(
                    "param(`$value); `$tm[{0}].Invoke(`$o, `$value)" -f $ix))
        }
        
        # add our property to the pscustomobject
        $prop = new-object management.automation.psscriptproperty $propName, $getter, $setter
        $custom.psobject.properties.add($prop)
    }
    
    # insert the interface name at the head of the typename chain (for get-member info)
    $custom.psobject.TypeNames.Insert(0, $InterfaceType.FullName)
    
    # dump our pscustomobject to pipeline
    $custom
}

The changes are around line 56, where I now declare $body with a script syntax, no longer using [scriptblock]::create. Where previously I used a subexpression inside a here-string to general literal function declarations, I now use a “for” loop and set-item to create the functions (thanks Ibrahim!). You can see that I am calling GetNewClosure() on the function body each time I create a new function. This “captures” the value of the $ix index variable, so that the value will not change on subsequent loops. If I did not use closures, when the for loop ended, all of the functions would be using the same terminating value of the loop for $ix, which would be $im.count + 1. Doh.

Module SessionState Context, Wha?

So if I ripped out that literal string parsing stuff for the module $body, why am I still using it for the property getter/setter generation? (lines 115 / 130)  Well, I have no choice: Creating a new module creates an entirely new SessionState; in simple terms, session state is essentially a giant Hashtable in which PSVariables are stored. If I closed over those get/set ScriptBlocks containing the $ix PSVariable, when it is bound to the module it will use the new module’s SessionState – which is empty! The value of $ix would be undefined. The other variables in those strings are escaped, because they will be evaluated in the module’s context later, when the getters and setters are invoked. Those variables are seeded by the module’s initialization done at the new-module call at line 88.

It’s all very succinct, but rest assured, very powerful.

Have fun!

NOTE: I realise that some more refactoring could probably eliminate this script parsing, but the value is in understanding how modules may affect usage of closures, and the problems that may occur.

PowerShell 2.0 CTP3: Modules, in Practice – .NET Interfaces

This is a bit of a hardcore example of the power that modules have brought to the table in v2.0. First, a little background - PowerShell’s type adaptation system has always ignored the concept of interfaces. Frankly, it never really needed to pay them any attention. The adapted view of any instance of a .NET class is just an aggregate of all its methods; the most derived instance is the default, and only, view. This is the most simple and most frequently used view. However, this all goes to hell when you bring in the notion of explicit interfaces.

Explicit Interface Definitions

Here’s an example of a C# class, Test, with two interfaces: IFace, and IFaceEx. I’ve put this whole example in PowerShell script so you can easily test it without firing up Visual Studio. Add-Type cmdlet to the rescue!

if (-not ("Test" -as [type])) {    
    add-type @"
        public interface IFace {
            string Hello(string name);
            string Goodbye();
        }
        
        public interface IFaceEx {
            string Hello(string name);
            string Goodbye();
            string Prop1 {
                get;
                set;
            }                
        }
        
        public class Test : IFace, IFaceEx {
            public string Hello(string name) {
                return "Hello" + name + " from IFace";
            }
            public string Goodbye() {
                return "Goodbye from IFace";
            }            
            string IFaceEx.Hello(string name) {
                return "Hello " + name + " from IFaceEx";
            }
            string IFaceEx.Goodbye() {
                return "Goodbye from IFaceEx";
            }
            
            private string _prop1 = "Foo";
            
            string IFaceEx.Prop1 {
                get { return _prop1; }
                set { _prop1 = value; }
            }
        }
"@    
}

If you new up an instance of Test, you’ll see that the explicit interface definitions are hidden. Any calls to Hello(string) will invoke the IFace implementation.

There is no way to call IFaceEx.Hello without using reflection!

So, this is where the beauty of modules can help us. The following function will take an instance of a .NET class, and the interface you want a  reference to, and will return a PSCustomObject with ScriptMethods and ScriptProperties bound to that interface’s contract. You don’t have to do this only with explicit interfaces, any interfaces will work ;-)

Introducing Get-Interface

A script is worth a thousand words.

function Get-Interface {

#.Synopsis
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#.Description
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#
#   As of v2.0, PowerShell cannot cast .NET instances to a particular interface. This makes it
#   impossible (shy of reflection) to call methods on explicitly implemented interfaces.   
#.Parameter Object
#   An instance of a .NET class from which you want to get a reference to a particular interface it defines.
#.Parameter InterfaceType
#   An interface type, e.g. [idisposable], implemented by the target object.
#.Example
#   // a class with explicitly implemented interface   
#   public class MyObj : IDisposable {
#      void IDisposable.Dispose()
#   }
#   
#   ps> $o = new-object MyObj
#   ps> $i = get-interface $o ([idisposable])
#   ps> $i.Dispose()      
#.ReturnValue
#   A PSCustomObject with ScriptMethods and ScriptProperties representing methods and properties on the target interface.
#.Notes
#   AUTHOR:    Oisin Grehan http://www.nivot.org/
#   LASTEDIT:  2009-03-25 11:35:23

    [CmdletBinding()]
    param(
        [ValidateNotNull()]
        $Object,
        
        [ValidateScript( { $_.IsInterface } )]
        [type]$InterfaceType
    )

    $private:t  = $Object.GetType()
    
    try {
        
        $private:m  = $t.GetInterfaceMap($InterfaceType)

    } catch [argumentexception] {
        
        throw "Interface $($InterfaceType.Name) not found on ${t}!"
    }

    $private:im = $m.InterfaceMethods
    $private:tm = $m.TargetMethods
    
    # TODO: use param blocks in functions instead of $args
    #       so method signatures are visible via get-member
    
    $body = [scriptblock]::Create(@"
        param(`$o, `$i)    
        
        `$script:t  = `$o.GetType()
        `$script:m  = `$t.GetInterfaceMap(`$i)
        `$script:im = `$m.InterfaceMethods
        `$script:tm = `$m.TargetMethods
        
        # interface methods $($im.count)
        # target methods $($tm.count)
        
        $(
            for ($ix = 0; $ix -lt $im.Count; $ix++) {
                $mb = $im[$ix]
                @"
                function $($mb.Name) {
                    `$tm[$ix].Invoke(`$o, `$args)
                }

                $(if (!$mb.IsSpecialName) {
                    @"
                    Export-ModuleMember $($mb.Name)

"@
                })
"@
            }
        )
"@)
    write-verbose $body.tostring()    
    
    # create dynamic module
    $module = new-module -ScriptBlock $body -Args $Object, $InterfaceType -Verbose
        
    # generate method proxies - all exported members become scriptmethods
    # however, we are careful not to export getters and setters.
    $custom = $module.AsCustomObject()
    
    # add property proxies - need to use scriptproperties here.
    # modules cannot expose true properties, only variables and 
    # we cannot intercept variables get/set.
    
    $InterfaceType.GetProperties() | % {

        $propName = $_.Name
        $getter   = $null
        $setter   = $null
       
        if ($_.CanRead) {
            
            # where is the getter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetGetMethod())
            
            # bind the getter scriptblock to our module's scope
            # and generate script to call target method
            $getter = $module.NewBoundScriptBlock(
                [scriptblock]::create("`$tm[{0}].Invoke(`$o, @())" -f $ix))
        }
        
        if ($_.CanWrite) {
            
            # where is the setter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetSetMethod())
            
            # bind the setter scriptblock to our module's scope
            # and generate script to call target method
            $setter = $module.NewBoundScriptBlock(
                [scriptblock]::create(
                    "param(`$value); `$tm[{0}].Invoke(`$o, `$value)" -f $ix))
        }
        
        # add our property to the pscustomobject
        $prop = new-object management.automation.psscriptproperty $propName, $getter, $setter
        $custom.psobject.properties.add($prop)
    }
    
    # insert the interface name at the head of the typename chain (for get-member info)
    $custom.psobject.TypeNames.Insert(0, $InterfaceType.FullName)
    
    # dump our pscustomobject to pipeline
    $custom
}
Here's how to use it, with our example Test class as added earlier:
$if = Get-Interface (new-object test) ([ifaceex]) -verbose
$if.Hello("Oisin") # returns Hello Oisin from IFaceEx

$if.Prop1 = "Test" # property setter
$if.Prop1 # propery getter

Have fun!

PowerShell 2.0 CTP3: Modules, in Practice.

There’s a bit of a flap going on now around the blogosphere as various vendors, eager to get onto the PowerShell train, are doing the bare minimum to get their product “powershellized.” No one has spent any time reading – or if they did, they weren’t successful in trying to understand it – the Microsoft Command Line Standard.

Modules as Namespaces

That’s right, modules are not as crazy sounding as they seem. They can be used quite simply to just group a load of functions together while allowing easy disambiguation should there be a name collision with another function or cmdlet. It wasn’t always this easy.

Namespaces in PowerShell v1.0

In PowerShell v1.0 it was possible to load two snap-ins that contained identically named commands. This causes PowerShell to spit out an error about ambiguous commands should you try to invoke one. The answer to this was to get the containing snap-in name, and prefix it to the cmdlet name using a backslash as a separator:

$o = Pscx\New-Object Collections.Generic.Dictionary –Of String, Int

As you can see, this lets us call the hypothetical PowerShell Community Extensions version of New-Object to create a generic type. If you want to call the original one, you would have to prefix it like this:

$o = Microsoft.PowerShell.Utility\New-Object Int[] 5

Blech - that’s a bit of a lengthy sentence, but the solution to this is to use an alias. Aliases are found before Cmdlets in the search path:

New-Alias New-Object Microsoft.PowerShell.Utility\New-Object
$o = New-Object Int[] 5 

The reason we need an alias here is because if you just typed the cmdlet without the snap-in prefix, PowerShell v1.0 complains that it doesn’t know which one you want. We forgot to add the mind reader snap-in!

How about functions in PowerShell v1.0? If you dot source a ps1 file that contains functions that already exist in the caller’s session, how do you disambiguate them? Oops! You can’t. They are overwritten. Ouch.

You cannot use the namespace\ prefix for functions in v1 - the support is just not there. However…

Namespaces in PowerShell v2.0

Things are a lot better in this version. Let’s say you have two groups of functions that you use in your business. One for your SharePoint farms, and another set for your Citrix farms. Let’s ignore the perfectly acceptable idea of prefixing the noun to differentiate for the moment and just imagine we have two ps1 files, containing ideally named function with approved verb, simple noun:

# sharepoint functions
function Get-Server {
   # ... gets sharepoint server
}
function Get-Farm {
   # ... gets sharepoint farm
}

And the second script:

# citrix functions
function Get-Server {
    # ... gets citrix server
}
function Get-Farm {
    # ... gets citrix farm
}

If you tried to load these up with via a dot source, one after the other, the functions from the second ps1 would overwrite the first lot. So this is where the wonder world of modules is entered. First step:

After renaming both ps1 files to use the psm1 extension instead, you have created modules of the simplest form. Ignoring the details of how this is deployed (there are several ways – documented on Microsoft and on many blogs, including this one), let's look at how it's loaded and used:

import-module citrix
# Now our functions are loaded. Invocation options:
# assuming no clash, invoke with simple names
get-farm bleh
# load sharepoint module (it also has get-farm)
import-module sharepoint
# ok, sharepoint functions are loaded too, so we have two get-farm commands
# and the last loaded wins, so citrix get-farm is inaccessible through simple syntax, but...
# let's refer specifically to the citrix module function
citrix\get-farm
# and for sharepoint:
sharepoint\get-farm
# and thirdly, if you don't want to use the module qualifier (or "namespace") then you ALSO have the
# choice of applying a prefix on import - this is the primary use case for quick interactive use:
import-module citrix -prefix ctx
# now you can call functions like:
get-ctxfarm
# or even citrix\get-ctxfarm if you so felt like it, but redundant.
# same for sharepoint
import-module sharepoint -prefix sp
get-spfarm

Make sense so far? Unfortunately, as mentioned earlier, in v1.0 the module qualifier (or "namespace") works ONLY for binary commands imported in a Snap-In DLL. Let’s see how that works with get-childitem, a command from one of the preloaded Snap-Ins in PowerShell:

gcm get-childitem | select pssnapin

outputs:

PSSnapIn
--------
Microsoft.PowerShell.Management

Ergo, we invoke it like:
Microsoft.PowerShell.Management\get-childitem
# ... outputs file listing ...

Module Aliasing

Some modules may have longer names that you would be comfortable to type all the time. Thankfully, there is a trick you can do that takes advantage of the fact that modules can be nested. Let’s say you have a module from a vendor that is called something like “Vendor.Division.Product” and it has a cmdlet in it called Get-Thing that happens to clash with another Get-Thing you have loaded. Normally to disambiguate, you have to type:

import-module vendor.division.product
Vendor.Division.Product\Get-Thing

…which is a bit annoying. Instead, wrap the initial import-module statement in a dynamic module where you supply an explicit name for it, and import that instead ;-)

new-module –name product { import-module vendor.division.product } | import-module
# you can now invoke the command like this:
product\get-thing

Any modules that are imported inside a module have their commands automatically exported as if they are part of the root module (unless you control visibility with export-modulemember). Cool, eh?

Have fun!

Visual Studio 2008 Extensions for WSS 3.0 v1.3 March CTP

It looks like the SharePoint team is slowly catching up to what the community has had for years in various guises on CodePlex, but I’ve a good feeling that sometime in the future, the community will finally be able to relax and get back to real work as official support will surpass even the best of the open source offerings (optimistic? we’ll see). Here’s the feature set for the current CTP:

(These are features added in v1.3 which were not available in v1.2 or v1.1)

· Support for developing and deploying on 64 bit (x64).

  • New menu commands within Visual Studio
    • Package - package the solution but does not deploy.
    • Retract - retracts and deletes the active solution from SharePoint.
    • Quick Deploy
      • Copy to 12 - copies template files, modules to the 12 Hive.
      • Copy Binarie(s) - deploys only the assemblies.
      • Copy Both
      • Recycle Application Pool
      • Attach to IIS Worker Processes
  • WSP View Improvements (Create New Feature)
    • Allows specify feature scope (Web, Site, Web Application, Farm)
    • Allows creation of a feature receiver
    • Option to automatically create elements.xml with new feature
    • Elements can be dragged from one feature to another
  • Build Commands Updates
    • Package command added to Visual Studio IDE and command line.
    • Retract command added to Visual Studio IDE and command line.

1) Command line operation is as follows:
devenv.exe <Project.csproj or Solution.sln and other usual flags> /deploy Debug
devenv.exe <Project.csproj or Solution.sln and other usual flags> /deploy Debug /package
devenv.exe <Project.csproj or Solution.sln and other usual flags> /deploy Debug /retract
Note that with the /package and /retract flags the /deploy flag is required.  

  • CTRL-F5 no longer launches the debugger. Use F5 to launch the debugger. CTRL-F5 will just launch the web browser and show the SharePoint site.
  • SharePoint Solution Generator Enhancements
    • Export from publishing sites. Note that round tripping of generated sites is not supported for anything but the simplest of sites. Instead some editing may be required.
  • Correct VB and C# Inconsistency Correction
    • VB VSeWSS projects no longer use VSeWSS as the root namespace
  • New Item Templates for RootFiles
    • Create a <RootFiles> item from template.
  • Web Part Item Template Improvements
    • Allows selection of deployment model to the global assembly cache (GAC) or to the \BIN directory. For /BIN deployment a simple Code Access Security permissions set is provided.
  • Easier Web Part Project Item Rename
    • Detect web part rename and modify dependent files
  • Support for deploying dependant assemblies added as a reference with CopyLocal=true.
    • VSeWSS now checks for any "CopyLocal=true" reference and incorporates this binary in the manifest.  The binary is added to the manifest with a DeploymentTarget matching the first binary found in the manifest, usually the project target binary.  Subsequent changes to "CopyLocal", or removing a binary altogether will correctly be cleaned up when rebuilding the manifest (either through WSP view or through a deployment command).  Changes to the DeploymentTarget can be made by editing the manifest.
  • Improved solution validation during full deployment
  • Validation Logging
    • Adding the element <add key="IfLog" value="true" /> to the <appSettings> node of your web.config will turn on validation logging. Logging occurs during a full deployment from a VSeWSS project and can be used for diagnostic purposes. Note: The log location will be noted on the build output window."
  • Conflict resolution dialog when deploying from within Visual Studio
    • Duplicate Features: If a feature with the same name as a feature you are trying to deploy is already on the server a dialog will give you the choice of uninstalling the existing feature first. If the feature was deployed via WSP the entire solution is retracted. If the feature was manually installed it will be deactivated then uninstalled.
    • Duplicate Site Definition Folders: Site definition folder is the same as 12/Templates/SiteTemplates or the WebTemp folder already exists. The option to delete the deployed folder is available.
    • RootFiles and Templates: If there is a RootFile or Template with the same name the option to delete the files first is given.
    • Solutions: If a solution with the same name exists on the server (possibly unique solution IDs) the option is given to retract and delete the existing solution.
  • The List Definition from Content Type template now allows for the creation of a List Definition Event Receiver.

Grab it from the Microsoft Download Center.

CTP3 ProxyCommand Tricks: Extending Get-Help to cover .NET Types and Members

UPDATED 2009-03-11: BUGFIXES FOR CTP3! PLEASE RE-DOWNLOAD v0.2.1

One of the things I hear now and then is that while PowerShell the language can be mastered with the requisite effort, the .NET namespace is considerable harder to get a handle on. Wouldn’t it be nice if you could type things like:

PS> get-help -object [string]::format

Since Functions take precedence over native Cmdlets in the command search order, the first thing you might think is to create a function with the same name, get-help, that can do this. The function itself would have the same parameters as the native Cmdlet, and add one of its own: -object. If the arguments do not belong to the “object” ParameterSet, then the function would splat (using the @ operator) the original arguments to the native Cmdlet transparently and transfer control. The user is none the wiser we had a quick peek at the arguments. So, before we run off duplicating a Cmdlet’s surface through script, first stop, Command Proxies!

Command Proxies

First off, required reading: Extending and/or Modifying Commands with Proxies. Ok, now that you’ve got read that entirely and gotten it  out of the way, you understand that the [ProxyCommand]::Create(…) method lets you automatically generate a function that delegates to a steppable pipeline wrapping the original Cmdlet, right? What I did then is to pack all of these functions into a psm1 module, create a nice psd1 module manifest and away we go.

The ObjectHelp Get-Help Extension Module

So here’s a quick look at the help for my module, in friendly, easy to read Cmdlet help style. The code for the PSM1 module file is way too large to dump here on my blog like I would usually do, so it’s available at as an attachment at the foot of the page. This help below is in a comment in the psm1 file itself.

    NAME
   
        ObjectHelp Extensions Module 0.2 for PowerShell 2.0 CTP3
    
    SYNOPSIS
   
         Get-Help -Object allows you to display usage and summary help for .NET Types and Members.
        
    DETAILED DESCRIPTION
   
        Get-Help -Object allows you to display usage and summary help for .NET Types and Members.
   
        If local documentation is not found and the object vendor is Microsoft, you will be directed
        to MSDN online to the correct page. If the vendor is not Microsoft and vendor information
        exists on the owning assembly, you will be prompted to search for information using Microsoft
        Live Search.
    
    TODO
    
         * localize strings into PSD1 file
         * Implement caching in hashtables. XMLDocuments are fat pigs.
         * Support getting property/field help
         * PowerTab integration
         * Test with Strict Parser
            
    EXAMPLES

        # get help on a type
        PS> get-help -obj [int]

        # get help against live instances
        PS> $obj = new-object system.xml.xmldocument
        PS> get-help -obj $obj

        or even:
       
        PS> get-help -obj 42
       
        # get help against methods
        PS> get-help -obj $obj.Load

        # explictly try msdn
        PS> get-help -obj [regex] -online

        # go to msdn for regex's members
        PS> get-help -obj [regex] -online -members

    CREDITS
   
        Author: Oisin Grehan (MVP)
        Blog  : http://www.nivot.org/
   
        Have fun! 

Usage Examples

So what does it actually look like when you call help? If the help is available locally, it is displayed inline in the console (or ISE). Help for all types in MSCorlib and System is pre-cached. Any other help for types in assemblies belonging to the BCL (Base Class Libraries – effectively the stock .NET assemblies) will be loaded on-demand. This typically takes just a few seconds and will cached for the rest of your session.

image

TODO and BUGS

Right now, it cannot deal with properties. That is to say, trying:

PS> get-help –object $s.length

where $s is a string, will get help on the property _type_, not the property itself. I have some ideas to get around this, so if you can wait for 0.3, I’d be happy. Of course you can wait. You have no choice. :D

Download

The two files come in a zip file. Unzip this file to ~\documents\windowspowershell\modules\objecthelp\ where ~ is your home directory. On vista/win7 this would be c:\users\username. On XP, it would be c:\documents and settings\username. To load it, just execute:

PS> import-module objecthelp

That’s it! Have fun!

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List