Sunday, January 04, 2009

Modules in PowerShell V2

Introduction

As PowerShell has evolved, there are a number of things that have been needed to be added in order to make it truly enterprise ready. One of the key concepts added into Version 2 of PowerShell is that of a module. A module is some chunk of code that you can import into PowerShell and then use. Once imported, any cmdlets, scripts, or providers can be accessed. Installation of a module is now very simple – just use xcopy. 

Modules came originally with CTP2 of PowerShell V2 and are greatly improved in the latest CTP (CTP3) issued late December 2008. Unfortunately, good documentation on how to use modules is scarce: there’s no detailed information in the release notes, no about_module help information, and all the module cmdlets lack auto-help (get-help just dumps raw syntax and offers nothing else in the way of help). Nevertheless, a bit of playing, extensive Googling, and lots of trial and error has yielded a better understanding. For such an important feature, MS has let the side down a bit. But I digress!

Modules vs Snap-ins

In Version 1, we had the Snap-in as the way to add functionality into PowerShell. Teams like Exchange used this to add their own Exchange cmdlets into Exchange 2007. You install a snap-in by copying the necessary files and then updating registry entries to point to those files. Once installed, you could use the Add-PSSnapIn cmdlet to add the functionality of the snap-in into your environment. With a snap-in, the installation routine had to be written (although it could be partly automated) and the user had to run commands to perform the installation. Modules simplify this greatly.

Modules are a replacement for Snap-ins, although V2 will still support V1 type snap-ins. Modules are a much much improved way of creating add-ins and are to be strongly preferred going forward. You can create a module either by using compiled code (as you did in V2),  but also by writing them in PowerShell script. You can decorate modules with meta data, including Parameter descriptions, to enable them to be better integrated into a user’s  PowerShell experience. By using the Auto-Help feature, the module is self documenting.

Creating a Module with Powershell CTP3

At it’s simplest, a module is just a PowerShell script, contained in a file with a .PSM1 extension. For it to be of any use, you must save this script in a folder below your modules folder which is where PowerShell looks for modules. PowerShell has an environment variable, PSMODULEPATH, which defines where modules are to be found. On my system, I can see this as follows:

PS C:\foo> dir env:psmodulepath

Name                           Value
----                           -----
PSMODULEPATH                   C:\Users\tfl\Documents\WindowsPowerShell\Modules; C:\Windows\SysWOW64\WindowsPowerShell\v1.0\Modules

As you can see, there are two folders on my system (one a per user, the other a per system). As you can also see, I’m running on an X64 OS (specifically Windows Server 2008 configured as a workstation!). I’m running PowerShell to create this post from within PowerShell Plus. Thus, you might see a different set of values on your system.

Much like Profiles, the per user module paths does not actually exist by default. You need to create the actual folder as Jeffrey Snover describes at the end of an interesting blog post. 

Sample Module

