Creating a PowerShell Provider pt 0: Gearing Up

§ May 26, 2009 01:11 by beefarino |

Last month I gave a talk at ALT.NET Charlotte on using PowerShell as a platform for support tools.  The majority of the talk revolved around the extensible provider layer available in PowerShell, and how this enables support engineers to accomplish many things by learning a few simple commands.  Creating providers isn't hard, but there is a bit of "black art" to it.  Based on the lack of adequate examples and documentation available, I thought I'd fill the void with a few posts derived from my talk.

In these posts, I will be creating a PowerShell provider around the ASP.NET Membership framework that enables the management of user accounts (for more information on ASP.NET Membership, start with this post from Scott Gu's blog) .  Basically, this provider will allow you to treat your ASP.NET Membership user repository like a file system, allowing you to perform heroic feats that are simply impossible in the canned ASP.NET Web Site Management Tool. 

Like what you ask?   Consider this:

dir users: | 
   where { !( $_.isApproved ) -and $_.creationDate.Date -eq ( ( get-date ).Date ) } 

which lists all unapproved users created today.  Or this:

dir users: | where{ $_.isLockedOut } | foreach{ $_.unlockUser() } 

which unlocks all locked users.  Or this:

dir users: | 
   where { ( (get-date) - $_.lastLoginDate ).TotalDays -gt 365 } |
   remove-item 

which removes users who haven't authenticated in the past year.  These are things you simply can't do using the clumsy built-in tools.  With a simple PowerShell provider around the user store, you can accommodate these situations and many others you don't even know you need yet.

This first post is aimed at getting a project building, with the appropriate references in place, and with the debugger invoking powershell properly.  The next few posts will focus on implementing the proper extensibility points to have PowerShell interact with your provider.  Future posts will focus on implementing specific features of the provider, such as listing, adding, or removing users.  Eventually I will discuss packaging the provider for distribution and using the provider in freely available tools, such as PowerGUI.

Prerequisites

You'll need the latest PowerShell v2 CTP; at the time of this writing, the latest was CTP3 and it was available here.  Since we're working with a CTP, you should expect some things to change before the RTM; however, the PowerShell provider layer is unchanged from v1.0 in most respects.  The only major difference is with regards to deployment and installation, which I'll discuss at the appropriate time.

If you are currently using PowerShell v1.0, you will need to uninstall it.  Instructions are available here and here.

The .NET Framework 3.5, although not required to run PowerShell v2 CTP3, is required for some of the tools that accompany the v2 release.

Finally, I suggest you fire up PowerShell on its own at least once, to verify it installed correctly and to loosen up the script execution policy.  To save some grief, one of the first commands I execute in a new PowerShell install is this one:

set-executionPolicy RemoteSigned

This will enable the execution of locally-created script files, but require downloaded script files be signed by a trusted authority before allowing their execution.

Setting Up the Project

Create a new Class Library project in Visual Studio named "ASPNETMembership".  This project will eventually contain the PowerShell provider code.

You will need to add the following references to the project:

  • System.Web - this assembly contains the MembershipUser type we will be exposing in our PowerShell provider;
  • System.Configuration - we'll need to access the configuration subsystem to properly set up our membership provider;
  • System.Management.Automation - this contains the PowerShell type definitions we need to create our provider.  You may have to hunt for this assembly; try here: C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0.

Now that the necessary references are configured, it's time to configure debugging appropriately. 

Open the project properties dialog and select the Debug tab.  Under Start Action, select the "Start external program" option.  In the "Start external program" textbox, enter the fully qualified path to the PowerShell executable: c:\windows\system32\windowspowershell\v1.0\powershell.exe.

Yes, even though you're running PowerShell v2, it still lives in the v1.0 directory.

In the "Command line arguments textbox, enter the following:

-noexit -command "[reflection.assembly]::loadFrom( 'ASPNETMembership.dll' ) | import-module"

The -command argument instructs PowerShell to execute the supplied command pipe string as if it were typed in at the console.  It may not be obvious what the command pipe string is doing.  In a nutshell, the first command in the pipe loads the ASPNETMembership project output dll into the AppDomain.  The second command in the pipe causes PowerShell to load any cmdlets or providers implemented in the dll.  I'll touch on the import-module command more in a future post.

The -noexit argument prevents PowerShell from exiting once the command has been run, which enables us to type in commands and interact with the console while debugging.

Test the Project Setup

Build and Run the empty project.  A PowerShell console should launch.

In the console, run the following command, which lists all assemblies loaded in the currently executing AppDomain whose FullName property matches 'aspnet':

[appdomain]::currentDomain.GetAssemblies() | 
   where { $_.FullName -match 'aspnet' } | 
   select fullname

Verify that the ASPNETMembership assembly is listed in the output of this command:

 

If it isn't, double-check the command-line arguments specified in the project properties -> debug tab.  Specifically, ensure that:

  • the command arguments are entered exactly as specified above, and
  • the working directory is set to the default (empty) value

That's it for now - next post we will begin implementing the MembershipUsers PowerShell provider and enable the most basic pieces necessary to list the users in the membership store.



Expanding-File Resource for Spring.NET

§ May 13, 2009 03:48 by beefarino |

I just had a fun emergency - the crew decided to redirect all configuration sources using environment variables, and while Log4Net supports this quite easily, the spring.net context resource-from-a-URI abstraction does not.  E.g., this will not work:

<spring>
    <context>
            <resource uri="file://%PROGRAMFILES%/myapp/ioc.config" />
    </context>
    ...

Knowing that spring.net can leverage custom resource implementations, I set out to extend the file system resource to expand environment variables.  Turns out to be easy-peasy-lemon-squeezie:

public class ExpandableFileSystemResource : Spring.Core.IO.FileSystemResource
{
    public ExpandableFileSystemResource()
        : base()
    {
    }
    public ExpandableFileSystemResource( string resourceName )
        : base( Environment.ExpandEnvironmentVariables( resourceName ?? String.Empty ) )
    {
    }
    public ExpandableFileSystemResource( string resourceName, bool suppressInitialize )
        : base( Environment.ExpandEnvironmentVariables( resourceName ?? String.Empty ), suppressInitialize )
    {
    }
}

Register the resource implementation via config:

<spring>
    <resourceHandlers>
      <handler protocol="filex" type="MyApp.ExpandableFileSystemResource, MyApp"/>
    </resourceHandlers>
    <context>
            <resource uri="filex://%PROGRAMFILES%/myapp/ioc.config" />
    </context>    
    ... 

 And it just works.  Enjoy!



ALT.NET Charlotte Presentation

§ May 7, 2009 14:16 by beefarino |

I just got back from the second meeting of ALT.NET Charlotte, where I presented a talk on PowerShell as a Tools Platform.  I think it went well.  The demos centered around building tools in PowerShell to manage the ASP.NET Membership store for a website, and they seemed to drive the point home.  Try doing this:

dir users: | where { $_.lastLoginDate –lt ( ( get-date ).addyear( -1 ) ) } | remove-item;

in the ASP.NET Website Management tool!

As well as I feel it went, I'm still hoping to get some feedback from the group.  Feel free to leave a comment here if you wish, or use the contact link at the top of this page.

And a very special thank you to those organizations and companies that provided their support for this presentation:

and those who sposor our kick-ass group:

 Thanks to everyone who came out to support the group and hear my talk!