Some time ago I published an example of how to create a PowerShell cmdlet on my blog, "Windows PowerShell - Make your application manageable - Write your first cmdlet". As a reader pointed out, the creation of a custom shell is no longer required. At the time of writing the .NET Magazine article (Dutch), the PowerShell software hadn't reached the RC stage and I hadn't much time to mess around with the SnapIn-methodology, so it wasn't incorporated in the original article (we were happy we could get the MSH to PowerShell rename in the published article in time, as well as doing the required incompatibilies spotting to have the original code compile on the RC bits).
Today, I'm going to point out how to modify the code from the "Windows PowerShell - Make your application manageable - Write your first cmdlet" post (with the FindString cmdlet) to a SnapIn that can be registered in PSH shells on your machine, without the need for creating a new custom shell using make-shell. For the original post, see the link above. Below you'll find the stripped-down and updated version of this post using a snap-in approach.
Your first cmdlet (snap-in)
Step 1 - Create a Visual Studio project
To kick off, create a new class library project (e.g. in C#) and give it a meaningful name, for example FindString. Next, add a reference to the System.Management.Automation.dll assembly which you can find in %programfiles%\Windows PowerShell\v1.0. Include a reference to System.Configuration.Install as well.
Step 2 - Declare the cmdlet class
Next, it's time to declare our cmdlet-acting class as follows (updated using VerbsCommon, thx to Lee for his comment):
using
System.Management.Automation;
[Cmdlet(VerbsCommon.Get, "Matches")]
public class FindString : PSCmdlet
{
}
The class has to inherit from PSCmdlet and has to be annotated with the CmdletAttribute attribute as you can see.
Step 3 - Parameterize the cmdlet
private string pattern;
[Parameter(Mandatory=true, Position=0)]
public string Pattern
{
get { return pattern; }
set { pattern = value; }
}
private string[] fullName;
[Parameter(Mandatory=true, Position=1, ValueFromPipelineByPropertyName=true)]
public string[] FullName
{
get { return fullName; }
set { fullName = value; }
}
The first parameter is called Pattern and will contain the regular expression pattern we'll look for. The second one is called FullName and can contain more than one filename, which can be fed in through the pipeline by the property name. That means that cmdlets that return objects with a public property called FullName can be linked to this cmdlet by means of piping (|), e.g. from the get-childitem cmdlet.
Step 4 - Implement the core functionality
The core of the cmdlet's implementation goes in the ProcessRecord method. The following is a pretty short implementation of our matching logic relying on System.Text.RegularExpressions and System.IO to read files, and System.Collections.Generic for the List class:
protected override void ProcessRecord()
{
List<Match> matches = new List<Match>();
ProviderInfo info;
foreach (string item in FullName)
foreach (string file in GetResolvedProviderPathFromPSPath(item, out info))
using (StreamReader sr = File.OpenText(file))
matches.Add(new Match(file, Regex.Matches(sr.ReadToEnd(), pattern)));
WriteObject(matches);
}
The call to WriteObject returns the result to the pipeline for further processing; other cmdlets could use our output as their input, or the user can just feed this in to cmdlets like format-list (or work with variables, etc). To aid our implementation, we've created a simple Match class:
public
class Match
{
public Match(string file, MatchCollection matches)
{
this.file = file;
this.matches = matches;
}
private MatchCollection matches;
public MatchCollection Matches
{
get { return matches; }
set { matches = value; }
}
private string file;
public string File
{
get { return file; }
set { file = value; }
}
}
Step 5 - Create a snap-in and deploy it
Basically, a snap-in is a wrapper around one or more cmdlets that can be "imported" in a PowerShell instance. Instead of creating a separate shell (using make-shell) one can rely on snap-ins to bring custom cmdlets to Windows PowerShell.
The core of the snap-in methodology lies in the CustomPSSnapIn class from the System.Management.Automation namespace. This class inherits from System.ComponentModel.Component and thus it can be used in combination with instalers in .NET (using installutil.exe). That's how snap-ins work. In order to bring this technology to our own cmdlet-implementation, we have to do the following (with using directives for the whole codefile):
using
System;
using System.ComponentModel;
using System.IO;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Text.RegularExpressions;
#region Snap-in
[RunInstaller(true)]
public class FindStringPSSnapIn : CustomPSSnapIn
{
private Collection<CmdletConfigurationEntry> cmdlets = new Collection<CmdletConfigurationEntry>();
private Collection<ProviderConfigurationEntry> providers = new Collection<ProviderConfigurationEntry>();
private Collection<TypeConfigurationEntry> types = new Collection<TypeConfigurationEntry>();
private Collection<FormatConfigurationEntry> formats = new Collection<FormatConfigurationEntry>();
public FindStringPSSnapIn() : base()
{
cmdlets.Add(new CmdletConfigurationEntry("get-matches", typeof(FindString), null));
}
public override string Name
{
get { return "FindStringPSSnapIn"; }
}
public override string Vendor
{
get { return "BdsSoft"; }
}
public override string Description
{
get { return "This snap-in contains the FindString cmdlet."; }
}
public override Collection<CmdletConfigurationEntry> Cmdlets
{
get { return cmdlets; }
}
public override Collection<ProviderConfigurationEntry> Providers
{
get { return providers; }
}
public override Collection<TypeConfigurationEntry> Types
{
get { return types; }
}
public override Collection<FormatConfigurationEntry> Formats
{
get { return formats; }
}
}
#endregion
This might look quite some code at first glance, but it's really straightforward. All that you have to do over here is override from CustomPSSnapIn and provide code that returns a vendor, a name, a description and a few collections through properties. Those collections hold references to cmdlets, providers, types and format that are included in the snapin. In this case, we only include one single cmdlet which is done by preparing a Collection (from System.Collections.ObjectModel) of CmdletConfigurationEntry (from System.Management.Automation.Runspaces) instances. In our case on single entry is enough, holding the name (get-matches) and type info (typeof(FindString)). The last constructor parameter isn't used as we don't have a help file associated with the cmdlet.
Compile the whole stuff and open a Visual Studio 2005 Command Prompt (or a PS prompt if you're ambitious) and run installutil.exe against the compiled .dll file (note: on Vista, you'll have to evelate the Visual Studoo 2005 Command Prompt to run with administrative credentials in order to allow installutil.exe to write to the HKLM registry hive):
C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug>installutil.exe FindString.dll
Microsoft (R) .NET Framework Installation utility Version 2.0.50727.88
Copyright (c) Microsoft Corporation. All rights reserved.
Running a transacted installation.
Beginning the Install phase of the installation.
See the contents of the log file for the C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.dll assembly's progress.
The file is located at C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.InstallLog.
Installing assembly 'C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.dll'.
Affected parameters are:
logtoconsole =
assemblypath = C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.dll
logfile = C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.InstallLog
The Install phase completed successfully, and the Commit phase is beginning.
See the contents of the log file for the C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.dll assembly's progress.
The file is located at C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.InstallLog.
Committing assembly 'C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.dll'.
Affected parameters are:
logtoconsole =
assemblypath = C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.dll
logfile = C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug\FindString.InstallLog
The Commit phase completed successfully.
The transacted install has completed.
C:\Users\Bart\Documents\Visual Studio 2005\Projects\FindString\FindString\bin\Debug>
Step 6 - Testing time
Launch the Windows PowerShell and display a list with registered snap-ins as follows:
PS C:\Users\Bart\Documents> get-pssnapin -registered
Name : FindStringPSSnapIn
PSVersion : 1.0
Description : This snap-in contains the FindString cmdlet.
As you can see, the snap-in was registered successfully. However, get-matches still doesn't work:
PS C:\Users\Bart\Documents> get-matches
'get-matches' is not recognized as a cmdlet, function, operable program, or script file.
At line:1 char:11
+ get-matches <<<<
To make it available, you have to call add-pssnapin, as displayed below:
PS C:\Users\Bart\Documents> add-pssnapin FindStringPSSnapIn
Now get-matches should work:
PS C:\Program Files\Windows PowerShell\v1.0> get-matches Wmi about_Alias.help.txt
Matches File
------- ----
{Wmi, Wmi, Wmi} C:\Program Files\Windows PowerShell\...
Step 7 - Taking it one step further
Performing add-pssnapin every time again doesn't boost your productivity. In order to provide easier access to your shell configuration you can export the settings using export-console:
PS C:\Users\Bart> export-console MyShell
This creates a file called MyShell.psc1 that contains the following piece of XML:
<?xml version="1.0" encoding="utf-8"?>
<PSConsoleFile ConsoleSchemaVersion="1.0">
<PSVersion>1.0</PSVersion>
<PSSnapIns>
<PSSnapIn Name="FindStringPSSnapIn" />
</PSSnapIns>
</PSConsoleFile>
You can now open your customized shell using powershell.exe by specifying the psc1-file as a parameter (or create a shortcut of course):
powershell -PSConsoleFile c:\users\bart\myshell.psc1
Have fun!
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks