Tome's Land of IT

IT Notes from the Powertoe – Tome Tanasovski

Corporate Powershell Module Repository – Part 2 – Developer Guide

This article continues the last one about architecting a corporate module repository.

Developer Guide

I put a formal submission process in place to ensure that all modules meet certain standards.  Developers are given a complete set of documentation that outlines best practices that are enforced via a review process.  When a developer submits a module for distribution in the repository their module is checked to ensure that it meets the following guidelines:

Namespaces

  • All in-house modules must “make sense” and must have the company-specific prefix.  Let’s say that prefix is Toe.  Something like ToeISE or ToeClipboard would be ok.
  • When a namespace already exists that is suitable for a developer’s module they should first try and contact the creator of the namespace to ensure there are no duplicate efforts.  Collaboration on modules is encouraged.
  • The noun of a cmdlet must use the modulename as a prefix or the noun can be the prefix itself, i.e., if the module is named ToeISE a proper cmdlet name might be Open-ToeISE, or if the module is called ToeHTML a proper cmdlet name could be ConvertTo-ToeHTMLTable
  • Cmdlets must use a valid verb returned from Get-Verb, and that verb should follow suit with what Microsoft currently uses the verb for.  For example, Out-ToeExcel would be more appropriate than Write-ToeExcel because the intention of “Write” is to write information to the user through the console.
  • When using add-type in your module you should ensure that the -namespace parameter uses your module name.  A great example of this is a module that wraps pinvoke code.  The following might be a part of a module called ToeClipboard:
$sig = @"
[DllImport("user32.dll")]
public static extern IntPtr GetClipboardData(uint uFormat);
[DllImport("user32.dll")]
public static extern uint EnumClipboardFormats(uint format);
[DllImport("user32.dll")]
public static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll")]
public static extern bool CloseClipboard();
[DllImport("user32.dll")]
public static extern bool EmptyClipboard();
[DllImport("user32.dll")]
"@
Add-Type -MemberDefinition $sig -Namespace ToeClipboard -Name User32

Module Files

  • Developers should create modules using .psm1 files where possible
  • Strict guidelines are given around .psd1 files.  There must be a .psd1 file with every distribution.  I have provided our developers with a company-specific script that runs New-ModuleManifest to ensure that fields are filled out appropriately.

Parameters

  • Parameters must be defined with param(), and they must contain the appropriate Parameter attributes outlined in Get-Help about_Functions_Advanced_Parameters

Help

  • All cmdlets must include inline help.  The following template was supplied to developers:
<#
 .Notes
 NAME:
 AUTHOR:
 Version:
 CREATED: 6/17/2010
 LASTEDIT:
 6/17/2010 1.0
 Initial Release

 .Synopsis
 One line blurb that discusses cmdlet

 .Description
 Multi-line description of cmdlet

 .Parameter ParameterName1
 Description of ParameterName1

 .Parameter ParameterName2
 Description of ParameterName2

 .Inputs
 Information about the types of input objects accepted

 .Outputs
 Information about the objects returned as output from the cmdlet

 .Example
 Detailed example.  Ideally there should be an example for each set of Parameters and a pipeline example.

 .Example
 Additional example

 .LINK
 Related cmdlets or relevant URL

#>

Aliases

  • Aliases for cmdlets/functions should not be exported.  This is required to ensure that there is no contention and clobbering of aliases.  Aliases may be suggested in your notes, but they must not be forced.
  • Aliases for parameters are OK because they are contained within the cmdlet.  They may also be necessary to ensure that pipeline input by name is accepted from multiple sources.

Internet Repository

Are you surprised that I would end this article calling for a Powershell module repository on the Internet? While the infrastructure and design of the in-house module repository I put together for my company will not work on the Internet, I think the standards I outlined for the developer’s guide is something that is enforceable for the module repository we will build on the Internet. In order to be successful we will need a governing body that ensures namespaces and standards are met with new code. I feel that this is the easy part. Just look at all of the Judges for the 2010 Scripting Games; Reading 100 scripts a day that are all nearly identical is hardly anyone’s idea of fun yet there were plenty of community leaders who were happy to take up the task when asked. I think the hard part is the design and maintenance of the infrastructure on the Internet. So get to work people – I guarantee you we can assemble the board to oversee it justly, fairly, and with proper standards in place.

Part three of this series will include my techniques for migrating snapins to modules for both in-house and 3rd party snapins.

