Windows PowerShell: Package and Distribute Custom Windows PowerShell Tools

You can accomplish some interesting things with dot-sourcing, but that can also lead to certain limitations if you’re not careful.

Don Jones

Dot-sourcing is a neat trick, but it can be difficult to remove commands later if you don’t need them or later discover they’re conflicting with something else. After we turned a command into a standalone, parameterized, reusable tool last month, I found two limitations we need to address.

We built the tool as a Windows PowerShell advanced function, which is also sometimes called a “script cmdlet.” The first problem is that the script itself is a bit difficult to use. The script contains a function, so simply running the script won’t make anything happen. You either have to modify the script and add the commands needed to run the function, or you have to dot-source the script into the shell, making the function available as a global command.

The second problem is that the script included two functions. The first one, Get-OSInfo, is the one I want people to use. The second, OSInfoWorker, does all the actual work. However, that’s not the one I want you to execute directly. Using dot-sourcing, there’s no way to hide OSInfoWorker (or make it private, in programmer terms). Making the script into a script module will address both problems.

Make Sense of Modules

Windows PowerShell supports three basic types of modules: binary, script and manifest modules. A binary module consists of a DLL built in Visual Studio that adds cmdlets, providers and other elements to the shell. A script module is just that: a single script that can add one or more functions to the shell. A manifest module can actually include multiple components, such as binary extensions, scripts and so on. Script modules are the simplest to create, so that’s what we’ll use.

There are three requirements for a script module:

  1. It needs to be a valid Windows PowerShell script, consisting primarily of functions that will be added to the shell.
  2. It must have a .psm1 filename extension, rather than .ps1.
  3. It must be located in a particular folder on your computer

That last requirement is actually more of a suggestion for the sake of convenience. Upon installation, Windows PowerShell defines a new system-wide environment variable called PSModulePath. This works a lot like the system’s Path environment variable. It contains the folders the shell automatically searches to find modules by name.

There are two module paths defined by default. One is under the System32 folder hierarchy. This one is meant for Microsoft-provided modules. The other is in your Documents folder and is meant for your own modules. We’ll use this one. You can, of course, modify or add to PSModulePath—perhaps defining a central path that your team uses to store shared modules.

The path we’ll use is \[My ]Documents\WindowsPowerShell\Modules. On Windows XP, it’s labeled as My Documents. In Windows Vista and later, it’s just Documents. The WindowsPowerShell folder doesn’t exist by default. You’ll have to create that one. The Modules subfolder also doesn’t exist by default, so you’ll need to create that as well.

The alternative to putting your module in one of the PSModulePath locations is to simply specify a full path and filename when you load a module. I find that to be less convenient for frequently used modules, so I tend to stick with the paths defined in PSModulePath.

Making a Module

I’m going to add three commands to the bottom of last month’s script (you can download the revised script here):

New-Alias goi Get-OSInfo

Export-ModuleMember -function Get-OSInfo

Export-ModuleMember -alias goi

The first command defines an alias, “goi.” This is for my Get-OSInfo function. The second two commands are only effective when you use them within a script module.

By default, when you load a module into the shell, every function within that module is made available. When you use Export-ModuleMember, however, only those functions—and aliases—that are explicitly named are made available.

My “goi” alias, as well as the Get-OSInfo function, should be visible to anyone using this module. The function I didn’t specify, OSInfoWorker, will be hidden. You can still use any function to call OSInfoWorker within the module itself, but it will not be directly accessible, as it’s a private function.

With those commands added, I need to give the script a proper name and put it in the right place. Let’s say I choose to name this module “MyModule.” That means the script file needs to go in \[My ]Documents\WindowsPowerShell\Modules\MyModule\MyModule.psm1.

The script has to go into a subfolder of Modules. Both the subfolder and the script file itself must carry the module name. With that done, I can run Import-Module MyModule to load my module.

This is an easy and effective way to distribute scripts to other users. I can include as many functions and aliases as I want within this single file. As long as it’s located in the right spot (or someone’s willing to specify its full path), then it’s easy for other users to load the module and use those functions.

Don Jones

Don Jones* is a founder of Concentrated Technology, and answers questions about Windows PowerShell and other technologies at ConcentratedTech.com. He’s also an author for Nexus.Realtimepublishers.com, which makes many of his books available as free electronic editions through his web site.*

Get More

Download sample code that goes with this month’s article.

Time is running out to register for Jones’ live, exclusive, hands-on three-day workshop co-located with TechMentor Spring 2011. Visit TechMentorEvents.comfor details.