PowerShell – Script and manifest modules

Chapter 13 – Script and manifest modules

In Chapter 4 you have found a number of ways that you can create a custom command…e.g. by creating custom scripts/functions. E.g. if you want to use a function like a command, then you have to dot-source the script that houses the function. However this is not a good solution from the end user’s point of view.

A better approach is to package your custom commands (in this case they are in the form of functions that are housed in a script) into modules.

First off, in linux, if you want to use a script in the same style as an ordinary command, then you have to place the script them in one of the directories that are defined in the $PATH variable. In Powershell you do the same kind of thing, along with a number of other things.

If you switch to the “env” psdrive:

cd env:\

Here you will find an item called “PSModulePath”

get-content PSModulePath # This is the ps equivalent of linux’s $PATH variable. It is also semi-colon seperated.
C:\Documents and Settings\SChowdhury\My Documents\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\

Another way to view this environment’s variable:

$env:PSModulePath

You can then change the value like this:

$env:PSModulePath = “C:\Documents and Settings\SChowdhury\My Documents\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\;C:\temp”

However this reverts back to it’s original value when you restart the shell. To make this change permenant, you need to do it from the gui:
right-click “mycomputer” and select “properties” | Advanced tab | Environment variables (This is is a windows xp machine.)

Before you create a module, you should first decide a name for your module, Ideally this should be unique, which you can do by adding a prefix e.g.:

hrmctool

Next, we create a folder by the same name (this folder must be within one of the directories listed in $env:PSModulePath):

PS C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules> new-item -ItemType directory -Name hrmcTool

Within this newly created folder you need to place your script, this scriptfile needs to:

– Have the exact same name as the parent folder. (it is also case sensitive)
– Have the extension “psm1” rather than “.ps1” (which means “windows powershell script module”)

For the functions (which will become the commands) within the script, it is best practice to:

– follow the verb-noun name convention, but prefix the noun part with something unique.
– prefix the noun with something unique (e.g. hrmc) to avoid any possible conflicts with existing commands with the same names.

Also, in this folder you can place the xml file that defines your custom view for your function’s (custom command’s) output. So at this point you should have:

PS C:\Documents and Settings\SChowdhury\My Documents\WindowsPowerShell\Modules\hrmctool> ls

Directory: C:\Documents and Settings\SChowdhury\My Documents\WindowsPowerShell\Modules\hrmctool

Mode LastWriteTime Length Name
—- ————- —— —-
-a— 27/08/2013 15:53 1766 hrmctool.psm1
-a— 28/08/2013 08:58 1643 hrmctools.format.ps1xml

However the problem is that the hrmctools.format.ps1xml will not automatically be loaded into memory when you activate the main module. However this can by overcome by creating a “ModuleManifest” file. This file is generated by running the following command:

New-ModuleManifest -Path .\hrmctool.psd1 ` # This defines the name of the new file.
-Author ‘Sher’ `
-CompanyName ‘HMRC’ `
-Copyright ‘(c) Sher Chowdhury’ `
-Description ‘this is a test description’ `
-FormatsToProcess .\hmrctool.format.ps1xml ` # This defines the custom view to be loaded into memory as part of this module
-ModuleVersion 1.0 `
-PowerShellVersion 3.0 `
-RootModule .\hmrctool.psm1 # This is the path to the module script itself.

I have broken down the above command into multiple lines so to make it easier to read. The end result is the creation of the new manifest file:

PS C:\Documents and Settings\SChowdhury\My Documents\WindowsPowerShell\Modules\hmrctool> ls

Directory: C:\Documents and Settings\SChowdhury\My Documents\WindowsPowerShell\Modules\hmrctool

Mode LastWriteTime Length Name
—- ————- —— —-
-a— 28/08/2013 10:51 4762 hmrctool.psd1 # This is the new file created by the new-modulemanifest command.
-a— 27/08/2013 15:53 1766 hmrctool.psm1
-a— 28/08/2013 08:58 1643 hmrctools.format.ps1xml

