Using PowerCLI the Right Way: Make Your Own Git-Friendly PowerCLI Script Modules and Manifests

TL;DR – The git repo is here.

Actual list of commands available in the TOOLKIT. And yes, that is Anton Chekhov in the background there.

What? You thought all I could do was Ansible? I have all kinds of ideas . . . .

Like, my pontifications about the geolinguistic and historical origins of the present progressive tense . . . or to what extent Tolstoy’s anarcho-pacifism was an influence on post-modern thought . . . or is there such a thing as, “Douchedar“? . . . The answer to that last question, by the way, is yes.

But this is a tech blog, so we can talk about those topics later.

Instead, I want to share what I have done with VMware PowerCLI and how to collaborate with it effectively using git. I call it the VMWARE POWERCLI TOOLKIT. Not sure why I do that in all caps, but that’s what I call it.

I should mention that I didn’t invent the approach I am about to detail here – this is a synthesis of various approaches I have stolen consolidated together that, in my humble opinion, can lead to a more IaC and collaborative approach to the use of PowerCLI for your team. I am not really that tall; I stand on the shoulders of giants.

I would also be remiss if I did not mention the importance of using PowerCLI as a vSphere Admin. I could not do my job effectively without it. It makes me cringe to think of doing bulk tasks or ad-hoc reports without it. My challenge, however, through two employers, is finding a way to get others to use the toolkit, so you will notice that I have put a lot of work into ease of use. The features I include in this approach are mostly for encouraging other people to actually use the TOOLKIT.

The Details

The features of my approach, are as follows:

  1. A git repo (real world examples that I have built are here).
  2. A standardized approach for use of the PowerCLI module(s) that can be leveraged by multiple people.
  3. A standardized approach that includes Get-Help text for each Function you build as part of the toolkit.
  4. A standardized method for versioning with the use of a manifest file.
  5. A method for non-git users to leverage the PowerCLI Toolkit, even through new git commits to master.
  6. With in-line ForEach loops, you can run these commands against multiple objects at once for reporting or bulk automation. If you don’t know how to do an in-line ForEach loop, I recommend it highly. Looping is so important that if you get nothing else out of this post, you should at least pick up that skill, toot sweet! There is an example below. KEEP READING!

The Problem with Standalone Scripts

Standalone *.ps1 scripts are great. I use them all the time. But I have some issues with them. They are harder to standardize in collaborative environments, particularly with parameterization. It is also not a scalable approach. My prod-level TOOLKIT has 54 Functions, and to do them separately as separate *.ps1 files would be unwieldy. However, that is not a dealbreaker since one could also use dot sourcing, which is a legitimate counter-argument. And finally, using separate *.ps1 scripts is harder to document, especially with collaboration. I prefer to make a Modules file with each CMDLet written as a function call.

Instead I would recommend using a script module with a manifest. Using this method, you can create your own CMDLets that have a life of their own. Just be aware that you should use the proper Verb-Noun syntax for your CMDLets (function names), otherwise, you will be warned endlessly about it. Additionally, the names of the modules should be unique and not conflict with shipped Powershell modules or CMDLets.

Getting Started with the Use of Functions as CMDLets

Let’s start with a basic function. We’ll use the very first Function in the script module file MY.VMWAREKIT.psm1:

