Setting up a powershell development environment for non-programmers

In most of my projects, powershell and its vendor-specific modules such as VMware's powercli is usually a necessity to work effectively. In addition, most customers appreciate it greatly if you can leave them with a handful of examples on how to run standard procedures through a script, even though it might not always be part of the deliverables.

Most of the time however, those scripts are written as one-offs, tend to require major rewriting for a different use case and don't have any kind of error handling or input validation. As such, i usually tend to rewrite a new script from scratch for a new project, which kind of defeats the purpose of the reusability of scripts.

Last week i sat down and decided to think up a proper development environment for powershell related tasks. While it's only a beginning, it does help with writing clean code allowing you to spend less time coding the next time.

For my current setup i'm using the following tools:

  • Visual studio code and the Visual studio powershell extension - As powerGUI has really reached the end of the line as far as support goes, i had to find another tool. Normally i'm used to things like sublime text for MacOS which is an amazing editor, but unfortunately the powershell input is quite limited.
  • Pester - A great testing framework for powershell that enables you to perform test-driven development on powershell. Unfortunately, you won't have the time for this in each project and if a script is truly one-off you'd have to question the added benefits of TDD, but in general i do try to write a test for each script.
  • PS script analyzer - A wonderful set of linting tools for powershell. In my opinion, everyone should be using this, as it allows you to write clean, standardized code instead of unique snowflakes. It also integrates with VS.Code and you can use it in unit tests in Pester.
  • Bitbucket - Saving all your scripts in a version control is the most essential part of proper coding. Without version control you'll end up with scripts all over the place, various versions modified for each use case, and you'll usually lose track of that "where did i place that?"-script you desperately need. Using version control also forces you to write reusable code because you don't want to end up with a large amount of branches for each use case.
  • A powershell template. Using a template file allows you to standardize parameters, help functions, and more. By using a template you'll create the same file over and over again, as well as making it easier for you to write clean code since filling out the supporting comments and information will be significantly easier.

For my current template i'm using a template based on the powershell template from 9to5it.com. Below is an example of an empty script i'm using to automate the management of IP sets in NSX.

#requires -version 4
<#  
.SYNOPSIS
  This script enables the creation of NSX IP sets based on information from a csv file. 

.DESCRIPTION
  This script enables the creation of NSX IP sets based on information from a csv file. 

.PARAMETER csv
This parameter sets the CSV file from which to import your NSX IP sets. It should be in the following format:

name;ipobject1,ipobject2,ipobjectn;description

.PARAMETER nsxmanager
This parameter sets the hostname of the NSX manager used to connect to your NSX environment. 


.INPUTS
  None

.OUTPUTS Log File
  The script log file stored in C:\Windows\Temp\create-ipsets.log

.NOTES
  Version:        1.0
  Author:         Sjors Robroek
  Creation Date:  14-06-2016
  Purpose/Change: Initial script development

.EXAMPLE
  import-ipset -nsxmanager nsxm.domain.tld -csv C:\temp\ipsets.csv


#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

#Set Error Action to Silently Continue
$ErrorActionPreference = 'stop'

#Import PSLogging Module
Import-Module PSLogging

#----------------------------------------------------------[Declarations]----------------------------------------------------------

#Script Version
$sScriptVersion = '1.0'

#Log File Info
$sLogPath = 'C:\Windows\Temp'
$sLogName = 'create-ipsets.log'
$sLogFile = Join-Path -Path $sLogPath -ChildPath $sLogName

#-----------------------------------------------------------[Functions]------------------------------------------------------------


Function import-ipset {  
  Param ( 
    [Parameter(Mandatory=$True,
    ValueFromPipeline=$True,
    ValueFromPipelineByPropertyName=$True,
      HelpMessage='please enter the path to your CSV file. ')]
    [validatescript({ test-path $_ })]
    [string]$csv
    ,
    [Parameter(Mandatory=$True,
    ValueFromPipeline=$True,
    ValueFromPipelineByPropertyName=$True,
      HelpMessage='Please enter the NSX manager FQDN')]
    [validateScript({ (resolve-dnsname -dnsonly -name $_) -ne $null })]
    [string]
    $nsxmanager
  )

  Begin {
    Write-LogInfo -LogPath $sLogFile -Message '<description of what is going on>...'
  }
  Process {
    Try {

    }

    Catch {
      Write-LogError -LogPath $sLogFile -Message $_.Exception -ExitGracefully
      write-error hallo
      Break
    }
  }

  End {
    If ($?) {
      Write-LogInfo -LogPath $sLogFile -Message 'Completed Successfully.'
      Write-LogInfo -LogPath $sLogFile -Message ' '
    }
  }
}

#-----------------------------------------------------------[Execution]------------------------------------------------------------
<#  
Start-Log -LogPath $sLogPath -LogName $sLogName -ScriptVersion $sScriptVersion  
#Script Execution goes here
Stop-Log -LogPath $sLogFile  
#>

Using PS script analyzer for linting

Using PS script analyzer is actually as simple as running Invoke-ScriptAnalyzer in the directory where you want to analyzer scripts. There are quite some other options, such as ignorning specific rules, running only a specific script, and writing your own rules, all of which can be found in the PSscriptAnalyzer documentation.

Using pester for test-driven development

When you want to do true test-driven development, your code should always be defined a test first. Chances are, if you can't write a test for it, you probably don't understand what you want your code to do exactly.

First you'd need to install pester. Fortunately, if you have a recent version of powershell or nuget installed, installing pester is as simple as running Install-Package Pester

After we've created our powershell script and functions, we'll create the same file with tests appended to it. So if your file is called configure-ha.ps1, your pester file would be called configure-ha.tests.ps1.

Let's say we want to test the HA status of our cluster. A simple test in pester should look like this. Since pester will need access to the configuration in your scripts, we'll need to use global variables unfortunately. Make sure that your script sets the global variable if needed, or prompt for them in pester.

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
. "$here\$sut"
Describe 'Cluster has HA configured' {  
  It "Tests if cluster has HA enabled" {
    (get-cluster $global.cluster).HAEnabled  | Should be $true
  }
  It "Tests if HA isolation response is set to PowerOff {
    (get-cluster $global.cluster).HAisolationResponse | Should be "PowerOff"          
  }
}

To actually run the tests, we'll need to invoke pester in the directory your scripts are located in. Run the command invoke-pester and it will start running all the unit test files it can find.

And with only the above code, we've created a unit test that will test if your cluster has HA enabled, and if the isolation response is set correctly. Pester will parse your powershell file and run the tests as shown in the Describe block and give you a very concise output of all the tests ran. And this is only a very small sample of what pester can do for you to validate your code. If you're the kind of person that regularly writes powershell code that others need to use, or if you want to start automating more daily operational tasks, i'd recommend taking a look at pester and setting up a proper development environment, even if you're not a developer.