Thursday, December 15, 2016

Create your own PowerShell Modules

If you are coding in PowerShell, most likely you have been using modules.  If you are using the Netapp Ontap PowerShell toolkit, you certainly know the line "Import-Module DataOntap".

So you can probably guess that importing a module is just loading someone else's code.  A module, when professionally written is most likely "compiled" code.  Code written C# or another .NET language and compiled into a DLL.  In Visual Studio you can create PowerShell projects.  Does that mean, if you want you own module, you need to start learning Visual Studio and C# ?  Hell no, you can create your own module in matter of minutes with nothing more that just plain old PowerShell.
This isn't going to be a PowerShell course, but I will need to teach you some of PowerShell's best practices if you want to create .  One of them is writing a little help for your own powershell cmdlets.  The other is add cmdlet-binding.

Use PowerShell ISE

If you are coding in PowerShell, you should at least use a proper PowerShell Editor, there are many out there, powerful ones too and expensive ones.  But Windows comes with a default one, called PowerShell ISE.  So start that one.  The screen can be divided in 3 parts.
  • The Shell (where your code is run)
  • The Script Pane (the editor)
  • The Command Add-on (where you can search modules & cmdlets)
In the View menu you can enable/disable these.  Usually I just use the script & shell.  BTW, using CTRL-R you can toggle the script pane.

Your script

Let's use a simple script for this example.  It will write a text in color and underline it.

Function Underline{
    Param(
        [string]$Text,
        [string]$Color="Cyan"
    )
    Begin{}
    Process{
        if($Text){
            Write-Host "`n$Text" -ForegroundColor $Color 
            Write-Host "".PadRight($Text.Length,"-")
        }
    }
}

Now we want this to be a module so we can use it everywhere we want.

Use ISE Snippet

The first thing I always do when I write a module is making sure my function has all the required elements.
  • Help text
  • Cmdlet binding
  • Pipeline input (optional)
No I don't know these things by hart.  I use the ISE Snippets to help me.  If you rightclick in the editor pan of the ISE window, right click and choose "snippets" (or just press ctrl-J).  Then choose "cmdlet (advanced function)".  It will spit out something like this

<#
.Synopsis
   Short description
.DESCRIPTION
   Long description
.EXAMPLE
   Example of how to use this cmdlet
.EXAMPLE
   Another example of how to use this cmdlet
#>
function Verb-Noun
{
    [CmdletBinding()]
    [Alias()]
    [OutputType([int])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        $Param1,

        # Param2 help description
        [int]
        $Param2
    )

    Begin
    {
    }
    Process
    {
    }
    End
    {
    }
}

Notice it has help at the top, it has the "CmdletBinding" and it's showing me how to implement pipeline input.  Now this is not a powershell course, so I do assume you know what pipeline input is.

Now, also notice that its says your function should have the format Verb-Noun !  This is important.  You want to know all the possible verbs ? (like : new-, add-,invoke-, get-, ...), just type "get-verb" in the console.

So now I merge these 2 into my final script

<#
.Synopsis
   Underlines your text
.DESCRIPTION
   Underlines your text
.EXAMPLE
   Underline "Hoo"
.EXAMPLE
   "Hoo" | Underline -Color Magenta
#>
Function Add-Underline{
    [CmdletBinding()]
    Param(

        # Your input text
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
        [string]$Text,

        # Your color
        [Parameter(Mandatory=$false,Position=1)]
        [string]$Color="Cyan"
    )
    Begin{}
    Process{
        if($Text){
            Write-Host "`n$Text" -ForegroundColor $Color 
            Write-Host "".PadRight($Text.Length,"-")
        }
    }
}
If you run this, notice you will now have a function "Add-Underline".  Notice if you type "Get-Help Underline -Full", it will actually show you help information, directly extracted from your code, and check out the pipeline input.  Just do something like "date | Underline".

PS C:\jumpstart> date | Add-Underline

12/15/2016 09:35:37
-------------------

PS C:\jumpstart> get-help Add-Underline -Full

NAME
    Add-Underline
    
SYNOPSIS
    Underlines your text
    
SYNTAX
    Add-Underline [-Text] <String> [[-Color] <String>] [<CommonParameters>]
    
DESCRIPTION
    Underlines your text
    

PARAMETERS
    -Text <String>
        Your input text
        
        Required?                    true
        Position?                    1
        Default value                
        Accept pipeline input?       true (ByValue)
        Accept wildcard characters?  false
        
    -Color <String>
        Your color
        
        Required?                    false
        Position?                    2
        Default value                Cyan
        Accept pipeline input?       false
        Accept wildcard characters?  false
        
    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see 
        about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 
    
INPUTS
    
OUTPUTS
    
    -------------------------- EXAMPLE 1 --------------------------
    
    PS C:\>Add-Underline "Hoo"
    
    
    -------------------------- EXAMPLE 2 --------------------------
    
    PS C:\>"Hoo" | Add-Underline -Color Magenta

Create a simple module

You want a simple module huh ?  Well, just do this :

Step 1 : Add this at the bottom of your script : Export-ModuleMember Add-Underline
This added line of code tells the module which functions should be cmdlets, all other functions (if any) are internal use only, and are not "exported" to the module.
So your script now looks like :

