For this article, we’re going to talk about integration tests with Pester. Although Pester was built to be a unit testing framework that examines actual code, execution integration tests are the next “layer” of testing.

When you write a PowerShell script to remove a file, delete a registry key, and restart a computer or start up a service, and it doesn’t show an error, do you consider that a success? Are you sure that the script did what you intended? It might have, but it also may have simply skipped over entire parts without you even knowing it! To be sure that the code did what you intended, you must write out your intentions in code and in the PowerShell world. This means writing Pester tests. In my previous post in my Pester series, I covered unit tests, today we will take a closer look at integration tests.

Integration tests don’t necessarily care about how the code was executed; instead, they address what the state of the environment is after the code has been executed.

Let’s dive into an example of how to build an integration test for a particular function. Let’s say I have function that looks something like this:

function Enable-RemoteDesktop
 {
     [CmdletBinding()]
     param
     (
         [Parameter(Mandatory)]
         [string]$ComputerName
     )
     $sb = { Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -name 'fDenyTSConnections' -Value 0;
 Enable-NetFirewallRule -DisplayGroup 'Remote Desktop'
 }
     Invoke-Command -ComputerName $ComputerName -ScriptBlock $sb
 }

This function remotely turns on remote desktop on a Windows computer. It sets a registry value called fDenyTsConnections to a DWORD value of 1 and enables a firewall rule display group called “Remote Desktop.”

What I just explained is the first step: defining the result of the function after it has been executed or what it should have done.

Next, you must figure out how to read the components of what the function changed. In this instance, we’re changing a registry value and enabling a firewall rule. In this function, I'm changing the values. However, I now need to read the values. To read the registry value, I can write some code that might look like this:

