Writing PowerShell Code Compatible with PowerCLI Modules and Snapins

PowerShell snapins are a relic of the version 1 days where additional cmdlets and providers had to be installed and registered onto the target computer. While snapins do further extend the shell to include new functionality, they end up being much less robust and flexible than modules, released with PowerShell 2 as the way-forward method for extending the shell. Today, PowerShell 2 is the fairly standard shell version that you’ll find on Windows, with many migrating to PowerShell 4 by way of Windows Update or more modern OS releases. Look for PowerShell 5 to release in the near future.

Modules are incredibly open to being whatever it is you need them to be. In a nutshell, PowerShell will search the path(s) found in $env:psmodulepath and offer the functionality found within the child-item folders. Running a simple $env:psmodulepath -split (“;”) command will show you all of the paths assigned to the environmental variable. I’ve demonstrated this below on a server with PowerCLI 6.0.0 installed.

PS C:\Users\chris> $env:psmodulepath -split (";")
C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules\

Any of the folders within can contain modules.

PS C:\Users\chris> gci "C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Modules"
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 4/5/2015 11:04 AM VMware.VimAutomation.Cis.Core
d---- 4/5/2015 11:04 AM VMware.VimAutomation.Cloud
d---- 4/5/2015 11:04 AM VMware.VimAutomation.Core
d---- 4/5/2015 11:04 AM VMware.VimAutomation.HA
d---- 4/5/2015 11:04 AM VMware.VimAutomation.PCloud
d---- 4/5/2015 11:04 AM VMware.VimAutomation.SDK
d---- 4/5/2015 11:04 AM VMware.VimAutomation.Storage
d---- 4/5/2015 11:04 AM VMware.VimAutomation.Vds

One of the slick things that the PowerCLI team did with PowerCLI 6 was to begin converting a number of their snapins into modules. It was Jonathan Medd’s blog post that caught my eye originally, and I later found this handy conversion cheat-sheet from VMware’s Power CLI 6.0 Release 1 documentation. Additionally, PowerCLI 6.0R1 has updated a few cmdlets to follow the list of Microsoft blessed verb-noun formats.

Below are all the files included in the VMware.VimAutomation.Vds module as an example. You’ll often find scripts (ps1), script modules (psm1), binaries (dll), type files (ps1xml), manifests (psd1), and other such content. Note that .psd1 file is the PowerShell module manifest or PowerShell Data file and includes a hashtable with details on how to process the module.

ComponentDescriptor-VMware.VimAutomation.Vds.Commands.xml
ComponentDescriptor-VMware.VimAutomation.Vds.xml
Initialize-VMware_VimAutomation_Vds.ps1
VMware.VimAutomation.Vds.Commands.dll
VMware.VimAutomation.Vds.Commands.dll-Help.xml
VMware.VimAutomation.Vds.Format.ps1xml
VMware.VimAutomation.Vds.Impl.dll
VMware.VimAutomation.Vds.Interop.dll
VMware.VimAutomation.Vds.ps1
VMware.VimAutomation.Vds.psd1
VMware.VimAutomation.Vds.Types.dll

I’ll take a moment to say bravo to the PowerCLI team for continuing to raise the bar. Very glad to see the direction that VMware is going with the super-sexy Windows shell.

Modules versus Snapins

Having modules makes handling requirements simpler because PowerShell supports module auto-loading of functionality found inside of a module, even if the current running session hasn’t yet imported the module. There’s actually several ways to import a module, from explicitly calling import-module to running a get-command or get-help cmdlet against the module. This is a strong upside when compared to snapins, which require being added using add-pssnapin before any of the cmdlets can be used.