4 responses to “Corporate Powershell Module Repository – Part 2 – Developer Guide

  1. Joel "Jaykul" Bennett August 14, 2010 at 9:23 am

    I’m not so sure about requiring the whole module-name on the command as a prefix (I mean, I don’t like forcing prefixes anyway, but…), imagine if Microsoft followed that:

    Invoke-SQLCmd would become Invoke-SqlServerCmdletSnapin100Cmd
    Get-ADUser would have to be Get-ActiveDirectoryUser
    Get-TfsChangeset would have to be Get-TfsBPAPowerShellSnapInChangeset (well, ok, that was a dumb name for a module/snapin anyway, but you get my drift)

  2. Tome August 16, 2010 at 9:59 am

    Understandable, but the big concern is clobbering. Ensuring that two cmdlets never have the same name when they come from different modules. That is the most important thing I have to be sure to prevent for in-house users.

    In regards to SQL, I agree. But, the solution for us is that the module itself would be called SQL, no SqlServerCmdletSnapin100Cmd. Same applies for the quest tools. It would be AD.

    This problem is elegantly solved by choosing better module names.

  3. Joel "Jaykul" Bennett August 16, 2010 at 8:22 pm

    If Quest named their module “AD” … what would Microsoft have named theirs? You can’t all use the simple name 😛

    The thing is though … commands aren’t clobbered. You _can_ specify a -Prefix when you import it … but even if you don’t you can always specify the module you want. So if Microsoft’s module was named AD and there was another named QuestAD, and they both had Get-User … but you needed both modules loaded for different cmdlets….

    1) You could chose to: Import-Module QuestAD -prefix QAD; Import-Module AD; ## and you could invoke: Get-QADUser and Get-User
    2) You could just: Import-Module QuestAD; Import-Module AD; ## and you could invoke: QuestAD\Get-User and Get-User
    3) You could reverse them: Import-Module AD; Import-Module QuestAD; ## and you could invoke: Get-User and AD\Get-User

    Of course, all those options would be confusing, but you can just pick one. My point is that there are plenty of options for modules without forcing coders to stick immovable prefixes on functions and cmdlets. I’m much happier if you stick them on when you import them, so everyone can choose their own way of doing it.

    • Tome August 16, 2010 at 9:00 pm

      We solved that problem by adding our own custom prefix to the prefix 🙂

      So, in my examples I used Toe, but in my company it’s a two letter prefix. Let’s say we’re building an Internet repository. Why not have the prefix MR (Module Repository). So the module in the Internet Repository that handles AD would be MRAD: get-MRADUser, ISE: open-MRISE, Wiki: ConvertTo-MRWiki.

      The problem is that an everyday user shouldn’t worry about namespaces or clobbering. Yes, there are ways around them, but if you put the responsibility on the developer and the repository you reduce the risk of users hurting themselves without knowing it.

      Don’t forget a user is going to install a set of modules, but they may not know all of the functions they are installing. They have no idea when they load them that the developer may have exported Get-Process (I realize they can easily get this information and prevent it, but they may not realize it)

      The repository should also ensure that there aren’t 5 modules with their own version of Get-User. The code should have prerequisite modules when needed. I know there’s more than one way to access a lot of this, but that doesn’t mean there should be 5 Get-User cmdlets because there are 5 different ways to do something. Or if there is relevance to doing get-user via [adsi] and a cmdlet that does get-user via WMI the cmdlets and the module should be named accordingly.

      My final argument is that a module’s cmdlets should stay relevant to their module. Enforcing name prefixes on nouns helps everyone make sure that the cmdlet belongs with that module. i.e. there shouldn’t be a function called get-ADUser in a module that enumerates the clipboard. This is my biggest problem with PSCX. I think that module is way too big. It should be a series of smaller modules, in my opinion. I guess that is what I want the Internet repository to be. A version of PSCX that is easier to contribute to and is broken into much smaller modular pieces that can be used as needed – and yes, I realize you can control what you import when you import a module, but again I think that can be better controlled by the developer and the repository.

      I should also note, for the benefit of the readers, that Joel is actively working on creating an Internet module repository through extensions of poshcode.org. You can weigh in on ideology in the comments of this article, but you can also weigh in on the decisions he’s making about module manifest meta data in the poshcode wikipage comments for the manifest format: http://wiki.poshcode.org/PoshCode_Project/PoshCode_Manifest_Format

Leave a comment