$ComputerName = 'REMOTECOMPUTER'
 Invoke-Command -ComputerName $ComputerName -ScriptBlock {(Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server').fDenyTSConnections }

This will return the registry value of fDenyTSConnections on the remote computer REMOTECOMPUTER, which presumably will be the computer that I just ran the function against.

Returning the registry value of fDenyTSConnections on the remote computer REMOTECOMPUTER

Next, I need to figure out how to read the firewall rule I just changed. I can do this by using the Get-NetFirewallRule cmdlet and checking the state of the Enabled property.

(Invoke-Command -ComputerName $ComputerName -ScriptBlock { Get-NetFirewallRule -DisplayGroup 'Remote Desktop' }).Enabled
Using the Get-NetFirewallRule cmdlet and checking the state of the Enabled property to read the firewall rule

Using the Get-NetFirewallRule cmdlet and checking the state of the Enabled property to read the firewall rule

In a display group, there are multiple rules we’re changing. We need to ensure that each is enabled. You can see an example of the three “True” values above. This means all three rules have their Enabled property set to True, indicating that our function was successful with this component.

We now have all the code we need to read the values that our function changed. Let’s add this code to a Pester test. If you’re not familiar with Pester, and if you write any form of serious PowerShell code, I highly encourage you to learn more about it.

When testing PowerShell functions, it’s always good practice to name your Pester describe block the same name as your function. This ensures that you know what test correlates to what function.

describe 'Enable-RemoteDesktop' {
 }

The describe block is the container. In this container, I need to add the individual integration tests. These tests will be contained in It blocks. In our example, I have two: testing the registry value and testing the firewall rule display group. Since I already have the code, I can just create the It blocks and place the code inside.

Notice that the It blocks contain a descriptive name of what each test is doing:

describe 'Enable-RemoteDesktop' {
     it 'enables the Remote Desktop firewall rule display group' {
         Invoke-Command -ComputerName $ComputerName -ScriptBlock {(Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server').fDenyTSConnections }
     }
     it 'sets the fDenyTSConnections registry value to 0' {
         (Invoke-Command -ComputerName $ComputerName -ScriptBlock { Get-NetFirewallRule -DisplayGroup 'Remote Desktop' }).Enabled
     }
 }

I now have the code necessary for the checks, but I need to assert what my intentions are. I need to define what I expect each of these commands to output. I can do this by using the should assertion. This allows me to specify a value to compare against and result in a Pass/Fail scenario.

I’ll go ahead and add my expected results with my should assertions.

describe 'Enable-RemoteDesktop' {
     it 'enables the Remote Desktop firewall rule display group' {
         Invoke-Command -ComputerName $ComputerName -ScriptBlock {(Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server').fDenyTSConnections } | should be 0
     }
     it 'sets the fDenyTSConnections registry value to 0' {
         $result = (Invoke-Command -ComputerName $ComputerName -ScriptBlock { Get-NetFirewallRule -DisplayGroup 'Remote Desktop' }).Enabled 
         Compare-Object $result @($true,$true,$true) | should be $null
     }
 }

My tests are done! Because I need to specify a computer name to run my tests against, I’ll need to configure my test to accept a ComputerName parameter.

Param($ComputerName)
 describe 'Enable-RemoteDesktop' {
     it 'enables the Remote Desktop firewall rule display group' {
         Invoke-Command -ComputerName $ComputerName -ScriptBlock {(Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server').fDenyTSConnections } | should be 0
     }
     it 'sets the fDenyTSConnections registry value to 0' {
         $result = (Invoke-Command -ComputerName $ComputerName -ScriptBlock { Get-NetFirewallRule -DisplayGroup 'Remote Desktop' }).Enabled 
         Compare-Object $result @($true,$true,$true) | should be $null
     }
 }

I'll now save this test script as Enable-RemoteDesktop.Tests.ps1 and run Invoke-Pester. Notice that I’m using the path to the script and passing my ComputerName to the test script as well.

Invoke-Pester –Script @{'Path' = 'C:\Enable-RemoteDesktop.Tests.ps1';'Parameters' = @{'ComputerName' = 'REMOTECOMPUTER'}}
Using the path to the script and passing my ComputerName to the test script

Using the path to the script and passing my ComputerName to the test script

I can only now truly be sure my Enable-RemoteDesktop function indeed did change the values I intended.

Subscribe to 4sysops newsletter!

For a full breakdown on what's possible with the PowerShell testing framework Pester check out The Pester Book by myself and Don Jones.

6 Comments
  1. Ben 5 years ago

    "it 'enables the Remote Desktop firewall rule display group'" and "it 'sets the fDenyTSConnections registry value to 0'" are swapped throughout this entire example.

    • Author

      You do need to set fDenyTSConnections to 0 to enable remote desktop. By default, it's set to 1.

      • Ben 5 years ago

        Param($ComputerName)
        describe 'Enable-RemoteDesktop' {
         This is describing the registry but the it statement says firewall ->   it 'enables the Remote Desktop firewall rule display group' {
               Invoke-Command -ComputerName $ComputerName -ScriptBlock {(Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server').fDenyTSConnections } | should be 0
           }
         This is describing the firewall but the it statement says registry ->      it 'sets the fDenyTSConnections registry value to 0' {
               $result = (Invoke-Command -ComputerName $ComputerName -ScriptBlock { Get-NetFirewallRule -DisplayGroup 'Remote Desktop' }).Enabled
               Compare-Object $result @($true,$true,$true) | should be $null
           }
        }

  2. Marc van Gorp 5 years ago

    Hi Ben,

    My personal best practice is to explicitly verify every change I make directly in the PowerShell script itself.

    So when I change a registry key my next step is to read that registry key and validate if the retrieved result matches the expected result. If this isn't the situation my script will generate an error or warning depending on the impact.

    My question: Should I remove these explicit checks from my scripts and create a Pester test for this, and if so, why?

    avatar
    • Author

      I wouldn't add all of that validation inside of the script itself. The script is meant to apply settings. I would definitely pull all of that out into a set of Pester tests. Putting some validation checks in your scripts is great but it sounds like you might be overdoing it. Adding more code adds more complexity and it's important to keep scripts as simple as possible. If those tests are separated out into a Pester test you have the opportunity to do unit testing as well as some of the integration testing you're talking about.

  3. For this example, I don't really see a point of a pester test?

    Since your pester test makes the change, the test becomes the script itself, no?

    And in this example, you'd have to know in advance what the value for the Registry Key was, to know if your function actually did anything. If it was already set to 0, but your function failed for whatever reason, it would return a false positive.
    Personally, if I wrote a script for this, I would much rather check what the value was, change it if needed, and then check it afterwards so I know that it was changed.

    Maybe I'm missing the point, but since Pestering actually carries out the work that it should only be *testing*, what is the actual point?
    Maybe if I'm only getting back information, but not when it actually makes changes(?)...

Leave a reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2021

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account