
close
close
If you are an experienced PowerShell user, chances are you have heard of Pester. This is an open source project that Microsoft started shipping as part of Windows 10. I’m not going to try and teach Pester here, although it really isn’t that difficult to pick up. But I wanted to show you some ways to use Pester that you might not have considered.
Pester is typically designed for software testing. You build a test script to run through different parts of your code and Pester validates it. This is a quick way to verify you haven’t broken something while introducing something new.
#requires -version 5.0 $computername = "CHI-P50" Describe $Computername { It "should have Hyper-V Feature installed" { Get-windowsFeature -Name Hyper-V -ComputerName $Computername | Should Be $True } It "Hyper-V service should be running" { $s = Get-Service -Name vmms -ComputerName $computername $s.status | Should Be "running" } It "DNS service should be running" { $s = Get-Service -Name dns -ComputerName $computername $s.status | Should Be "running" } It "Should have 25% free space on drive C:" { $c = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "deviceid = 'c:'" -ComputerName $computername ($c.FreeSpace/$c.size)*100 | Should BeGreaterThan 25 } It "Should have 10% free space on drive E:" { $e = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "deviceid = 'e:'" -ComputerName $computername ($e.FreeSpace/$e.size)*100 | Should BeGreaterThan 10 } }
I could run this as a regular PowerShell script. But I prefer to use the Invoke-Pester cmdlet.
Invoke-Pester c:\scripts\pester-chi-p50.ps1 -PassThru | Select -ExpandProperty TestResult | Where {-not $_.passed} | foreach { Send-MailMessage -Subject "$($_.Describe) Test Failure" -body ($_ | Out-String) }
#use pester to validate servers or other infrastructure <# Read in data to test per server. Could be read from an XML file Invoke-Pester <this file> #> $all = [pscustomobject]@{ Computername = "CHI-P50" Services = @{Running = "vmms","vmcompute"},@{Stopped= "RemoteRegistry","Spooler"} Features = @{Installed = "Hyper-V","Containers","Windows-Server-Backup"},@{NotInstalled = "Direct-Play","Internet-Print-Client"} Versions = @{PowerShell = 5; Windows = 2016} }, [pscustomobject]@{ Computername = "CHI-DC04" Services = @{Running ="DNS","ADWS","KDC","NetLogon"},@{Stopped= "RemoteRegistry","Spooler"} Features = @{Installed = "DNS","AD-Domain-Services","Windows-Server-Backup"},@{NotInstalled = "SMTP-Server","Internet-Print-Client"} Versions = @{PowerShell = 5; Windows = 2012} }, [pscustomobject]@{ Computername = "CHI-HVR2" Services = @{Running ="vmms"},@{Stopped= "RemoteRegistry"} Features = @{Installed = "Hyper-V","Windows-Server-Backup"},@{NotInstalled = "SMTP-Server","Internet-Print-Client"} Versions = @{PowerShell = 4; Windows = 2012} } foreach ($item in $all) { Describe $($item.Computername) -Tags $item.Computername { $computername = $($item.Computername) $ps = New-PSSession -ComputerName $computername $cs = New-CimSession -ComputerName $computername It "Should be pingable" { Test-Connection -ComputerName $computername -Count 2 -Quiet | Should Be $True } It "Should respond to Test-WSMan" { {Test-WSMan -ComputerName $computername -ErrorAction Stop} | Should Not Throw } Context Features { $installed = Get-WindowsFeature -ComputerName $computername | Where Installed $Features = $($item.Features.Installed) foreach ($feature in $features) { It "Should have $feature installed" { $installed.Name -contains $feature | Should Be $True } } $NotFeatures = $($item.features.notinstalled) foreach ($feature in $Notfeatures) { It "Should NOT have $feature installed" { $installed.Name -contains $feature | Should Be $False } } } #features Context Services { $Stopped = $($item.services.Stopped) $Running = $($item.services.Running) $all = Invoke-Command { Get-Service } -session $ps foreach ($item in $Stopped) { It "Service $item should be stopped" { $all.where({$_.name -eq $item}).status | Should Be "Stopped" } } foreach ($item in $Running) { It "Service $item should be running" { $all.where({$_.name -eq $item}).status | Should Be "Running" } } } #services Context Versions { $winVer = $($item.versions.Windows) It "Should be running Windows Server $winVer" { (Get-CimInstance win32_operatingsystem -cimSession $cs).Caption | Should BeLike "*$winver*" } $psver = $($item.versions.powershell) It "Should be running PowerShell version $psver" { Invoke-Command { $PSVersionTable.psversion.major } -session $ps | Should be $psver } } #versions Context Other { It "Security event log should be at least 16MB in size" { ($cs | Get-CimInstance -ClassName win32_NTEVentlogFile -filter "LogFileName = 'Security'").FileSize | Should beGreaterThan 16MB } It "Should have C:\Temp folder" { Invoke-Command {Test-Path C:\Temp} -session $ps | Should Be $True } } #other $ps | Remove-PSSession $cs | Remove-CimSession } #describe } #foreach
I’ve hard coded the input values, but you could just as easily input them from an external source such as XML.
#requires -version 5.0 #requires -Module ActiveDirectory, DNSClient <# Use Pester to test Active Directory Last updated: July 5, 2016 #> $myDomain = Get-ADDomain $DomainControllers = $myDomain.ReplicaDirectoryServers $GlobalCatalogServers = (Get-ADForest).GlobalCatalogs Write-Host "Testing Domain $($myDomain.Name)" -ForegroundColor Cyan Foreach ($DC in $DomainControllers) { Describe $DC { Context Network { It "Should respond to a ping" { Test-Connection -ComputerName $DC -Count 2 -Quiet | Should Be $True } #ports $ports = 53,389,445,5985,9389 foreach ($port in $ports) { It "Port $port should be open" { #timeout is 2 seconds [system.net.sockets.tcpclient]::new().ConnectAsync($DC,$port).Wait(2000) | Should Be $True } } #test for GC if necessary if ($GlobalCatalogServers -contains $DC) { It "Should be a global catalog server" { [system.net.sockets.tcpclient]::new().ConnectAsync($DC,3268).Wait(2000) | Should Be $True } } #DNS name should resolve to same number of domain controllers It "should resolve the domain name" { (Resolve-DnsName -Name globomantics.local -DnsOnly -NoHostsFile | Measure-Object).Count | Should Be $DomainControllers.count } } #context Context Services { $services = "ADWS","DNS","Netlogon","KDC" foreach ($service in $services) { It "$Service service should be running" { (Get-Service -Name $Service -ComputerName $DC).Status | Should Be 'Running' } } } #services Context Disk { $disk = Get-WmiObject -Class Win32_logicaldisk -filter "DeviceID='c:'" -ComputerName $DC It "Should have at least 20% free space on C:" { ($disk.freespace/$disk.size)*100 | Should BeGreaterThan 20 } $log = Get-WmiObject -Class win32_nteventlogfile -filter "logfilename = 'security'" -ComputerName $DC It "Should have at least 10% free space in Security log" { ($log.filesize/$log.maxfilesize)*100 | Should BeLessThan 90 } } } #describe } #foreach Describe "Active Directory" { It "Domain Admins should have 5 members" { (Get-ADGroupMember -Identity "Domain Admins" | Measure-Object).Count | Should Be 5 } It "Enterprise Admins should have 1 member" { (Get-ADGroupMember -Identity "Enterprise Admins" | Measure-Object).Count | Should Be 1 } It "The Administrator account should be enabled" { (Get-ADUser -Identity Administrator).Enabled | Should Be $True } It "The PDC emulator should be $($myDomain.PDCEmulator)" { (Get-WMIObject -Class Win32_ComputerSystem -ComputerName $myDomain.PDCEmulator).Roles -contains "Primary_Domain_Controller" | Should Be $True } }
More in PowerShell
Most popular on petri