Audit policy using Pester

In my previous post, I’ve shown how to leverage DSC to apply a configuration that defines the local Audit policy.
If you’re just interested in the compliance of a system, Pester might be more suitable to assess it against a template. It will somehow validate the operational status of the system.

First, I create a hashtable of settings like this:

auditpol.exe /get /category:* /r |
ConvertFrom-Csv |
Select Subcategory*,*lusion* | 
ForEach-Object {
    '    @{'
    "        Name = '{0}'" -f $_.Subcategory
    "        GUID = '{0}'" -f $_.'Subcategory GUID'
    "        Inclusion = '{0}'" -f $_.'Inclusion Setting'
    '    },'

and I populate the file auditpol.ps1 using the output of the above command. The content looks like this. The hashtable is stored inside an array named $AuditPolicy.

Now in the test file auditpol.tests.ps1, I’ve:

Let’s see how to use it

Invoke-Pester ~/documents/pester/auditpol


If I change the Logoff policy for example like this:

auditpol /Set /subcategory:{0CCE9216-69AE-11D9-BED3-505054503030} /failure:enable

and run the pester test a second time, I’ll get:
…my system isn’t compliant anymore with the settings defined in my template.


PowerShell celebrated its 10th anniversary on Monday, November 14th.

To celebrate it, there was a live stream all day long that was announced on the PowerShell Team blog

If you missed it, no problem, you can go to channel9 an watch it using this link


The community demonstrated all day long how they use PowerShell. That was awesome!!! And Kenneth Hansen and Angel Calvo discussed Future Directions for PowerShell

There were also Code Golf holes to celebrate that day 😀

  • Code golf hole 1
  • I submitted the following answer and introduced a old trick (works in PS2.0) to subtract days to the current datetime object

    gcim(gwmi -li *ix*).Name|? I*n -gt((date)+-30d)

    and it passed the pester test
    Here’s what it does:

    • gwmi is the alias of the Get-WmiObject cmdlet.
    • -li is the shortest version of the -List parameter of Get-WmiObject.
    • -List allows wildcards when looking for WMI classes. So *ix* matches the Win32_QuickFixEngineering WMI class that the Get-Hotfix cmdlet queries.
    • gcim is the alias of the Get-CimInstance cmdlet.
    • (gwmi -li *ix*).Name returns Win32_QuickFixEngineering.
    • Now that we have the list of hotfixes as CIM instances we can filter on the right.
    • ? is the alias of Where-Object.
    • I*n is the short name of the InstalledOn property that is a datetime object.
    • So we can compare it to the current date minus 30 days.
    • We can omit Get- in Get-Date and just type (date).
    • To subtract 30 days we use the old trick (date)+-30d 😎

    A longer form would be

    Get-CimInstance (Get-WmiObject -List *ix*).Name |
    Where InstalledOn -gt (Get-date).AddDays(-30d)
    # or 
    Get-CimInstance Win32_QuickFixEngineering |
    Where InstalledOn -gt (Get-date).AddDays(-30d)
  • Code golf hole 2
  • I submitted the following answer that uses the -File switch parameter. Some answers submitted have a problem and may be broken when you change the path to another drive like HKLM: or Cert: for example. Mine is also somehow broken and works only if the console is started as administrator where the default path is set to C:\windows\system32.

    (ls c: -File|% E*n|group|sort c* -d)[0..9]

    …but it passed the pester test
    Anyway, here’s how to decode it:

    • ls is the alias of the Get-ChildItem cmdlet.
    • ls c: -File will return only files including those that don’t have an extension in system32.
    • % is the alias of the ForEach-Object cmdlet.
    • E*n is the short name of the Extension property of items returned by Get-ChildItem.
    • Group is the short version of the Group-Object cmdlet. We can usually omit the -Object (Noun) for cmdlets that deal with -Object except for the New-Object cmdlet.
    • sort is the alias of the Sort-Object cmdlet.
    • c* is the short name of the Count property returned by Group-Object.
    • -d is the short name of the -Descending switch parameter of the Sort-Object cmdlet.
    • To get only the first 10, we enclose everything in parentheses to treat it as an array and then we enumerate the elements in the array using the [0..9] notation.

    A longer form would be

    (Get-ChildItem -Path c: -File | ForEach-Object { 
    } |Group-Object | 
    Sort-Object -Property Count -Descending)[0..9]
  • Code golf hole 3
  • For the 3rd hole, I submitted the following solution 😎

    gal ?,?? -e h,g?,?s

    and it passed the pester test
    Here is how to read it:

    • gal is the alias of the Get-Alias cmdlet.
    • Get-Alias uses by the default the -Name parameter and it accepts an array of strings and wildcards.
    • * represents all/any characters and ? only one character (it’s the same in DOS) and ?? represents two characters.
    • -e is the short name of the -Exclude parameter of the Get-Alias cmdlet.
    • -Exclude also accepts an array of strings and wildcards.
    • To avoid aliases for Get- cmdlets, we explicitly exclude h, the alias of the Get-History cmdlet, all the aliases for Get- cmdlets that begin by g and followed by a second letter like gi (Get-Item), gc (Get-Content),…, and finally the last two Unix aliases of the Get-Process cmdlet, ps, and the Get-ChildItem cmdlet, ls.

    A longer form would be

    Get-Alias -Name ?,?? -Exclude h,gc,gi,gl,gm,gp,gu,gv,ps,ls

Security policy and DSC

When I showcased DSC to our security team, I also built another wrapper of secedit.exe.
I took the same quick’n dirty approach as the audit policy DSC script from my previous post. Again, only a File and a Script DSC resources are involved in the configuration.

Note that there’s also a limitation in my code.
Secedit.exe can handle more than just the local security policy.
There are other areas it can cover: restricted group settings, user logon rights,

To get the security baseline I first exported the local security policy to a file like this:

secedit.exe /export /Cfg C:\secpol.txt /areas SECURITYPOLICY

… and I copied/pasted the content of the resulting C:\secpol.txt into to Content property of my File resource.

Audit policy and DSC


It can be found on the powershell gallery and/or github

Now that Microsoft has published a full module for this purpose, I can actually show you the quick’n dirty way I coded it a few months ago when I needed to showcase DSC to our internal security team.

It only uses native File and Script DSC resources. In other words, there’s no dependency on any external DSC resource 🙂

First to get the content of the CSV file we’ll drop on the disk and that represents our desired settings, I do

auditpol.exe /get /category:* /r |
ConvertFrom-Csv |
Select Subcategory*,*lusion* | 
Export-Csv -Path ~/Documents/polaudit.csv

Then I paste the content of the polaudit.csv into the Content property of the File resource.

The Get and Test part of the script DSC resource use the same trick above to get the output of our brave old legacy (heritage) auditpol.exe as objects: