Tag Archives: pester

The Pester people don’t really recommend this, but, I find it can be really helpful sometimes. What I’m talking about is dynamically creating assertions inside of a Pester test using PowerShell. While I think you should strive to follow best practices, sometimes what’s best for you isn’t always a best practice, and as long as you know what you’re doing, I think you can get away with bending the rules sometimes. Don’t tell anyone I said that.

Overview

Pester and PSScriptAnalyzer are both fundamental tools for testing the effectiveness and correctness of PowerShell scripts, modules, and other PowerShell artifacts. While it is relatively convenient and straightforward to run these tools on a local development workstation, and even on owned/on-prem testing servers, it is somewhat more complicated to execute these tests in your own Microsoft-hosted Visual Studio Team Services environment.

Pester is an open source domain specific language developed originally by Dave Wyatt, which enjoys contributions from a variety of prominent members of the PowerShell community, as well as Microsoft employees on the PowerShell product team. Microsoft is a big enough fan of Pester that it comes with Windows 10, and reference it frequently in talks and written material. Pester is used for PowerShell unit testing.

PSScriptAnalyzer is a static code checker for PowerShell modules and scripts that checks the quality of code by comparing it against a set of rules. The rules are based on PowerShell best practices identified by the PowerShell team at Microsoft and the community. It is shipped with a collection of built-in rules but supports the ability to include or exclude specific rules, and also supports custom rule definitions. PSScriptAnalyzer is an open source project developed originally by the PowerShell team at Microsoft.

Lots of DevOps teams use the above tools together, along with their internally generated standards and style guide, to ensure that PowerShell code that is released into any environment meets their standards. By using Pester to ensure that a piece of code performs the tasks required, using PSScriptAnalyzer to inspect code for general best practice violations, and using a peer review process to validate that code conforms to our standards and style guidelines, you can rigorously test and ensure the quality and functionality of all PowerShell code that you produce.

As part of a PowerShell Release Pipeline, you may store your code in the source control portion of VSTS, hosted by Microsoft. I’d suggest you use the automated build and release components of VSTS to execute a series of tasks before deploying, and to deploy PowerShell code. Two of these tasks are running Pester tests and PSScriptAnalyzer. As a standard, don’t release builds if any part of either of these two tests fail.

In previous versions of VSTS, the hosted build service ran PowerShell 4.x. Because installing modules from the PowerShell Gallery (to get Pester and PSScriptAnalyzer module files so they may be run) requires PowerShell 5.0 or higher, it was necessary to use a third-party configured build step or perform some other hijinks that possibly compromised the integrity of a build. Now that VSTS runs PowerShell 5.0, we can run Pester, PSScriptAnalyzer, and many other helpful modules without exporting them with our other code, or using third-party build steps.

Hole 1

The challenge was to get all the security updates installed on the local computer in the last 30 days and return the results in the form of a [Microsoft.Management.Infrastructure.CimInstance] object (or an array of them).

The winner of this hole is Simon Wåhlin. Here is their 46 character submission.

PowerShell

1

gcim(gcls*ix*|%*mC*e)|?I*n-gt((date)+-30d)

gcls *ix* gets the CimClass win32_quickfixengineering and % *mC*e gets the CimClassName property. gcim is an alias for Get-CimInstance which, as per the previous section, is getting the win32_quickfixengineering class. The results are piped into the where-object cmdlet where the property matching the pattern I*n (which happens to be InstalledOn) is greater than the current date, minus 30 days.

Hole 2

The challenge was to get the top ten file extensions in c:\windows\system32, only return 10 items and group results by extension.

The winner of this hole is Simon Wåhlin again. Here is their 42 character submission.

PowerShell

1

(lsC:\*\s*2\*.*|%E*n|group|sortc*-d)[0..9]

ls c:\*\s*2\*.* means Get-ChildItem where the path is c:\<any directory>\<a directory matching s*2>\<files, not directories> and this pattern only matches the path c:\windows\system32\<files>. This is piped into the foreach-object cmdlet to retrieve the property that matches the pattern E*n,which is the Extension property. The extensions are piped into the sort-object cmdlet, sorted by the property that matches the pattern c*, which is count, and returned in descending order. This is an array, and the items in positions 0-9 are returned.

There were shorter submissions for this hole that didn’t explicitly target c:\windows\system32 and therefore missed the challenge. You could not assume we were already on c: or running as admin, etc. Some solutions included folders in the results which also missed the challenge.

Hole 3

The challenge was to get all the active aliases that are fewer than three characters long and do not resolve to a Get- command. For this hole, even though it wasn’t in the Pester test, you had to assume that non-standard aliases might be on the system. That’s why we specifically mentioned that we didn’t want you to return aliases that resolve to Get-*, and the Pester test checked the ResolvedCommand.Name property of the aliases you returned.

To break some submissions that didn’t check what the aliases resolved to, you could just run New-Alias x Get-ChildItem to create a new alias of ‘x’ that resolves to Get-ChildItem.

