Microsoft & .NET Constructing your First PowerShell Provider

Constructing your First PowerShell Provider

Introduction


I think it’s safe to say that PowerShell will be a staple on the Microsoft platform for the foreseeable future. Version 2.0 is shipping standard with Windows 7 and Windows Server 2008 R2. Exchange leverages PowerShell, SQL Server 2008 leverages Powershell, and future server products will be leveraging PowerShell. If you’re not building a product’s administration features on top of PowerShell you soon will be.


There is, however, a PowerShell learning curve. As a developer you need to master two major PowerShell concepts. The first is familiarity with the PowerShell environment. The second is learning how to build CmdLets and Providers, the components that plug into PowerShell. Much has already been written about building CmdLets. So, in this article, I’m going to demonstrate some basic Provider construction.


Provider Overview


A complete introduction to PowerShell and PowerShell Providers is beyond the scope of this article. So I’ll focus on a few of the major PowerShell Provider concepts.


First, I think of a Provider as a storage place for an application’s data. A Provider plugs into PowerShell and through the standard or custom CmdLets navigates and modifies the application’s data. Typically a Provider is represented by a path which looks similar to the one you would see from the Windows command line. Most of the Microsoft Server products have some type of Provider.


Like CmdLets, Providers can emit .NET Objects. Because Providers and CmdLets are using the Common Language Runtime and the .NET Framework, objects from different providers can interact in new and interesting ways. PowerShell commands can be joined through a Pipeline, so the results of a particular command can feed the input to another command. When you consider the ecosystem of PowerShell providers already shipping with many Microsoft products and the extensibility provided by the .NET Framework; it shouldn’t be hard to justify PowerShell support for your product.


Sample Overview


I developed the sample in Visual Studio 2008 and targeted PowerShell 1.0.


Like any software application a Provider’s complexity depends on the problem it solves and how the provider solves the problem. The first step to building a provider is to understand how CmdLets interacting with the Provider are translated into function calls in your code. This sample arose from a need to study the Cmdlet to Provider to code interaction.


Aside from dishing up .NET objects, a Provider is responsible for validating the path information input by the user. The sample does some simple validation and emits Win32 debugging information you can view inside of the DebugView utility. Sources at the end of the article include DebugView information for downloading DebugView. The animation below shows the sample code in action.



Figure 1: PowerShell Sample in action

There are many styles of Providers. Styles are in a hierarchy of Provider functionality. The hierarchy appears below.



  • DriveCmdLetProvider
  • ItemCmdLetProvider
  • ContainerCmdLetProvider
  • NavigationCmdLetProvider


The lowest level of functionality is a Drive Provider and the highest is a Navigation Provider. I chose a Navigation Provider so I could explore all possible functionality. Since each level of Provider must supply all the functionally of the Provider a level up, I partitioned the sample code into regions so you can browse all the functions related to a particular Provider in the hierarchy. Later in the article I’ll refer to the regions.


Before looking at Navigation Provider code, I want to explain how I utilized DebugView.


Trace and DebugView


.NET Trace and Debug statements are great for troubleshooting real-time software. Basically, they’re useful anytime it’s not feasible to step through an application with the Debugger. I built the following class to handle writing Trace statements targeted for DebugView.


      public class TraceTest
{

public TraceTest(string area, string application)
{
_application = application;
_area = area;
}

public void WriteLine(string application, string area, string message)
{
if (TraceTest.Activated)
{
Trace.WriteLine(application + “:” + area + “:” + message);
}
}
public void WriteLine(string area, string message)
{
this.WriteLine(_application, area, message);
}
public void WriteLine(string message)
{
this.WriteLine(_application, _area, message);
}
}



Though PowerShell has its own Trace and Debug statements, I realized that I wouldn’t always have the foresight to activate PowerShell verbose mode. I also realized that what I display in PowerShell may be different than what I want to display when I’m debugging. In addition, I thought it would be faster to do unit testing from a Console Application and hook my code into PowerShell once I had my code debugged. Therefore, I wouldn’t always be running from within PowerShell. Finally, with DebugView filtering, I felt I had more selective control over what was displayed.


With some background on PowerShell Providers and some of the tools I used to debug my Provider, I’m ready to explain how the sample provider works.

Getting Started and Drive


Like all .NET development PowerShell development starts
with referencing a set of assemblies.
System.Management.Automation contains most of
what you’ll need for building PowerShell components.
System.Configuration.Install is another useful
assembly I’ll discuss later in the article.


In the sample, the Provider is implemented in the
TestPSProvider class. As I stated earlier, I’m
implementing a Navigation Provider so the base class
inherits from NavigationCmdLetProvider. Aside
from selecting a Provider base class, a Provider class
requires the CmdLetProvider attribute. Below
is the Provider class definition.




[CmdletProvider(“TestPSProvider”, ProviderCapabilities.None)]
public class TestPSProvider : NavigationCmdletProvider
{

PowerShell Provider state is stored in a Drive. Drives
typically inherit from PSDriveInfo. The sample
Drive class is called TestDriveInfo. The
TestDriveInfo class appears below.




internal class TestDriveInfo : PSDriveInfo
{
private TraceTest _trace = new TraceTest(“PS”);

public TraceTest TraceMessage
{
get { return _trace; }
}

public TestDriveInfo(PSDriveInfo driveInfo)
: base(driveInfo)
{
string somethingToSave = “”;
somethingToSave = “Something”;

_trace.WriteLine(“PSDriveInfo ” + driveInfo.Description + ” ” + driveInfo.Root + ” ” + driveInfo.Name + ” ” + driveInfo.Provider.Name + ” ” + driveInfo.Provider.Description);
this.SomethingToSave = somethingToSave;
}

public string SomethingToSave { get; protected set; }

}



Aside from storing state, the Drive can be considered the
root of a Path. Drive naming choices appear in PowerShell.
To create drives, a PowerShell Provider must override
InitializeDefaultDrives, the function appearing
below.




protected override System.Collections.ObjectModel.Collection<PSDRIVEINFO> InitializeDefaultDrives()
{
System.Collections.ObjectModel.Collection<PSDRIVEINFO> col = new System.Collections.ObjectModel.Collection<PSDRIVEINFO>();

TraceTestPSOut(“BEGIN InitializeDefaultDrives”);

PSDriveInfo info = new PSDriveInfo(“TestPS”, this.ProviderInfo, _pathSeparator, “Default Root”, PSCredential.Empty);

col.Add(NewDrive(info));

TraceTestPSOut(“END InitializeDefaultDrives”);

return col;
}


Most of the really interesting parts of a PowerShell
Provider relate to the “item” CmdLets. Item CmdLet
processing is handled in the sample code by the functions in
the Item and Container regions.


Item and Container


Run the “get-command” Cmdlet and you’ll
notice a lot of CmdLets ending in “-Item”, a few examples
are: New-Item, Copy-Item, and Clear-Item. Invoking these
CmdLets changes data in the Provider calling similarly named
functions among the Item and Container Regions in the Sample
application. An annotated view of the Item and Container
regions appears below.




protected override bool ItemExists(string path)

protected override void ClearItem(string path)

protected override void GetItem(string path)

protected override void SetItem(string path, object value)

protected override void CopyItem(string path, string copyPath, bool recurse)

protected override void GetChildItems(string path, bool recurse)



Earlier I mentioned the hierarchy of Providers. The Item
region corresponds with functions on the
ItemCmdLetProvider and the Container region
corresponds to the ContainerCmdLetProvider. The
remaining functionality is supplied by the Navigation region
functions. Before I cover Navigation I want to briefly
touch on Dynamic Parameters.

Navigation and Dynamic Parameters


One interesting PowerShell Provider feature is Dynamic
Parameters. If you peruse the
NavigationCmdLetProvider object in the object
model viewer, you’ll notice a
DynamicParameter” function for almost every
standard override function. DynamicParameters
allow a developer to extend the number of parameters for the
standard CmdLets. For example, normally if you want to
invoke the New-Item Cmdlet the data is supplied in the –
value parameter separated by commas like in the example
below.




New-Item -value one, two, three


DynamicParameters allow a developer to create named
parameters rather than a series of values separated by
commas. So, you can invoke the New-Item CmdLet on the sample
with the following PowerShell code.




New-Item -MyParameter1 one -MyParameter2 two


Below is the NewItemDynamicParamer function
followed by the NewItem function.




protected override void NewItem(string path, string itemTypeName, object newItemValue)
{
TraceTestPSOut(“BEGIN NewItem”);

if (newItemValue == null)
{
TraceTestPSOut(“newItemValue is NULL”);
}
else
{
TraceTestPSOut(path + ” itemTypeName ” + itemTypeName + ” value ” + newItemValue.ToString());

object[] parms = (object[])newItemValue;

foreach (object obj in parms)
{
TraceTestPSOut(“Obj ” + obj.GetType().ToString() + ” ” + obj.ToString());
}
}

if (this.DynamicParameters == null)
{
TraceTestPSOut(“DynamicParameters == NULL”);
}
else
{
foreach (RuntimeDefinedParameter objParm in ((RuntimeDefinedParameterDictionary)this.DynamicParameters).Values)
{
TraceTestPSOut(” RunTimeDefinedParm ” + objParm.Name + ” ” + objParm.Value.ToString());
}
}

TraceTestPSOut(“END NewItem”);
}

protected override object NewItemDynamicParameters(string path, string itemTypeName, object newItemValue)
{
TraceTestPSOut(“BEGIN NewItemDynamicParameters”);

RuntimeDefinedParameterDictionary dic = new RuntimeDefinedParameterDictionary();
ParameterAttribute attrib = null;
Collection<ATTRIBUTE> col = null;
RuntimeDefinedParameter runDefParm = null;

attrib = new ParameterAttribute();
attrib.ParameterSetName = “MyParameters”;
attrib.Mandatory = false;
attrib.ValueFromPipeline = false;
col = new Collection<ATTRIBUTE>();
col.Add(attrib);

runDefParm = new RuntimeDefinedParameter(“MyParameter1”, typeof(string),
col);
dic.Add(“MyParameter1”, runDefParm);

runDefParm = new RuntimeDefinedParameter(“MyParameter2”, typeof(string),
col);
dic.Add(“MyParameter2”, runDefParm);

TraceTestPSOut(“END NewItemDynamicParameters”);

return dic;
}



Because I chose to accept how PowerShell handles general
path validation in a Provider, there was not much to I
needed to override in the Navigation region of the
sample.


That covers all the Provider functions. There is some
needed code though to get the sample to work in
PowerShell.


Attributes and Installation


To load your Provider you need a PSSnapIn class. Here is
the TestPSProviderPSSnapIn class from the sample.




[RunInstaller(true)]
public class TestPSProviderPSSnapIn : PSSnapIn
{
/// <SUMMARY>
/// Create an instance of the TestPSProviderPSSnapIn
/// </SUMMARY>
public TestPSProviderPSSnapIn()
: base()
{
}


Other than providing some naming properties there is not
much to change in this class. Note however, the
Installer Attribute and the
System.Configuration.Install assembly reference
is necessary to enable InstallUtil.exe
functionality.


The PowerShell code below loads the SnapIn
and installs the Provider. It’s been configured to run the
64 bit version of InstallUtil.




$path = Resolve-Path “C:articles and presentationsPowerShellPowerShellTesting.PowerShell.BasicsbinDebugTesting.PowerShell.Basics.dll”
$register = “$env:windir/Microsoft.NET/Framework64/v2.0.50727/installutil”
& $register $path
add-PSSnapin TestPSProviderPSSnapIn
set-location TestPS:Container


Conclusion


A PowerShell Provider is a lot like the database of a
PowerShell solution. Standard CmdLets act on the Provider,
adding, removing, and reading data stored in the Provider.
To understand how to build a Provider, a developer must
understand how, when, and where .NET Provider code gets
executed. This article and the accompanying sample code
demonstrates .NET code execution in a Navigation
Provider.


Resources



Designing Your Windows PowerShell Provider

How to Create a Windows PowerShell Provider

Windows PowerShell Blog

Windows PowerShell Getting Started Guide
DebugView

Latest Posts

Related Stories