Automate Exchange installation and configuration with DSC Part 2:Configure node’s LCM

This is the second post of a serie around DSC and my personal journey:

Part 1: Pull server (setup and querying node status)

Part 2: Configure node’s LCM in a bulk operation reading GUID from AD and using individual certificates

Part 3: Automate creation and publishing to pull server of configurations reading Configuration and Environment Data files

In this post I will cover how you can configure the Local Configuration Manager (LCM) of the target nodes to pull their configuration from a pull server. The challenge was to keep track of the used GUID and not to use the same certificate on all nodes in order to Securing the MOF files.

Update 28.08.2015

Update 04.09.2015

You can configure the following properties of the Local Configuration Manager:

Property Description
AllowModuleOverwrite Controls whether new configurations downloaded from the configuration server are allowed to overwrite the old ones on the target node. Possible values are True and False.
CertificateID GUID a certificate used to secure credentials for access to the configuration. For more information see Want to secure credentials in Windows PowerShell Desired State Configuration?.
ConfigurationID Indicates a GUID which is used to get a particular configuration file from a server set up as a “pull” server. The GUID ensures that the correct configuration file is accessed.
ConfigurationMode Specifies how the Local Configuration Manager actually applies the configuration to the target nodes. It can take the following values:

  • ApplyOnly: With this option, DSC applies the configuration and does nothing further unless a new configuration is detected, either by you sending a new configuration directly to the target node (“push”) or if you have configured a “pull” server and DSC discovers a new configuration when it checks with the “pull” server. If the target node’s configuration drifts, no action is taken.
  • ApplyAndMonitor: With this option (which is the default), DSC applies any new configurations, whether sent by you directly to the target node or discovered on a “pull” server. Thereafter, if the configuration of the target node drifts from the configuration file, DSC reports the discrepancy in logs. For more about DSC logging, see Using Event Logs to Diagnose Errors in Desired State Configuration.
  • ApplyAndAutoCorrect: With this option, DSC applies any new configurations, whether sent by you directly to the target node or discovered on a “pull” server. Thereafter, if the configuration of the target node drifts from the configuration file, DSC reports the discrepancy in logs, and then attempts to adjust the target node configuration to bring in compliance with the configuration file.
ConfigurationModeFrequencyMins Represents the frequency (in minutes) at which the background application of DSC attempts to implement the current configuration on the target node. The default value is 15. This value can be set in conjunction with RefreshMode. When RefreshMode is set to PULL, the target node contacts the configuration server at an interval set by RefreshFrequencyMins and downloads the current configuration. Regardless of the RefreshMode value, at the interval set by ConfigurationModeFrequencyMins, the consistency engine applies the latest configuration that was downloaded to the target node. RefreshFrequencyMins should be set to an integer multiple of ConfigurationModeFrequencyMins.
Credential Indicates credentials (as with Get-Credential) required to access remote resources, such as to contact the configuration server.
DownloadManagerCustomData Represents an array that contains custom data specific to the download manager.
DownloadManagerName Indicates the name of the configuration and module download manager.
RebootNodeIfNeeded Certain configuration changes on a target node might require it to be restarted for the changes to be applied. With the value True, this property will restart the node as soon as the configuration has been completely applies, without further warning. If False (the default value), the configuration will be completed, but the node must be restarted manually for the changes to take effect.
RefreshFrequencyMins Used when you have set up a “pull” server. Represents the frequency (in minutes) at which the Local Configuration Manager contacts a “pull” server to download the current configuration. This value can be set in conjunction with ConfigurationModeFrequencyMins. When RefreshMode is set to PULL, the target node contacts the “pull” server at an interval set by RefreshFrequencyMins and downloads the current configuration. At the interval set by ConfigurationModeFrequencyMins, the consistency engine then applies the latest configuration that was downloaded to the target node. If RefreshFrequencyMins is not set to an integer multiple of ConfigurationModeFrequencyMins, the system will round it up. The default value is 30.
RefreshMode Possible values are Push (the default) and Pull. In the “push” configuration, you must place a configuration file on each target node, using any client computer. In the “pull” mode, you must set up a “pull” server for Local Configuration Manager to contact and access the configuration files.

Note: I took the table from this KB article.

As you can see there are plenty properties to configure. But the major important ones are the following:

  • CertificateID
  • ConfigurationID

As described in the table CertificateID is used to determine, which certificate the target node is using to decrypt the credentials. Whereas the ConfigurationID is used to determine, which configuration to pull and apply.

