Coming Soon: GET:IT Endpoint Management 1-Day Conference on September 28th at 9:30 AM ET Coming Soon: GET:IT Endpoint Management 1-Day Conference on September 28th at 9:30 AM ET
PowerShell

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.

Sponsored Content

Say Goodbye to Traditional PC Lifecycle Management

Traditional IT tools, including Microsoft SCCM, Ghost Solution Suite, and KACE, often require considerable custom configurations by T3 technicians (an expensive and often elusive IT resource) to enable management of a hybrid onsite + remote workforce. In many cases, even with the best resources, organizations are finding that these on-premise tools simply cannot support remote endpoints consistently and reliably due to infrastructure limitations.

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:
http://jdhitsolutions.com/blog/essential-powershell-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.

Related Topics:

BECOME A PETRI MEMBER:

Don't have a login but want to join the conversation? Sign up for a Petri Account

Register
Comments (0)

Leave a Reply

Live Webinar: Active Directory Security: What Needs Immediate Priority!Live on Tuesday, October 12th at 1 PM ET

Attacks on Active Directory are at an all-time high. Companies that are not taking heed are being punished, both monetarily and with loss of production.

In this webinar, you will learn:

  • How to prioritize vulnerability management
  • What attackers are leveraging to breach organizations
  • Where Active Directory security needs immediate attention
  • Overall strategy to secure your environment and keep it secured

Sponsored by: