How to Query the DSC Compliance Server

If you’ve been paying attention to PowerShell, then you’ve probably picked up on the fact the Desired State Configuration (DSC) is the next big thing. Microsoft is devoting a lot of resources to DSC and you’ve most likely seen a lot of discussion about it in the PowerShell community. I’m going to assume you at least have a passing understanding of the technology, because I want to talk about a feature that I think will be important to you but one that you probably don’t know much about.


When you set up an HTTP pull server, you can also configure a compliance server. This is a web service designed to capture node information. When a server pulls a configuration, information should be captured by the compliance server. The theory is that you can later query the server to discover servers that are not in compliance with their desired configuration. This is especially important if you aren’t using an ApplyAndAutoCorrect setting. While this is an important feature, there is not a lot of official documentation on it, and I suspect it is still being finalized. What I am going to show you is something I’ve worked on using PowerShell 4.0. Since PowerShell 5.0 is still in preview and possibly changing with each release, I wanted to stick with what you could use in production today. That said, there may be changes with PowerShell 5.0 officially ships. For now, let’s take a look at how you can query a compliance server.
When you set up the compliance server, you define a port and endpoint name. These elements are used to construct a URI to connect to the web service.
You could try opening this in a browser.

Viewing the compliance server from a browser. (Image Credit: Jeff Hicks)
Viewing the compliance server from a browser. (Image Credit: Jeff Hicks) 

Although this isn’t entirely helpful, I can see there are three items. If you examine the source or view with a browser plugin that shows the underlying XML, then you’ll see something like this:
Viewing the source code in the browser. (Image Credit: Jeff Hicks)
Viewing the source code in the browser. (Image Credit: Jeff Hicks)

There is data, but a browser doesn’t help you. However, we can use PowerShell to reveal more information. Here’s a function from a Microsoft blog article that demonstrates how to retrieve node information from a compliance server.

#from http://blogs.msdn.com/b/powershell/archive/2014/05/29/how-to-retrieve-node-information-from-pull-server.aspx
function QueryNodeInformation {
  Param (
       [string] $Uri = "http://chi-web02.globomantics.local:9080/PSDSCComplianceServer.svc/Status",
       [string] $ContentType = "application/json"
     )
  Write-Host "Querying node information from pull server URI  = $Uri" -ForegroundColor Green
  Write-Host "Querying node status in content type  = $ContentType " -ForegroundColor Green
 $response = Invoke-WebRequest -Uri $Uri -Method Get -ContentType $ContentType -UseDefaultCredentials -Headers @{Accept = $ContentType}
 if($response.StatusCode -ne 200)
 {
      Write-Host "node information was not retrieved." -ForegroundColor Red
 }
 $jsonResponse = ConvertFrom-Json $response.Content
 return $jsonResponse
 }

I’ve adjusted the code to set a default value for my compliance server. The function uses Invoke-Webrequest to get the web service and converts the result to a JSON object. You’ll get a result like this:

    TargetName         : 172.16.30.215
    ConfigurationId    : ee791d3d-a2d7-406f-8c46-066a695d896c
    ServerCheckSum     : E60C4CC1E346433CBDF1B6198BEF400F4D72D71938DBB08ACAB526A169101A03
    TargetCheckSum     : E60C4CC1E346433CBDF1B6198BEF400F4D72D71938DBB08ACAB526A169101A03
    NodeCompliant      : True
    LastComplianceTime : 2015-02-03T21:39:08.030476Z
    LastHeartbeatTime  : 2015-02-03T21:39:08.030476Z
    Dirty              : True
    StatusCode         : 0

Yes, you get an object for each node, but it’s far from complete. For example, what is the computer name? All I see is an IP address? What does a StatusCode of 0 really mean?

And the time values make my head hurt trying to convert them into something meaningful. In addition, depending on how you set up your compliance server or where you are querying it, you might need to provide credentials. Because of this, I created a more complete advanced function that incorporates some suggestions I came across from other members of the PowerShell community.