<#
.Synopsis
   Underlines your text
.DESCRIPTION
   Underlines your text
.EXAMPLE
   Underline "Hoo"
.EXAMPLE
   "Hoo" | Underline -Color Magenta
#>
Function Add-Underline{
    [CmdletBinding()]
    Param(

        # Your input text
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
        [string]$Text,

        # Your color
        [Parameter(Mandatory=$false,Position=1)]
        [string]$Color="Cyan"
    )
    Begin{}
    Process{
        if($Text){
            Write-Host "`n$Text" -ForegroundColor $Color 
            Write-Host "".PadRight($Text.Length,"-")
        }
    }
}

Export-ModuleMember Add-Underline

Create a folder called "Underline"
Save your script (.psm1) in that directory as "Underline.psm1".

Now your module "Underline" is ready.  You can now already import it locally like :
import-module .\Underline
(.\Underline is the path of your directory, powershell will assume that there is a subdirectory called Underline with an underline.psm1 in that directory).

PS C:\jumpstart\Underline> cd..

PS C:\jumpstart> Import-Module .\Underline

PS C:\jumpstart> Get-Module

ModuleType Version    Name                                ExportedCommands                                                      
---------- -------    ----                                ----------------                                                      
Script     1.0.0.0    ISE                                 {Get-IseSnippet, Import-IseSnippet, New-IseSnippet}                   
Manifest   3.1.0.0    Microsoft.PowerShell.Management     {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Content...}    
Manifest   3.0.0.0    Microsoft.PowerShell.Security       {ConvertFrom-SecureString, ConvertTo-SecureString, Get-Acl, Get-Aut...
Manifest   3.1.0.0    Microsoft.PowerShell.Utility        {Add-Member, Add-Type, Clear-Variable, Compare-Object...}             
Manifest   3.0.0.0    Microsoft.WSMan.Management          {Connect-WSMan, Disable-WSManCredSSP, Disconnect-WSMan, Enable-WSMa...
Script     0.0        Underline                           Add-Underline                                                         



PS C:\jumpstart> Get-Command -Module Underline

CommandType     Name                                               Version    Source                                            
-----------     ----                                               -------    ------                                            
Function        Add-Underline                                      0.0        Underline                                         

Move your module to the module repository

Now, to make sure you don't have to know the path of your module, just move it to the repository.  There are 2 of them :
%USERPROFILE%\Documents\WindowsPowerShell\Modules\ (create this if needed)
%WINDOWS%\System32\WindowsPowerShell\v1.0\Modules\

The first is user-bound, the second is global.  As soon as you've copied your module (the directory) to one of these locations, you will now always be able to call it just by typing "import-module Underline".   And don't forget : your .psm1 file must be in a directory with the same name (\Underline\Underline.psm1).

Create a Manifest

Now you probably noticed that your module didn't have a "Version number" and maybe you want to add some "Author" information, or your want to add dependencies, like the powershell version, or you want to make sure another module is loaded/installed.  Now, again, this isn't a powershell course and I won't tell you everthing about manifests (hell, I don't know everything about manifests), but I can show you the very basics.

First : remove the line "Export-ModuleMember" again from your .psm1 file.  We will add that information in the manifest file instead.

Now before we start : there is NOTHING fancy about this manifest.  It's just a plain text file in a specific format, containing some information about your module, like Author, company, version, exported functions, ...  The file must be in your module-directory and the extension of the file is .psd1

Now, to create such a manifest file, there is actually a cmdlet called "New-ModuleManifest", but it has A LOT of parameters.  Luckily, the PowerShell ISE application has a CmdLet GUI !

In your PowerShell ISE application, show the Command Add-On (in the view menu).  And search for manifest.  Select "New-ModuleManifest" and notice a GUI appear for all parameters.

Now, here is the difficult part, for the manifest to work properly, you need to fill in the right parameters and in the right format.  This is the part were I lost time the first time.  In fact, I always open one of my previous working manifest files to have a peep how to do it.

This is my method
Fill in these fields, leave the rest blank if you want :
  • Path = c:\jumpstart\underline\Underline.psd1
  • RootModule = Underline.psm1
  • ModuleVersion = 1.0
  • Author = The Wfa Guy
  • CompanyName = NetApp
  • Description = A cool function
  • FunctionsToExport = 'Add-Underline'
  • ModuleList = 'Underline.psm1'
  • FileList = 'Underline.psm1'
Rootmodule : is the name of your module-file (psm1)
Moduleversion : start with something, you could change versions once you modify stuff
FunctionsToExport, ModuleList & FileList : This is a tricky one, you must wrap them in single-quotes and comma separate them as these parameters are of type "string[]".



Once you run the cmdlet, your psd1 file is created.  If you open it, you'll see it will have added more things, like copyright, a GUID (this is a unique ID and guarantees that versioning works, in case your module name is not unique).

In case your module has extra files, maybe an ini-file or an xml-file or some other dll, then add them in the "filelist" too.

Also note that I use "functions to export" not "cmdlets to export" !!

That's it.  Let me know if this was useful !

No comments :

Post a Comment