The winner of this hole is EdijsPerkums. Here is their 24 character submission.

PowerShell

1

gal?,??|?Di*-NotmGet

Get-Alias is passed an array of regex patterns, ?,?? which corresponds to one and two characters. The results are piped into the where-object cmdlet to isolate aliases whose property that matches the pattern Di* (DisplayName) doesn’t match Get.

Congratulations to all the winners! We will be in touch to get you your prizes. We hope you all had fun with this mini-competition. Don’t forget to check out all the terrific material from the PowerShell 10 Year Anniversary on Channel 9!

If you don’t know what Pester is, it’s a framework for running unit tests and validating PowerShell code. Also, it’s awesome. In May I finally dipped my toe in the water with a pretty simple test for a REALLY simple function. I’m not going to go into a world of detail on how exactly all my Pester code works because there are tons of guides for that. What I’m going to do instead is provide a quick run down of what I came up with.

First things first, I need a function to validate.

PowerShell

1

2

3

4

5

6

7

8

functionWrite-SomeMath

{

param(

[int]$First,

[int]$Second

)

return$First+$Second

}

I guess that will work. Write-SomeMath takes two integers and returns their sum. Hardly a breathtaking display of complexity and function but it will do just fine for this example.

Now I need to install Pester. The easiest way to do this is using the PSGet module in PowerShell 5.0 to get it from PowerShellGallery.com.

PowerShell

1

2

Install-ModulePester-ScopeCurrentUser-Force

Import-ModulePester

The next thing I need is a Describe block.

PowerShell

1

2

3

Describe'GoofingWithPester.ps1'{

}

This Describe block will contain and – you guessed it – describe the tests (I just used my filename) and provide a unique TestDrive (check out the getting started link).

Now I need a Context block.

PowerShell

1

2

3

4

5

Describe'GoofingWithPester.ps1'{

Context'Write-SomeMath'{

}

}

I’m further grouping my tests by creating a Context here for my Write-SomeMath function. This could have been named anything.

Now, I could start with a bunch of tests, but I want to show off a particular feature of Pester that allows you to pass an array of different test cases.

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

Describe'GoofingWithPester.ps1'{

Context'Write-SomeMath'{

$testcases=@(

@{

fir=1

sec=2

exp=3

test='1 and 2'

},

@{

fir=3

sec=6

exp=91#wrong on purpose

test='3 and 6 (wrong on purpose)'

},

@{

fir=4

sec=6

exp=10

test='4 and 6'

}

)

}

}

All I did was define an array called $testcases which holds an array of hash tables. It’s got the first number, second number, expected result and a name of what we’re testing. Now I can pass this entire array to a test rather than crafting different tests for all of them individually.

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

Describe'GoofingWithPester.ps1'{

Context'Write-SomeMath'{

$testcases=@(

@{

fir=1

sec=2

exp=3

test='1 and 2'

},

@{

fir=3

sec=6

exp=91#wrong on purpose

test='3 and 6 (wrong on purpose)'

},

@{

fir=4

sec=6

exp=10

test='4 and 6'

}

)

It'Can add <test>'-TestCases$testcases{

param($fir,$sec,$exp)

Write-SomeMath-First$fir-Second$sec|Should Be$exp

}

}

}

This is an It block which is what Pester calls a test. I’ve named it “Can add <test>” and it will pull the “test” value from the hashtable and fill it in. Cool! I’m using the -TestCases parameter to pass my array of test cases to the It block. Then I’ve got parameters inside the test for my first value, second value and expected outcome. I execute Write-SomeMath with the values pulled from my test cases and pipe the result to “Should Be” to compare the outcome to my expected outcome.

Now, just one more test for fun. What if I don’t pass an integer to my function?

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

Describe'GoofingWithPester.ps1'{

Context'Write-SomeMath'{

$testcases=@(

@{

fir=1

sec=2

exp=3

test='1 and 2'

},

@{

fir=3

sec=6

exp=91#wrong on purpose

test='3 and 6 (wrong on purpose)'

},

@{

fir=4

sec=6

exp=10

test='4 and 6'

}

)

It'Can add <test>'-TestCases$testcases{

param($fir,$sec,$exp)

Write-SomeMath-First$fir-Second$sec|Should Be$exp

}

It'Detects wrong datatypes'{

{Write-SomeMath-First9-Second'cat'}|Should throw

}

}

}

Another It block for detecting wrong datatypes. I pipe the result into Should throw because my function should throw an error. For this to work properly, the code I’m testing has to be wrapped in a scriptblock, otherwise the thrown error will occur and be trapped in my function.

Here’s the outcome when I run this file!

Pester Results

Pretty cool. My first test passes, the second one fails and tells me why, the third and fourth tests pass. The fourth one is especially interesting. The function FAILED but because the test said it SHOULD FAIL, the test itself passed.

Just moved in

I just moved my blog from one hosting provider to another. If you see a broken image or some other weirdness, I’d love if you let me know by email at thmsrynr@outlook.com or hit me up on Twitter @MrThomasRayner.