There are many examples out there, which are using the same certificate on all nodes in order to en/decrypt credentials. That’s fine and easy as long as this fits your company’s needs. But what when you cannot use of the same certificate or you don’t want to? Then it’s going to be somehow complicated as you would need to keep track of any used certificate on each node. Similar the same applies to the GUID for the ConfigurationID. It’s going to be hard to keep track for which node which GUID is used.

CertificateID

As in my environment an Enterprise CA exist and each server anyways got its own certificate, I thought it would be a good idea to use this existing certificate. The challenge is now to get from the node’s certificate the thumbprint and use this one to configure the LCM. Luckily there already exist a function, which covers this in some way. Just scroll down on the KB article about Securing the MOF files. At the end you will find an example on the whole process with the function called Get-EncryptionCertificate.

The function is searching on a node for a certificate, which is valid and the node has a private key for. If such a certificate exist, it will be exported locally on the node first and then copied to a folder on the server from where the function was run from.

I thought this is almost perfect, but I needed to tweak it a little bit in order to meet my needs. Therefore I change the function as follows:

Parameters

I added two parameters:

  • Path: Here you define the folder to where the certificate will be exported. The folder will be automatically created on the node if it doesn’t exist. This will only happen, when you use the switch DoExport. But you have to make sure that the same path exists on the server from where you run the function!
  • DoExport: Will try to export the certificate as a first step locally on the node and then copy it to the server you are calling the function from.

Certificate check

The script only checks if the certificate is valid and the node has the corresponding private key. I extended this to the following criteria:

  • $_.PrivateKey.KeyExchangeAlgorithm: Node has the corresponding private key
  • $_.Verify(): the certificate is valid
  • $_.DnsNameList -match $env:Computername $Server: The DNS name of the server is in the certificate
  • $_.Issuer -ne $_.Subject: the certificate is NOT a selfsigned one

As I’m sorting the certificates based on the attribute NOTAFTER ($certificates = dir Cert:\LocalMachine\my | sort NOTAFTER -Descending), I get the certificate, which is valid for the longest time. Just in case there are multiple certificates matching the criteria. The other ones would also be exported.

Firewall rule

I couldn’t copy the exported certificate to the pull server sometimes via SMB.I had to enable a rule: File and Printer Sharing (SMB-In) will be enabled for all profiles. I’m still working on this as I don’t like this as it is.

Update 28.08.2015:

I came across an issue, while exporting a certificate. This is now fixed.

Update 04.09.2015:

Minor fix for certificate filtering.
Here is the modified function:

function Get-EncryptionCertificate
{
    [CmdletBinding()]
    param (
        [string]$ComputerName,
        [string]$Path,
        [switch]$DoExport
    )
    If ($DoExport) {
    [array]$returnValue= Invoke-Command -ComputerName $computerName -ScriptBlock {
            $certificates = dir Cert:\LocalMachine\my | sort NOTAFTER -Descending |
             ?{$_.PrivateKey.KeyExchangeAlgorithm -and $_.Verify() -and ($_.DnsNameList -match $env:ComputerName) -and ($_.Issuer -ne $_.Subject)} |
             select -First 1
            $FW = (Get-NetFirewallRule -DisplayName "File and Printer Sharing (SMB-In)").Enabled
            $Folder = $using:Path
            $Server = $using:Computername
            if ($certificates) {
                # Create the folder to hold the exported public key
                if (! (Test-Path $Folder)) {
                    md $Folder | Out-Null
                }
                #Enable FW
                #netsh adv firew set rule name="File and Printer Sharing (SMB-In)" dir=in profile=any new enable=yes
                If ($FW -eq "False") {
                    #Write-Host "Enable FW rule!"
                    Enable-NetFirewallRule -DisplayName "File and Printer Sharing (SMB-In)"
                    Sleep 2
                }
                # Export the public key to a well known location
                $certPath = Export-Certificate -Cert $certificates -FilePath (Join-Path -path $Folder -childPath "$Server.cer") -Force
                # Return the thumbprint, and exported certificate path
                return @($certificates.Thumbprint,$certPath);
            }
        }
    # Copy the exported certificate locally
    $destinationPath = join-path -Path "$Path" -childPath "$Computername.cer"
    Copy-Item -Path (join-path -path \\$Computername -childPath $returnValue[1].FullName.Replace(":","$"))  $destinationPath -Force | Out-Null
    }
    Else {
    [array]$returnValue= Invoke-Command -ComputerName $ComputerName -ScriptBlock {
            $certificates = dir Cert:\LocalMachine\my | sort NOTAFTER -Descending |
             ?{$_.PrivateKey.KeyExchangeAlgorithm -and $_.Verify() -and ($_.DnsNameList -match $env:ComputerName) -and ($_.Issuer -ne $_.Subject)} |
             select -First 1
             if ($certificates) {
                # Return the thumbprint
                return $certificates.Thumbprint;
             }
        }   
    }
    # Return the thumbprint, and exported certificate path
    return @($returnValue[0],$returnValue[1].FullName)
}