Now to activate this module along with its custom view, do:

PS C:\> Import-Module -Name hmrctool

Now here are 2 ways to check that the above command has worked

First check:
PS C:\> Get-module # This lists all the modules that are enabled, along with the commands they come with.

ModuleType Name ExportedCommands
———- —- —————-
Script hmrctool new-hmrcobject
Manifest Microsoft.PowerShell.Management {Add-Computer, Add-Content, Checkpoint-Computer, Clear-Content…}
Manifest Microsoft.PowerShell.Utility {Add-Member, Add-Type, Clear-Variable, Compare-Object…}

The above indicates that the “new-hmrcobject” can now be used like a command because the module hmrctool has now been enabled.

Second check:
we can simply run the newly enabled function, as if it is a command:

PS C:\> new-hmrcobject
columnX columnY columnZ
——- ——- ——-
aaa bbb ccc

The above indicates also indicates that our custom view has also been activated. But you can check this uisng “get-formatdata”:

PS C:\> Get-FormatData | Where-Object -FilterScript {$_.TypeName -match ‘hmrc’}

TypeName FormatViewDefinition
——– ——————–
HMRCtoolcustomformat {HMRCtoolcustomformat , TableControl}

Note: The bare minimum required to create a module is create the module’s folder, and then place the psm1 file into it. Then you can run the import-module command straight after that. The creation of the psd1 and ps1xml are both optional, but they are highly recommended if you want to create a more mature and well rounded module.

We can also create default settings for our module, which is a bit like a module’s preference settings. For example I have added the following lines
to the module script (for incorporating a log file):

$hmrclogfile=’c:/temp/hmrclogfile.txt’ # This define’s the new log file. Notice that this isn’t defined inside a function. That’s because
# it’s likely that other function would want to use the same variable. Also you can’t access this variable
# from the command line. I.e. This variable is private and is only visible to other components within the # module script. This means that importing a module is not quite the same thing as dot-sourcing.
function new-hmrcobject {

$MyHashtable = @{

‘column1’=”aaa”;
‘column2’=”bbb”;
‘column3’=”ccc”
}

$obj = New-Object -TypeName psobject -property $MyHashtable
$obj.PSObject.TypeNames.Insert(0,’HMRCtoolcustomformat’)

$obj

“object succesfully outputted” | out-file -filepath $hmrclogfile -append # here we make use of this variable. Nothing special here.
}

export-ModuleMember -variable hmrclogfile # This is the important part. This makes this variable visible (and updateable) on the command line.

export-ModuleMember -function new-hmrcobject # By convention if you use the “export-ModuleMember” anywhere in the script (as we have done
# just above), all the functions automatically becomes private. You then have to explicitly export # them to be able to use them again.

Now when you import the module, you will now find that the following variable now exists:

PS C:\> $hmrclogfile
c:/temp/hmrclogfile.txt

This variable actually resides in the “variable” psdrive:

PS C:\> Get-PSDrive | Where-Object -FilterScript {$_.Name -match “variable”}
Name Used (GB) Free (GB) Provider Root CurrentLocation
—- ——— ——— ——– —- —————
Variable Variable

PS C:\> cd variable:\
PS Variable:\> ls | Where-Object -FilterScript {$_.Name -match “hmrc”}
Name Value
—- —–
hmrclogfile c:/temp/hmrclogfile.txt

Now you can simply change this variable, in the same way you would do a linux env variables.

Also, this variable disappears when you remove the module:

remove-module hmrctool

This essentially means that the whole module is completely self-contained.

Another way to create a “preference” is to pass the value into the function using parameters, just like we normally do. However some things (like setting the logfile’s default path) is more of a preference thing rather than a parameter. To help decide whether somehting should be a parameter, or a preference setting, you should check if it is best to define the variable insided a function, or outsied. If it is defined outsied of all functions (so that all function can make use of it) then it is a preference setting.