Function Connect-MYvCenters ([Parameter(Mandatory=$true)][string]$PathtovCentersFile) {
    This will connect to vCenters that are in a CSV file.
    Allows you to connect to "All MY vCenters specified in a csv file." Create a csv file with the vCenters by IP or FQDN to which you would like to connect.
    Connect-MYvCenters -PathtovCentersFile C:\Path\to\csvfile.csv
    Set-PowerCLIConfiguration -InvalidCertificateAction ignore -confirm:$false
    Set-PowerCLIConfiguration -DefaultVIServerMode Multiple
    $vCenters = Get-Content -Path $PathtovCentersFile
    $c = Get-Credential
    ForEach ($vc in $vCenters) {
        Connect-VIServer -Server $vc -credential $c -WarningAction 0 -ErrorAction SilentlyContinue

Simple, right? First of all, notice the commented block that contains Help Text. More on that later. Right now, just look at the Function call at the top with the mandatory parameter and the code starting with the Set-PowerCLIConfiguration. This is the first function in a list of additional functions (I believe 34 total if I recall) that will become your list of functions available in your module. Using the above as your guide, you should be able to create your own modules and add them into the list!

Importing the Module Using a PowerShell Module Manifest

You could simply Import the module as-is, but I recommend going one further and implementing the aforementioned companion manifest file. This has some very distinct advantages that go beyond this post, but my primary motivation for its use is versioning. Hopefully you are of the same IaC mindset as I am and agree with proper documentation and versioning . . . because, you know, I am always, like, THE BEST at documentation and versioning. . . .

Anyway, if you take a look at the MY.VMWAREKIT.psd1 manifest file, there is an item called “ModuleVersion”:

# Version number of this module.
ModuleVersion = '1.0.0'

As you add, improve, or make changes to your functions in the script module, you can increment your ModuleVersion to reflect those changes. If anything, you can work with someone else to see if they are running the latest version of the Module by having them type:

PS> Get-Command -Module MY.VMWAREKIT

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Function        Connect-MYvCenters                                 1.0.0      MY.VMWAREKIT
Function        Get-MYAllHardwareDriverInfo                        1.0.0      MY.VMWAREKIT
Function        Get-MYBIOSInfo                                     1.0.0      MY.VMWAREKIT
Function        Get-MYCDandDVD                                     1.0.0      MY.VMWAREKIT
Function        Get-MYCiscoDiscoveryInfo                           1.0.0      MY.VMWAREKIT
. . . . .

What’s that you ask? How do you know what to increment and by how much? Well, you can use semantic versioning, and I kind of sort of stick close to that. I mostly increment the Minor version, but that’s just me. As long as you know what qualifies as “current” then you should be good. OH! And don’t forget to increment the version and edit any text in the file as you make changes.

Now that you know what a Manifest is, let’s actually use it. It’s simple actually. You are just going to Import the Manifest file, not the script module file:

PS> Import-Module ./MY.VMWAREKIT.psd1 -Global -Force -Verbose

Because the Manifest file has the RootModule = 'MY.VMWAREKIT.psm1' in it, this will import the Module with the metadata included in the manifest file. You can see some of the relevant manifest information if you type the Get-Command -Module MY.VMWAREKIT as above.

You can also create your own manifest file using the New-ModuleManifest CMDLet, but the one I have in the repo should be a good start – just realize you will need to use a new GUID. Hint: use the New-GUID CMDLet.

Including Help Text

In the TestandSupport directory, there is a template set of Help Text headings in the HelpText.ps1 file:




These are not the only items, but the above, in my humble opinion, are the bare minimum items that people expect. If you take a look at the first block of code in the Connect-MYvCenters example from above, you can see an example. If you have each of those filled out, then when someone types Get-Help for your Module function, they will see something that is hopefully very familiar to them, especially if they have used Powershell before:

PS> Get-Help Connect-MYvCenters -detailed


    This will connect to vCenters that are in a CSV file.

    Connect-MYvCenters [-PathtovCentersFile] <String> [<CommonParameters>]

    Allows you to connect to "All MY vCenters specified in a csv file." Create a csv file with the vCenters by IP or FQDN to which you would like to connect.

    -PathtovCentersFile <String>

        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see
        about_CommonParameters (

    -------------------------- EXAMPLE 1 --------------------------

    PS > Connect-MYvCenters -PathtovCentersFile C:\Path\to\csvfile.csv

Special Gifts: profile.ps1 and GetLatestToolkitfromRepo.ps1

I have included my own profile.ps1 file in the StandaloneScripts directory. Use it as you see fit. It imports the module for me once I open Powershell Core on my Mac and prompts me for my most commonly-connected vCenters.

I have also included something I made for non-git users. It’s a simple script that can be run repeatedly in their running directory. It will download the* files and import them into Powershell for them. Be sure to alter the uris to reflect the location of your raw* files. This script is run like any other standalone script. What it will do is look for any existing* files and delete them. If there are none, it will simply continue and download the latest versions of those files from your git repo.

IMPORTANT WARNING: DO NOT run the GetLatestToolkitfromRepo.ps1 in your master git repo, otherwise the files will get deleted. This should NEVER be run in the master repo directory.

Running the Functions and Using In-line ForEach Loops

Running the Functions are straightforward. Most of them use the Host as the single object to run against, but there are exceptions. Use the Get-Help command if needed.

More importantly, however, is the ability to run these functions against multiple hosts. That’s the real power of this method. The steps are in the file, but here they are:

  1. Once connected to your vCenter(s), create a variable that contains the Hosts/Clusters/VMs you want to run the command on.
  2. Then, use an inline ForEach loop to execute across each Host/VM/Cluster.
  3. Additionally, you can set the ForEach loop to a variable as well (I call this iterative variables) which would allow for additional actions, including exporting the results as a csv-file report.
  4. That process looks something like this, using the Get-MYHostConfiguration Module Function:
PS> $AllHosts = Get-VMHost | Sort-Object Name
PS> ForEach ($h in $ALLHosts) { Get-MYHostConfiguration -ESXiHost $h }

or to iterate these for a CSV report . . .

PS> $HostConfigReport = ForEach ($h in $ALLHosts) { Get-MYHostConfiguration -ESXiHost $h }
PS> $HostConfigReport | Export-CSV -Path /path/to/report/location

If you think about the fact that you can connect to multiple vCenters, you can possibly perform these actions throughout your entire vSphere infrastructure! Need to set syslog to a different value on every single host? Use the Set-MYSyslogValue in a ForEach loop. BOOM GOES THE DYNAMITE!

Look Around and Explore!

Through all of this, I haven’t even mentioned the list of Functions included in the module I’ve shared. It’s mostly stuff that I do all the time, and as you can imagine, not all of it is mine. One of my favorites isn’t mine . . . check out the Get-MYvMotionHistory CMDLet in the module. Very useful!

All of the above leads to a more collaborative approach. With the Script module/manifest method, you have a scalable, standardized, all-in-one-file approach to your PowerCLI methodology. The versioning included ensures everyone’s on the same page. The Help Text allows for users who consume your Automation to have something familiar so that use of your modules is easy to use and more friendly. With the in-line ForEach loop you can run this on multiple objects easily and repeatedly. And finally, with the GetLatestToolkitfromRepo.ps1 people who are unfamiliar with git can use the toolkit without issues.

Questions? Hit me up on twitter @RussianLitGuy or email me at I would love to hear from you.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s