- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
When a software developer is asked about testing, they'll most likely talk about unit, integration, functional, or acceptance testing. These are the kinds of tests that have been around for decades and are considered formal testing strategies. Then, one day, system administrators decided they wanted in on the coding action, and infrastructure development was born. I'm going to define an "infrastructure developer" as anyone that writes code to provision and manage any kind of infrastructure.
Infrastructure code, by its nature, changes stuff in an environment. It provisions VMs, installs software, copies files, and so on. It's advisable to write unit tests to check the code itself, integration tests to ensure the code changed the stuff you intended, and acceptance tests to test the result. However, there's a gray area in there that's like a combination of integration and acceptance testing; I'm calling this infrastructure testing.
Infrastructure testing is primarily testing the result of infrastructure code or just ensuring the infrastructure works as expected; no code involved at all. Think of infrastructure testing as simply writing code to ensure servers are online, registry keys are created in the right spot, files are where they're supposed to be, user accounts are created correctly, etc.
To demonstrate infrastructure testing, let's cover a common task: provisioning new users in Active Directory. A PowerShell script this might look something like this:
[string]$FirstName = 'Joe' [string]$LastName = 'Blow' [string]$Department = 'HR' [string]$Title = 'VP' [string]$DefaultPassword = 'p@$$w0rd12345' ## Don't do this...really $Username = "$($FirstName.SubString(0, 1))$LastName" #region Create the new user $NewUserParams = @{ 'UserPrincipalName' = $Username 'Name' = $Username 'GivenName' = $FirstName 'Surname' = $LastName 'Department' = $Department 'SamAccountName' = $Username 'AccountPassword' = (ConvertTo-SecureString $DefaultPassword -AsPlainText -Force) 'Enabled' = $true } New-AdUser @NewUserParams #endregion
You can see that I'm just assigning a few variables to some user properties and then running New-AdUser to create the user account. Let's say I run this and I get no output. No errors? Great! It worked! Maybe. How do you know it worked? Just because you didn't see a sea of red text doesn’t necessarily mean it created the account. For all we know, you might have had $ErrorActionPreference inadvertently set to SilentlyContinue. It's important that we test the result of this code. Let's build an "infrastructure" test with Pester.
Since this script creates an AD user, I'll name the describe block accordingly.
describe 'New AD User Script' { }
Now comes the fun part: actually creating the It blocks (the tests themselves). When creating an infrastructure test, it's important to ask yourself: "What does this code actually do?" We know it creates an AD user. Sure. But we must get more granular than that. What is this code changing in my environment? It's creating an AD object, and objects have properties. We need to ensure not only that the user account was created but also that the object that was set up has all of the properties we expect.
Spotting the properties in this script is easy. Since it's a single command, we'll just look at the parameters like FirstName, LastName, etc. However, it's not so easy in larger scripts. The key is to separate out "functional" code and logic from the actual data that's changing. Indicators such as defined parameters, variables, etc. are typically a safe bet to test.
In this example, I'm going to ensure an AD user was created, and the following attributes were defined correctly:
- FirstName
- LastName
- Username
- Department
- Enabled
To be as verbose as possible, I'm going to create separate tests for each attribute. This isn't necessarily required, but it allows you to easily see what's being tested. But first, we'll need to ensure that the user object was created. After all, if a user can't be found, we're not going to be able to test the properties of that object.
We'll first define each of the expected values of each attribute so they can be referenced later.
describe 'New AD User Script' { $expectedValues = @{ 'FirstName' = 'Adam' 'LastName' = 'Bertram' 'Username' = 'abertram' 'Department' = 'HR' 'Enabled' = $true } }
I like to define all expected values early in a hash table so it makes them explicit and easily changeable.
Next, I'll grab the user account:
describe 'New AD User Script' { $expectedValues = @{ 'FirstName' = 'Joe' 'LastName' = 'Blow' 'Username' = 'jblow' 'Department' = 'Accounting' 'Enabled' = $true } $actualUser = Get-AdUser –Filter "samAccountName -eq '$($expectedValues.Username)'" } Notice that I'm not even in an It block yet. I'm just gathering everything to test first. But now that I have the actual object, I'll then create tests for each of the attributes that I'd like to ensure are set. describe 'New AD User Script' { $expectedValues = @{ 'FirstName' = 'Joe' 'LastName' = 'Blow' 'Username' = 'jblow' 'Department' = 'HE' } $actualUser = Get-AdUser –Filter "samAccountName -eq '$($expectedValues.Username)'" –Properties Department it 'creates an AD user with the right username' { $actualUser.samAccountName | should be $expectedValues.Username } it 'creates an AD user with the right first name' { $actualUser.givenName | should be $expectedValues.FirstName } it 'creates an AD user with the right last name' { $actualUser.surName | should be $expectedValues.LastName } it 'creates an AD user with the right department' { $actualUser.Department | should be $expectedValues.Department } it 'creates an AD user that is enabled' { $actualUser.Enabled | should be $true } }
I will then save this in a Tests file and run it.
Save the script in a Tests file and run it
Oops! Notice that it correctly created everything except for the department. Looking back up at the original script, it looks like I fat-fingered the department. I'm glad I created the test! If not, this would have gone unnoticed.
Subscribe to 4sysops newsletter!
If you're not writing Pester tests to test infrastructure code, start now. You can see that it's easy to do and might just save you from a lot of headaches in the future. For more information on infrastructure testing and Pester, in general, check out The Pester Book by myself and Don Jones.