Friday, August 1, 2014

Getting Started with PowerShell Modules, Part 1

Modules

Modules are the key to organizing you PowerShell code in a logical way. There are two ways to write modules:
  • .NET assemblies
  • PowerShell advanced functions
Which you use depends on what components you are interacting with and how easily you want your end users to be able to modify the finished product.

Creating a .NET PowerShell Module

I really have to commend Microsoft one this. Creating a new .NET cmdlet module is trivial. Contrary to the convoluted hoops you have to jump through to do something things in .NET it is a refreshing change.
Here are the steps:
  1. Create a new Class Library in your favourite language.
  2. Target the correct .NET Framework version for the PowerShell version you want to support.
  3. Reference the System.Management.Automation assembly.
  4. Derive a class from Cmdlet or PSCmdlet and decorate it with a Cmdlet attribute.
Here is a table showing how the framework versions correspond to the PowerShell versions:
PowerShell v2PowerShell v3PowerShell v4
.NET 3.5.NET 4.0.NET 4.5
If you are not sure what version of PowerShell you are running, you can find out by running the following command:
PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
3      0      -1     -1

Details

Step 1: Create the Project
Select File->New->Project… from the Visual Studio menu.
New Project

Step 2: Target the Framework
Right-click the project and select Properties. Select the appropriate framework version from the Target framework dropdown.
Target Framework

Step 3: Add the Assembly Reference
Right-click the project references. Select Add Reference and browse to
C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\

Then browse into the appropriate subfolder for your version. For me that was 3.0. Select System.Management.Automation.dll and click the Add button.
Assembly Reference
Step 4: Create an Derived PSCmdlet Class
The basic skeleton follows:
using System;
using System.Management.Automation;

namespace Namespace
{
    [Cmdlet(VerbsCommon.Get, "Something")]
    public class GetSomething : PSCmdlet
    {
        [Parameter(
            Mandatory = true,
            Position = 0        
            )]
        public string Name { get; set; }

        protected override void BeginProcessing()
        {
            base.BeginProcessing();
        }

        protected override void ProcessRecord()
        {
            base.ProcessRecord()
        }

        protected override void EndProcessing()
        {
            base.EndProcessing();
        }
    }
}
That is it. The BeginProcessing and EndProcessing are not required. So a really simple cmdlet would like this:
using System;
using System.Management.Automation;

namespace Namespace
{
    [Cmdlet(VerbsCommon.Get, "Something")]
    public class GetSomething : PSCmdlet
    {
        [Parameter(
            Mandatory = true,
            Position = 0        
            )]
        public string Name { get; set; }

        protected override void ProcessRecord()
        {
            WriteObject(Name);
        }
    }
}
Once you compile your project into an assembly, you have created your first PowerShell module. To use the module, you need to import it.

Importing .NET Modules

Modules can be imported in the same three ways that scripts can be run, relative reference, absolute reference and by placing it in your path. However, the third is slightly different for modules than scripts.

Relative Reference
PS C:\SourceCode> Import-Module MyNewProject\bin\Debug\MyNewProject.dll
Absolute Reference
PS C:\SourceCode> Import-Module C:SourceCode\MyNewProject\bin\Debug\MyNewProject.dll
Installing it in your Module Path
First, verify your module path by running the following:
PS C:\> $env:PSModulePath
C:\Users\swoogan\Documents\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
The first path is specific to your logged in user. The second is a system wide path available to all users. Any additional paths may have been installed by third parties.

This leads to two possible options for you to install your module. You can either put it in a directory already present in your PSModulePath, or you can create a new location and add it to your path. I recommend using what is already there.

Now, note that there is a little trick to installing modules this way. You need to create a folder in the module path with the same name as your assembly. For example, if you assembly is MyNewProject.dll, then you need to install it into a subfolder called MyNewProject. Here is an example of performing the installation:
PS C:\> $path = Join-Path -Path $env:PSModulePath.Split(';')[0] -ChildPath MyNewProject
PS C:\> mkdir $path


    Directory: C:\Users\csvingen\Documents\WindowsPowerShell\Modules


Mode        LastWriteTime      Length  Name
----        -------------      ------  ----
d----       8/1/2014   1:24 PM         MyNewProject

PS C:\> Copy-Item .\SourceCode\MyNewProject\bin\Debug\* $path -Verbose
VERBOSE: Performing operation "Copy File" on Target "Item: C:\SourceCode\MyNewProject\bin\Debug\MyNewProject.dll
Destination: C:\Users\swoogan\Documents\WindowsPowerShell\Modules\MyNewProject\MyNewProject.dll".

To make sure that you have installed your module correctly, run the following:
PS C:\> Get-Module -ListAvailable

    Directory: C:\Users\csvingen\Documents\WindowsPowerShell\Modules


ModuleType Name                                ExportedCommands
---------- ----                                ----------------
Binary     MyNewProject                        {Get-Something}
Script     Swoogan                             {Find-InFiles, grep}

...

Notice that the type of MyNewProject is binary, indicating it is a .NET module. If you do not see your module, you know something has gone wrong with the installation.

Now it is much easier to import your cmdlets. You no longer need to figure out the relative path or keep track of a potentially long absoulte path. You can simply use the name of the module’s folder. When first implementing, I like to import the module with the Verbose flag to ensure that I get what I am expecting:
PS C:\Users\csvingen> Import-Module MyNewProject -Verbose
VERBOSE: Importing cmdlet 'Get-Something'.

Testing and Debugging

There are two important thing to be aware of when it comes to debugging your compiled modules. The first is that re-importing the module into your existing session will not bring in changes that you have made. You need to remove the modules first:
Remove-Module MyNewProject
Second, you will not be able to build your module in Visual Studio once you have imported the module. PowerShell will maintain a lock on the file until you unload your session.

I like to write a Test.ps1 script, that I load in the PowerShell ISE, for testings:
Import-Module .\bin\Debug\MyNewProject.dll
Get-Something
Remove-Module MyNewProject
Notice that the import uses the relative path in this case, since installing it is reserved for a completed project. Also note that you import with a path, but remove with just the module name.


Since this post has become a lot longer that I was expecting, I am going to split it in two and demonstrate Advanced Functions in the next installment.

No comments:

Post a Comment