Now that the drive object of the ASP.NET Membership PowerShell provider is fully functional, it's time to extend the PowerShell provider to fetch users from the membership store.
The PowerShell cmdlet used to retrieve one or more items from a provider is get-item, or gi for you alias monkeys out there. get-item works against any provider that support item retrieval; e.g., it works for files and folders:
> get-item c:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 3/12/2009 2:23 PM <DIR> temp
It works for environment variables:
> get-item env:PROGRAMFILES
ProgramFiles C:\Program Files
It works for certificates:
> get-item cert:/CurrentUser/My/E0ADE6F1340FDF59B63452D067B91FFFA09A621F
E0ADE6F1340FDF59B63452D067B91FFFA09A621F Eemail@example.com, CN=127.0.0.1, OU=sec, O=ptek, L=here, S=nc, C=US
So, how does PowerShell know which provider to invoke when it processes a get-item cmdlet?
The item's full
path contains all of information necessary to locate the appropriate
provider. In the examples above, the drive portion of
the path indicates which provider should be used to process
the item request; you can see the mapping of drives to providers using the get-psdrive cmdlet:
Name Provider Root
---- -------- ----
C FileSystem C:\
cert Certificate \
Gac AssemblyCache Gac
HKCU Registry HKEY_CURRENT_USER
HKLM Registry HKEY_LOCAL_MACHINE
Each PowerShell drive is directly associated with a provider type:
the C: drive maps to the FileSystem provider, the cert: drive to the
Certificate provider, and the env: drive to the Environment provider.
PowerShell recognizes several forms of path syntax, all of them designed to allow for provider discovery; providers are expected to support these path formats as appropriate:
- Drive-qualified: this is equivalent to a fully-qualified file path. The drive is explicitly specified at the start of the path, as in "c:/temp/junk.txt" or "cert:/localmachine".
- Provider-direct: starts with "\\" (or "//"); the provider for the current location (i.e., the value of $pwd) is assumed to be the provider of the path. This syntax is often used to identify resources on other machines, such as UNC paths or remote registry hives.
- Provider-qualified: the provider name is prepended to the
drive-qualified or provider-direct item path, delimited by '::'. E.g., "FileSystem::c:/temp/junk.txt", or
"FileSystem::\\share\temp\junk.txt". This format is used when
the appropriate provider must be explicity stated.
- Provider-internal: this is the portion of the provider-qualified path following the '::' delimiter.
Of the four supported path formats, the ASP.NET Membership PowerShell provider will support these three:
- Drive-qualified: users:/username
- Provider-qualified: ASPNETMembership::users:/username
- Provider-internal: this is idential to the drive-qualified path syntax
Provider-direct paths and UNC-style provider-internal paths will not be supported by the ASP.NET Membership PowerShell provider.
Knowing the path formats to expect, it's time to implement support for the get-item cmdlet.
Enabling item cmdlet support for the ASP.NET Membership PowerShell provider begins with deriving the provider from ContainerCmdletProvider:
[CmdletProvider( "ASPNETMembership", ProviderCapabilities.None )]
public class Provider : ContainerCmdletProvider
Deriving from ContainerCmdletProvider adds many item-related methods to the provider. To enabling the get-item cmdlet, at least two of these methods must be overridden.
The first required override is the GetItem method, which is called to process a get-item invocation at runtime:
protected override void GetItem( string path )
var user = GetUserFromPath(path);
if( null != user )
WriteItemObject( user, path, false );
The GetItem override delegates almost all of the work to the GetUserFromPath utility method; if GetUserFromPath returns a valid object reference, it is written back to the
current pipeline using the WriteItemObject method of the provider
object's base (line 6).
GetUserFromPath uses the provider's custom drive object to access the ASP.NET Membership provider. The drive object for the path is available in the PSDriveInfo property; PowerShell conveniently sets this value to the appropriate DriveInfo
object for the item's path before calling GetItem:
MembershipUser GetUserFromPath( string path )
var drive = this.PSDriveInfo as MembershipDriveInfo;
var username = ExtractUserNameFromPath( path );
return drive.MembershipProvider.GetUser( username, false );
static string ExtractUserNameFromPath( string path )
if( String.IsNullOrEmpty( path ) )
// this regex matches all supported powershell path syntaxes:
// drive-qualified - users:/username
// provider-qualified - membership::users:/username
// provider-internal - users:/username
var match = Regex.Match( path, @"(?:membership::)?(?:\w+:[\\/])?(?<username>[-a-z0-9_]*)$", RegexOptions.IgnoreCase );
if( match.Success )
return match.Groups[ "username" ].Value;
The custom drive object exposes the ASP.NET Membership Provider, which offers a GetUser method that returns the MembershipUser object for a valid username (line 5). The username is extracted from the path string using a simple regular expression that matches the three path formats supported by the PowerShell provider (line 17).
The second required override is the ItemExists method, which is called by PowerShell to determine if the provider contains an item at a specified path (e.g., by the test-path cmdlet).
PowerShell calls ItemExists before the GetItem method when processing get-item; if ItemExists returns false, GetItem is not called and a "cannot find path" error is reported on the pipeline. The ASP.NET Membership provider reuses the GetUserFromPath utility method to ascertain whether the path contains a valid username:
protected override bool ItemExists( string path )
return null != GetUserFromPath( path );
With these two overrides and their supporting utility methods, our provider can support the get-item cmdlet.
Build and run; in the PowerShell console, create the users drive as follows:
> new-psdrive -psp aspnetmembership -root "" -name users -server localhost -catalog awesomewebsitedb;
Name Provider Root CurrentLocation
---- -------- ---- ---------------
Once the drive is created, you can use get-item to fetch MembershipUser objects from the ASP.NET Membership user store:
> get-item users:/testuser
PSPath : ASPNETMembership::testuser
PSDrive : users
PSProvider : ASPNETMembership
PSIsContainer : False
UserName : testuser
ProviderUserKey : 09a9c356-a400-4cff-825d-231207946c94
Email : firstname.lastname@example.org
PasswordQuestion : what is your favorite color?
IsApproved : True
IsLockedOut : False
LastLockoutDate : 12/31/1753 7:00:00 PM
CreationDate : 6/11/2009 12:59:45 PM
LastLoginDate : 6/11/2009 12:59:45 PM
LastActivityDate : 6/11/2009 12:59:45 PM
LastPasswordChangedDate : 6/11/2009 12:59:45 PM
IsOnline : False
ProviderName : AspNetSqlMembershipProvider
At this point, a whole new world of complexity is available from our provider:
> ( get-item users:/testuser ).ResetPassword( 'asdf1234' )
We can also leverage some of the built-in goodies of PowerShell against our ASP.NET Membership store in a natural way:
$u = get-item users:/testuser;
if( $u.IsLockedOut )
Discovery is a big part of PowerShell, and in the post I'll extend the ASP.Net Membership PowerShell provider to support the get-childitem (alias dir or ls) cmdlet, to enable listing of all users in the store. I'll also add support for the set-location (alias cd) cmdlet, which will allow operators to set the shell's current location to our custom users drive.
The code for this post is available here:
ASPNETMembership_GetItem.zip (5.55 kb)
As always, thanks for reading, and if you liked this post, please Kick
It, Shout It, trackback, tweet it, and comment using the clicky