ConfigurationID

As we are using a pull server we need we need a MOF file, which needs to have a GUID as name (e.g.:7d6d6ee7-d504-497f-ad8f-72dba244e186.mof). Besides this you also need a checksum for this file. But this will be part of the next post.

You can easily create a new GUID in many ways. One example to do so in Powershell would be like this:

$GUID= [guid]::NewGuid().Guid

But you will have the problem to keep track of the nodes names and their corresponding GUIDs. This can be really challenging especially when you have to update configurations.

I thought by myself it would be a good idea to use an existing GUID for this, which is bound to the node:

Why not just use the AD attribute objectguid of the node’s computer account?

For my scenario this was the perfect solution. If you have to deal with non-domain joined nodes, you will need to find another way.

Assuming $Name is the target node’s NetBIOS name it looks like this:

$GUID = ([guid]([adsisearcher]”(samaccountname=$Name`$)”).FindOne().Properties[“objectguid”][0]).Guid

If $Name is the target node’s DNS name it looks like this:

$GUID = ([guid]([adsisearcher]”(dNSHostName=$Name)”).FindOne().Properties[“objectguid”][0]).Guid

The difference is that I use the property samaccountname in the first and dNSHostname in the second search to find the object in AD.

Set-DSCLCMforPullServer

Putting all together I wrote a script, which imports a CSV file(could also be the output from the Exchange 2013 Server Role Requirements Calculator) and configures the LCM as you want. You only have to make sure to have a column called ServerName in this file. It takes care of the above mentioned important properties. You can download it here. The script has the following parameters:

Parameter

Description

ServerCSVFile Full path to the CSV file containing the nodes
MOFsPath Path to a folder to store the generated MOF files
PullServer The name of the DSC pull server
Port The port of the PSDSCComplianceServer component of a DSC pull server. The default is 8080.
ConfigurationMode The mode of the DSC engine on the node. You can set it one of the following values:”ApplyAndAutoCorrect”, “ApplyOnly”, “ApplyAndMonitor”.
The default is “ApplyAndAutoCorrect”.
RefreshFrequencyMins To specify the LCM property RefreshFrequencyMins. Default is 30.
ConfigurationModeFrequencyMins To specify the LCM property ConfigurationModeFrequencyMins. Default is 30.
RebootNodeIfNeeded To specify the LCM property RebootNodeIfNeeded. Default is $True.
AllowModuleOverwrite To specify the LCM property AllowModuleOverwrite. Default is $True.
ForceReboot To force a reboot of the node after you have configured the LCM. Default is $False.
SkipPing To bypass connectivity test. Maybe useful when ICMP is not allowed on FW, which is the default. Default is $False.
CertThumbprint If you want to specify a thumbprint of the certificate use this parameter. All nodes will use the same thumbprint.

Here are some examples and screenshoots:

Set-DSCLCMforPullServer.ps1 -ServerCSVFile C:\DSC\CSVs\LabServers.csv -MOFsPath C:\DSC\Config -PullServer fabpullsrv.fabrikam.local

Set-DSCLCM_01

As you can see there was a problem getting the certificate from fabex03 and on fabex02 and fabex04 the connectivity check failed. Therefore those 2 servers will be skipped. If you are sure that those servers would be up just use the parameter SkipPing.

The corresponding CSV file looks like this:

ServerName,FQDN,Location,DAGID,Role
fabex01.fabrikam.local,fabex01.fabrikam.local,amer,dag1,FirstDAGMember
fabex02,fabex02.fabrikam.local,amer,dag1,AdditionalDAGMember
fabex03,fabex03.fabrikam.local,emea,dag1,AdditionalDAGMember
fabex04,fabex04.fabrikam.local,emea,dag1,AdditionalDAGMember

You can also use the verbose switch:

Set-DSCLCM_02

This was the second part. In the third one I will shared my approach how I incorporated the method described above into the process of creating MOF files for nodes.

Feedback is always welcome!