Function Get-DSCNodeStatus {
<#
.SYNOPSIS
Get DSC compliance status.
.DESCRIPTION
This command will query a DSC compliance server and return status information on all nodes. You can filter by computername or IP address.
Normally, compliance information is presented like this:
    TargetName         : 172.16.30.215
    ConfigurationId    : ee791d3d-a2d7-406f-8c46-066a695d896c
    ServerCheckSum     : E60C4CC1E346433CBDF1B6198BEF400F4D72D71938DBB08ACAB526A169101A03
    TargetCheckSum     : E60C4CC1E346433CBDF1B6198BEF400F4D72D71938DBB08ACAB526A169101A03
    NodeCompliant      : True
    LastComplianceTime : 2015-02-03T21:39:08.030476Z
    LastHeartbeatTime  : 2015-02-03T21:39:08.030476Z
    Dirty              : True
    StatusCode         : 0
This command reformats the output so that datetime values are [DateTime] objects, the IP address is resolved to the host name and the status code is resolved to more descriptive value.
In addition, this command will check the local configuration manager on each node to retrieve its ConfigurationModeFrequencyMins, RefreshFrequencyMins and ConfigurationMode properties. See examples.
.PARAMETER Uri
The URI to the compliance server feature.
.PARAMETER Credential
Alternate credentials for querying the compliance server as well as checking the local configuration manager on the nodes.
.PARAMETER IPaddress
Filter by IPv4 Address. A regular expression pattern is used to validate this parameter.
.PARAMETER Computername
Filter by computername. It can be a Netbios or fully qualified name although a FQDN is recommended. This parameter has aliases of CN and Host.
.EXAMPLE
PS C:\> Get-DSCNodeStatus
IPAddress          : 172.16.30.13
NodeName           : chi-core01.globomantics.local
ConfigurationId    : 15c70bc6-8778-436c-b4f5-dcf3403621bc
ServerCheckSum     : 6668B4481FEDBC41D024337B9E04E2F1C8D5A6296B67658F20C9BDCC5B661007
TargetCheckSum     : 6668B4481FEDBC41D024337B9E04E2F1C8D5A6296B67658F20C9BDCC5B661007
NodeCompliant      : True
Mode               : ApplyAndMonitor
ConfigFreq         : 15
RefreshFreq        : 30
LastComplianceTime : 2/6/2015 2:48:49 PM
LastHeartBeatTime  : 2/6/2015 2:48:49 PM
Dirty              : True
StatusCode         : 0
Status             : Configuration was applied successfully
...
IPAddress          : 172.16.30.215
NodeName           : chi-test01.globomantics.local
ConfigurationId    : 5cd9b956-415e-473d-9991-f1148a4062e4
ServerCheckSum     : 1EA2E3B617F2C9199FD5CB4C164ABE81F2F84386260F66E9ED698A95E6D434A9
TargetCheckSum     : 1EA2E3B617F2C9199FD5CB4C164ABE81F2F84386260F66E9ED698A95E6D434A9
NodeCompliant      : True
Mode               : ApplyAndMonitor
ConfigFreq         : 15
RefreshFreq        : 30
LastComplianceTime : 2/6/2015 2:47:48 PM
LastHeartBeatTime  : 2/6/2015 2:47:48 PM
Dirty              : True
StatusCode         : 0
Status             : Configuration was applied successfully
Get status for all nodes.
.EXAMPLE
PS C:\> Get-DSCNodeStatus -computername chi-test01 -credential globomantics\administrator
IPAddress          : 172.16.30.215
NodeName           : chi-test01.globomantics.local
ConfigurationId    : 5cd9b956-415e-473d-9991-f1148a4062e4
ServerCheckSum     : 1EA2E3B617F2C9199FD5CB4C164ABE81F2F84386260F66E9ED698A95E6D434A9
TargetCheckSum     : 1EA2E3B617F2C9199FD5CB4C164ABE81F2F84386260F66E9ED698A95E6D434A9
NodeCompliant      : True
Mode               : ApplyAndMonitor
ConfigFreq         : 15
RefreshFreq        : 30
LastComplianceTime : 2/6/2015 2:47:48 PM
LastHeartBeatTime  : 2/6/2015 2:47:48 PM
Dirty              : True
StatusCode         : 0
Status             : Configuration was applied successfully
Get compliance information for CHI-TEST01 using alternate credentials.
.NOTES
NAME        :  Get-DSCNodeStatus
VERSION     :  1.0
LAST UPDATED:  2/6/2015
AUTHOR      :  Jeff Hicks
Learn more about PowerShell:
Essential PowerShell Learning Resources
**************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** .LINK Invoke-WebRequest ConvertFrom-JSON Get-DSCLocalConfigurationManager .INPUTS None .OUTPUTS None #> [cmdletbinding(DefaultParameterSetName="All")] Param ( [Parameter(Position=0)] [ValidateNotNullorEmpty()] [string]$Uri = "http://chi-web02.globomantics.local:9080/PSDSCComplianceServer.svc/Status", [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, [Parameter(ParameterSetName="IP")] [ValidatePattern("^(\d{1,3}(\.)?){4}$")] [string]$IPaddress, [Parameter(ParameterSetName="Name")] [Alias("CN","Host")] [string]$Computername ) $ContentType = "application/json" if ($Computername) { #resolve computername to IP Write-Verbose "Filtering by computername $Computername" Try { $IPAddress = (Resolve-DnsName -Name $computername -Type A -ErrorAction Stop).IpAddress } Catch { Write-Warning "Can't determine IP address of $Computername" #bail out Return } } if ($IPAddress) { #edit the uri to filter by the IPAddress Write-Verbose "Filtering for IP $IPAddress" $uri+="?`$filter=trim(TargetName) eq '$IPAddress'" } Write-Verbose "Querying node information from pull server URI = $Uri" Write-Verbose "Querying node status in content type = $ContentType " Try { $paramHash = @{ Uri = $Uri Method = 'Get' ContentType = $ContentType Headers = @{'Accept'=$ContentType} ErrorAction = 'Stop' } if ($Credential.UserName) { Write-Verbose "Using alternate credentials" $paramHash.Add("Credential",$Credential) } else { Write-Verbose "Using default credentials" $paramhash.add("UseDefaultCredentials",$True) } Write-Verbose ($paramHash | out-string) $response = Invoke-WebRequest @paramHash if($response.StatusCode -ne 200) { Write-Warning "node information was not retrieved." throw } } #try Catch { Write-Warning "There was a problem." Write-Warning $_.exception.message #bail out Break } Write-Verbose "Converting from JSON" $jsonResponse = ConvertFrom-Json $response.Content write-verbose ($jsonResponse | out-string) #decode StatusCode values $StatusCode = @" 0 = Configuration was applied successfully 1 = Download Manager initialization failure 2 = Get configuration command failure 3 = Unexpected get configuration response from pull server 4 = Configuration checksum file read failure 5 = Configuration checksum validation failure 6 = Invalid configuration file 7 = Available modules check failure 8 = Invalid configuration Id In meta-configuration 9 = Invalid DownloadManager CustomData in meta-configuration 10 = Get module command failure 11 = Get Module Invalid Output 12 = Module checksum file not found 13 = Invalid module file 14 = Module checksum validation failure 15 = Module extraction failed 16 = Module validation failed 17 = Downloaded module is invalid 18 = Configuration file not found 19 = Multiple configuration files found 20 = Configuration checksum file not found 21 = Module not found 22 = Invalid module version format 23 = Invalid configuration Id format 24 = Get Action command failed 25 = Invalid checksum algorithm 26 = Get Lcm Update command failed 27 = Unexpected Get Lcm Update response from pull server 28 = Invalid Refresh Mode in meta-configuration 29 = Invalid Debug Mode in meta-configuration "@ $hashStatusCode = ConvertFrom-StringData $StatusCode $values = $jsonResponse.value foreach ($Value in $values) { #resolve IP to hostname write-Verbose "Resolving $($value.Targetname)" Try { $nodeName = (Resolve-DnsName -Name $value.TargetName -ErrorAction Stop).namehost } Catch { #couldn't resolve name so use IP $nodeName = $value.TargetName } #get LCM settings if server is online if (Test-Connection $nodename -quiet -count 1) { Try { write-verbose "Getting LCM settings for $nodename" #create a cim session $phash = @{ Computername = $nodename ErrorAction = "Stop" } if ($Credential.username) { #add the credential to New-CimSession parameters $phash.Add("Credential",$Credential) } $cs = New-CimSession @phash if ($cs) { $lcm = Get-DscLocalConfigurationManager -CimSession $cs -ErrorAction Stop } } #Try Catch { #server probably offline Write-Warning "Error retrieving local configuration manager from $nodename" Write-Warning $_.Exception.Message } #Catch if ($cs) { Remove-CimSession $cs } } #if test connection if (-Not ($lcm.PSComputerName -eq $nodeName) ){ #server probably offline or IP address could not be resolved Write-Warning "Could not retrieve local configuration manager from $($value.targetName) [$nodename]" $lcm = [pscustomobject]@{ ConfigurationModeFrequencyMins = -1 RefreshFrequencyMins = -1 ConfigurationMode = "Unknown" } } #if lcm <> computername #write custom objects to the pipeline Select -InputObject $value @{Name="IPAddress";Expression={$_.TargetName}}, @{Name="NodeName";Expression={$nodename}}, ConfigurationID,ServerCheckSum,TargetCheckSum,NodeCompliant, @{Name="Mode";Expression={$lcm.ConfigurationMode}}, @{Name="ConfigFreq";Expression={$lcm.ConfigurationModeFrequencyMins}}, @{Name="RefreshFreq";Expression={$lcm.RefreshFrequencyMins}}, @{Name="LastComplianceTime";Expression={$_.LastComplianceTime -as [datetime]}}, @{Name="LastHeartBeatTime";Expression={$_.LastHeartBeatTime -as [datetime]}}, Dirty,StatusCode, @{Name="Status";Expression={$hashStatusCode.($_.statuscode)}} } #foreach value } #end function

Now I can run a command like this:

get-dscnodestatus -Credential globomantics\jeff | out-gridview -title "DSC Status"

Desired State Configuration status. (Image Credit: Jeff Hicks)
Desired State Configuration status. (Image Credit: Jeff Hicks) 

Here’s what a result looks like in more detail:
Detailed result in Windows PowerShell. (Image Credit: Jeff Hicks)
Detailed result in Windows PowerShell. (Image Credit: Jeff Hicks)

I use the Resolve-DNSName cmdlet in this function to find the computer name from the IP address. I also use a hash table to decode the status code to a more descriptive property. Of course, converting the time values to a [datetime] object is pretty easy. The last addition I made was to query the computer for its local configuration manager settings, assuming it is up and running. I thought it would be helpful to see the mode and refresh values. Now at a glance I have a better understanding about the state of a node. Because I am writing objects to the pipeline, I can export or convert the results. I can also filter the results to find non-compliant servers.

get-dscnodestatus -Credential globomantics\jeff | where {$_.NodeCompliant} | Select Nodename,Last*

The last thing I want to talk about is filtering. In developing my function, it seemed to me that I should be able to filter for a specific IP address.

http://chi-web02.globomantics.local:9080/PSDSCComplianceServer.svc/Status('172.16.30.215')

Judging from the error message in the HTML response, I’m figuring this is a bug. I’ve mentioned it to the PowerShell team at Microsoft and am waiting to hear back from them. In the meantime, I discovered a workaround.

The compliance server uses an oData compliant web service, which means there are rules it should follow. Don’t worry if you don’t know what oData is or how to use it. You shouldn’t. I discovered I could use an oData method like Trim=() in a filter.

http://chi-web02.globomantics.local:9080/PSDSCComplianceServer.svc/Status?$filter=trim(TargetName) eq '172.16.30.215'

In my function, I added code to update the URI if the user needs to filter by IP address. If the user requests to filter by IP Address, I resolve the name and use that IP address in the filter.

get-dscnodestatus -Credential globomantics\jeff -Computername chi-test01

Using the get-dscnodestatus cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
Using the get-dscnodestatus cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks) 

As I mentioned, I think there is still work to be done on the compliance server feature, and I expect it to change. I hope this function will help you out. If you are trying to get up to speed with DSC, there are a number of articles on the Petri IT Knowledgebase. There is coverage in my PowerShell in Depth 2nd edition book, and I have courses on Pluralsight.