As a quick example, I’ll try running Connect-VIServer in a PowerCLI 5.8 environment. This cmdlet is provided by a snapin called VMware.VimAutomation.Core. Here’s the step-by-step of what you’re seeing:

  1. I first run Get-PSSnapin to see what snapins have been loaded. Note the absence of any VMware snapins.
  2. Using the Connect-VIServer cmdlet from VMware.VimAutomation.Core fails.
  3. Add the VMware.VimAutomation.Core snapin.
  4. Validate that VMware.VimAutomation.Core is added.
  5. Try Connect-VIServer again, which now starts asking me for a vCenter Server. The cmdlet runs because VMware.VimAutomation.Core is loaded.

connect-viserver-pssnapin-fail

In this next example, I’ll use the same cmdlet on a PowerCLI 6.0.0 environment. The cmdlet is provided by a module called VMware.VimAutomation.Core. Here’s exactly what I’m doing:

  1. Using Get-Module to show which modules I have loaded. Note the absence of any VMware modules.
  2. Using the Connect-VIServer cmdlet from VMware.VimAutomation.Core succeeds and starts asking me for a vCenter Server.
  3. Running Get-Module again, which now shows that VMware.VimAutomation.Core has been imported automatically.

connect-viserver-module-success

VMware.VimAutomation.Sdk gets imported because VMware.VimAutomation.Core’s module manifest calls it out as a required module. Snazzy, right?

core-manifest

Code Changes for PowerCLI 6.0.0+

If you’ve written PowerCLI code, you’ve likely told the script to load PowerCLI snapins. I tend to add the VMware.VimAutomation.Vds snapin frequently because I work with the vSphere Distributed Switch (VDS) on a regular basis. If you’re in the same boat, make sure to write your scripts to allow for both PowerCLI 5.8 or earlier (snapins) along with PowerCLI 6.0 and later (modules). There’s no need to import the Core module if you plan to use the Vds module; the module manifest for Vds will do that for you (which then in turn imports the Sdk module).

vds-manifest

The code that I’ve come up with (thus far) uses a switch to parse through the major versions of PowerCLI’s VMware.VimAutomation.Core snapin to determine which one you have installed. This is because PowerCLI 6.0.0 installs VMware.VimAutomation.Core as both a snapin and a module. If that changes, I’ll need to update the validation code again, perhaps using multiple try/catch combos to comb through modules and then snapins (ugh). I liked the idea of a switch instead of if/elseif logic because it’s simpler to iterate as newer versions are released without creating a “nested if” nightmare.

Below is a gist showing a sample of the code; note that I’ve spaced it out to make it easier to read, but it can be squashed into a few lines if that’s your cup of tea. Also, using the ListVersions argument with Get-Module takes a long time to run, so I opted not to use that option. I’m open to better methods, but this one works against a pair of test boxes running PowerCLI 5.8 and 6.0.

$powercli = Get-PSSnapin VMware.VimAutomation.Core -Registered

try
    {
    switch ($powercli.Version.Major)
        {
        {$_ -ge 6}
            {
            Import-Module VMware.VimAutomation.Vds -ErrorAction Stop
            Write-Host "PowerCLI 6+ module imported"
            }
        5
            {
            Add-PSSnapin VMware.VimAutomation.Vds -ErrorAction Stop
            Write-Host "PowerCLI 5 snapin added; recommend upgrading your PowerCLI version"
            }
        default {throw "This script requires PowerCLI version 5 or later"}
        }
    }
catch {throw "Could not load the required VMware.VimAutomation.Vds cmdlets"}

Additionally, I’ve not had much luck with getting the VMware.VimAutomation.Vds module to auto load. As an example, Get-VDPortgroup is a cmdlet contained within the VMware.VimAutomation.Vds module, but it refuses to load unless I fire off an import-module or use get-help / get-command.

vds-no-autoload

This further highlights the need to test my scripts for failure scenarios, not successful ones. 🙂

Speed Testing

There was a comment below concerned with long load times for the module. I’ve recorded how long it takes in on my jump box (1 vCPU, 1 GB RAM) using Measure-Command below:

measure-module

The value is 1.7 seconds.

For comparison, here’s the snapin load time on my physical desktop which is still running PowerCLI 5.8.

measure-snapin

0.0009 seconds.