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.

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:

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 globomanticsadministrator
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 globomanticsjeff | out-gridview -title "DSC Status"

Here’s what a result looks like in more detail:

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 globomanticsjeff | 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 globomanticsjeff -Computername chi-test01

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.