To illustrate PowerShell modules, here’s a module based on a script I posted to MDSN and to my PowerShell Scripts blog:

  1. function DeviceInterface { 
  2. param ($value
  3. switch ($value) { 
  4. 0    {"Other"
  5. 1    {"Unknown"
  6. 3    {"Serial"
  7. 4    {"PS/2"
  8. 5    {"Infrared"
  9. 6    {"HP-HIL"
  10. 7    {"Bus Mouse"
  11. 8    {"ADP (Apple Desktop Bus)"
  12. 160  {"Bus Mouse DB-9"
  13. 161  {"Bus Mouse Micro-DIN"
  14. 162  {"USB"
  15.  
  16. function Handedness { 
  17. param ($value
  18. switch ($value) { 
  19. 0 {"Unknown"
  20. 1 {"Not Applicable"
  21. 2 {"Right-Handed Operation"
  22. 3 {"Left-Handed Operation"
  23.  
  24. function PointingType { 
  25. param ($value
  26. switch ($value) { 
  27. 1 {"Other"
  28. 2 {"Unknown"
  29. 3 {"Mouse"
  30. 4 {"Track Ball"
  31. 5 {"Track Point"
  32. 6 {"Glide Point"
  33. 7 {"Touch Pad"
  34. 8 {"Touch Screen"
  35. 9 {"Mouse - Optical Sensor"
  36.  
  37. function get-MouseInfo { 
  38.  
  39. # Now do script stuff 
  40. # Get Mouse information 
  41. $mouse = Get-WmiObject -Class Win32_PointingDevice 
  42.  
  43. # Display details 
  44. "Mouse Information on System: {0}" -f $mouse.systemname 
  45. "Description            : {0}" -f $mouse.Description 
  46. "Device ID              : {0}" -f $mouse.DeviceID 
  47. "Device Interface       : {0}" -f (Deviceinterface($mouse.DeviceInterface)) 
  48. "Double Speed Threshold : {0}" -f $mouse.DoubleSpeedThreshold 
  49. "Handedness             : {0}" -f (Handedness($mouse.handedness)) 
  50. "Hardware Type          : {0}" -f $mouse.Hardwaretype 
  51. "INF FIle Name          : {0}" -f $mouse.InfFileName 
  52. "Inf Section            : {0}" -f $mouse.InfSection 
  53. "Manufacturer           : {0}" -f $mouse.Manufacturer 
  54. "Name                   : {0}" -f $mouse.Name 
  55. "Number of buttons      : {0}" -f $mouse.NumberOfButtons 
  56. "PNP Device ID          : {0}" -f $mouse.PNPDeviceID 
  57. "Pointing Type          : {0}" -f (Pointingtype ($mouse.PointingType)) 
  58. "Quad Speed Threshold   : {0}" -f $mouse.QuadSpeedThreshold 
  59. "Resolution             : {0}" -f $mouse.Resolution 
  60. "Sample Rate            : {0}" -f $mouse.SampleRate 
  61. "Synch                  : {0}" -f $mouse.Synch 
  62.  
  63. # Export just the last function. 
  64. Export-ModuleMember Get-Mouseinfo 
  65. # End of Script 

Using Modules

First, I saved this file to MouseInfo.psm1, and then I stored that file under the folder MouseInfo contained in my per-user module folders (in specific, C:\Users\tfl\Documents\WindowsPowerShell\Modules\MouseInfo). Once this folder and the .PSM1 file are in place, you add this module into PowerShell by calling Import-Module, as follows:

PS C:\foo> # Note function does not currently exist!
PS C:\foo> get-mouseinfo
The term 'get-mouseinfo' is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again.
At line:1 char:14
+ get-mouseinfo <<<<
    + CategoryInfo          : ObjectNotFound: (get-mouseinfo:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

PS C:\foo> # So now import it – note no output by default
PS C:\foo>
Import-Module mouseinfo
PS C:\foo> # now use a function contained in the module
PS C:\foo> get-mouseinfo
Mouse Information on System: COOKHAM8
Description            : PS/2 Compatible Mouse
Device ID              : ACPI\PNP0F13\4&33DCE2F0&0
Device Interface       : PS/2
Double Speed Threshold :
Handedness             :
Hardware Type          : PS/2 Compatible Mouse
INF FIle Name          : msmouse.inf
Inf Section            : PS2_Inst
Manufacturer           : Microsoft
Name                   : PS/2 Compatible Mouse
Number of buttons      : 0
PNP Device ID          : ACPI\PNP0F13\4&33DCE2F0&0
Pointing Type          : Unknown
Quad Speed Threshold   :
Resolution             :
Sample Rate            :
Synch                  :
PS C:\foo> # Find out more with Get-Module cmdlet
PS C:\foo> Get-Module mouseinfo

Name              : mouseinfo
Path              : C:\Windows\SysWOW64\WindowsPowerShell\v1.0\Modules\mouseinfo\mouseinfo.psm1
Description       :
Guid              : 00000000-0000-0000-0000-000000000000
Version           : 0.0
ModuleBase        : C:\Windows\SysWOW64\WindowsPowerShell\v1.0\Modules\mouseinfo
ModuleType        : Script
PrivateData       :
AccessMode        : ReadWrite
ExportedAliases   : {}
ExportedCmdlets   : {}
ExportedFunctions : {[get-MouseInfo, get-MouseInfo]}
ExportedVariables : {}
NestedModules     : {}

As you can see, importing a module is very simple (once it’s in the right folder). And once you have imported your module, you can use the functions from the module that have been exported. In the MouseInfo module above, there are several helper functions to decrypt the values returned from WMI and which I (as the module’s author) do not wish to expose to a user of the module. The only exported function – in other words the only function that you can use from this module – is Get-MouseInfo. I explicitly export this function by using the Export-ModuleMember cmdlet in line 71 of the module.

Module Cmdlets

Modules are a key part of CTP3. To support modules, there are 7 module related cmdlets included with Powershell V2 CTP3, as follows:

  • New-Module – creates a new module from a script block.
  • Import-Module – imports a module from your Modules folder
  • Export-ModuleMember – notes the functions that a module exports and you can use once you import the module
  • Get-Module – gets information about modules
  • Remove-Module – removes a module
  • New-ModuleManifest – Helps create a new module manifest
  • Test-ModuleManifest – tests a module manifest

In addition, you can use the Get-Command cmdlet, specifying the module you want to get information from. For example:

PS MyMod:\> Get-Command -Module mouseinfo

CommandType Name              Definition
----------- ----              ----------
Function    get-MouseInfo     ...

I’ll try to blog more details on the module (and manifest) related cmdlets in due course.

Module Manifests

A module manifest is a specially constructed PowerShell script saved in a .PSD1 file. A module manifest is used to define precisely what is contained in a module. A manifest is an optional component, but the PowerShell team strongly advises use of a manifests to better document and describe a module. For more complex modules, e.g. more than just a single .psm1 file, a manifest is probably required.

A Module Manifest is really just a script that creates a hash table. This hash table contains the keys/values that PowerShell uses in managing your module. Since the manifest is pretty simple, it is very easy create one – just use your favourite script edtiro, and add in the values that you need. To make things even simpler, you can use the New-ModuleManifest cmdlet to create a manifest (and then tweak it using your favourite script editor).

Based on the MouseInfo module above, creating a new manifest using New-ModuleManifest is simple:

PS C:\foo> New-Modulemanifest .\mouseinfo.psd1

cmdlet New-ModuleManifest at command pipeline position 1
Supply values for the following parameters:
NestedModules[0]:Mouseinfo.psm1
Author: Thomas Lee
CompanyName: PS Partnership
Copyright: 2009
Description: My first module
TypesToProcess[0]:
FormatsToProcess[0]:
RequiredAssemblies[0]:
OtherFiles[0]:

After a bit of reformatting for publication and tidiness, here’s what the Mouseinfo module manifest  looks like:

  1. # Module manifest for module 'Mouseinfo' 
  2. # Generated by: Thomas Lee 
  3. # Generated on: 1/3/2009 
  4. @{ 
  5. # These modules will be processed when the module manifest is loaded. 
  6. NestedModules = ‘Mouseinfo.psm1’
  7. # This GUID is used to uniquely identify this module. 
  8. GUID = '94979266-70b4-4243-bef8-6fd87529af69' 
  9. # The author of this module. 
  10. Author = 'Thomas Lee' 
  11. # The company or vendor for this module. 
  12. CompanyName = 'PS Partnership' 
  13. # The copyright statement for this module. 
  14. Copyright = '2009' 
  15. # The version of this module. 
  16. ModuleVersion = '1.0' 
  17. # A description of this module. 
  18. Description = 'Cool Module' 
  19. # The minimum version of PowerShell needed to use this module. 
  20. PowerShellVersion = '2.0' 
  21. # The CLR version required to use this module. 
  22. CLRVersion = '2.0' 
  23. # Functions to export from this manifest. 
  24. ExportedFunctions = 'Get-MouseInfo' 
  25. # Aliases to export from this manifest. 
  26. ExportedAliases = '*' 
  27. # Variables to export from this manifest. 
  28. ExportedVariables = '*' 
  29. # Cmdlets to export from this manifest. 
  30. ExportedCmdlets = '*' 
  31. # This is a list of other modules that must be loaded before this module. 
  32. RequiredModules = @() 
  33. # The script files (.ps1) that are loaded before this module. 
  34. ScriptsToProcess = @() 
  35. # The type files (.ps1xml) loaded by this module. 
  36. TypesToProcess = @() 
  37. # The format files (.ps1xml) loaded by this module. 
  38. FormatsToProcess = @() 
  39. # A list of assemblies that must be loaded before this module can work. 
  40. RequiredAssemblies = @() 
  41. # Lists additional items like icons, etc. that the module will use. 
  42. OtherItems = @() 
  43. # Module specific private data can be passed via this member. 
  44. PrivateData = '' 

If I had included this manifest in my Mouseinfo module folder, after importing the Module, I’d be able to get better help information about the module (e.g. Get-Module would provide the description and the GUID created by New-ModuleManifest, etc). Manifests are an important aspect of PowerShell modules – I’ll cover them more in a future blog post.

References For More Information on Modules

As noted above, details on modules in CTP3 are hard to come by (and many are out of date) . Even the help text is pretty bare! Some blog posts that discuss modules and may be still be useful include:

Summary

With PowerShell V2 CTP3, you create and use modules to add functionality into PowerShell simply and easily. Modules are built using code, script or a combination. A module can be just one .PSM1 file, while more complex modules can be a combination of code, script, and other resources described in a module manifest. You can control what your users see when they import a module by using a module manifest and by using Export-ModuleMember cmdlet. To manage modules, you have a number of module related cmdlets to use. Finally, Module manifests are a key tool to describe modules.

I’ll be posting more about modules in the coming weeks.

Technorati Tags: ,,,

1 comment:

Bernd Kriszio said...

Late december 2009?
Where will PS be then.
CTP 3 was still in 2008.