I’m currently involved in an Exchange 2013 project. One of the goals is to get things automated and to prevent configuration drifts later in the operating phase.
I thougt this would be a great chance to have a look into Powershell Desired State Configuration (DSC). And indeed: I really like it! Especially in combination with xExchange module authored by Mike Hendrickson, Jason Walker, Michael Greene.
I’m not going to write another blog post about the module. Mike’s posts are very good and there is nothing to add. This is the start of a serie around DSC and my personal journey in accomplishing the goals that have been set:
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
To create a pull server for DSC you need to make your first decision: What kind of pull server you are going to install? You can choose one of the following options: HTTP, HTTPS and SMB.
I picked HTTPS as I wanted to use the PSDSCComplianceServer component, which is anyways HTTP based. The server itself is based on Windows Server 2012 R2.
Prerequisites
- xPSDesiredStateConfiguration
- a certificate
As I picked Windows Server 2012 R2 as OS WMF 4.0 is already built-in. I picked the example from Technet article here and started to create my configuration for the pull server:
configuration CreatePullServer { param ( [string[]]$NodeName = 'localhost', [ValidateNotNullOrEmpty()] [string] $certificateThumbPrint, [ValidateNotNullOrEmpty()] [string] $PSDSCPullServer ) Import-DSCResource -ModuleName xPSDesiredStateConfiguration Node $NodeName { #configure LCM LocalConfigurationManager { RebootNodeIfNeeded = $True #allow a reboot or not ConfigurationMode = "ApplyAndAutoCorrect" #ApplyOnly(does nothing), ApplyAndMonitor(logs only) or ApplyAndAutoCorrect(takes action based on config) ConfigurationModeFrequencyMins = 15 #time in minutes for checking } WindowsFeature IISFeature { Ensure = "Present" Name = "Web-Server" } WindowsFeature IISMgmtFeature { Ensure = "Present" Name = "Web-Mgmt-Console" DependsOn = "[WindowsFeature]IISFeature" } WindowsFeature DSCServiceFeature { Ensure = "Present" Name = "DSC-Service" DependsOn = @("[WindowsFeature]IISMgmtFeature","[WindowsFeature]IISFeature") } xDscWebService PSDSCPullServer { Ensure = "Present" EndpointName = $PSDSCPullServer Port = 8080 PhysicalPath = "$env:SystemDrive\inetpub\wwwroot\PSDSCPullServer" CertificateThumbPrint = $certificateThumbPrint ModulePath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Modules" ConfigurationPath = "$env:PROGRAMFILES\WindowsPowerShell\DscService\Configuration" State = "Started" DependsOn = "[WindowsFeature]DSCServiceFeature" } xDscWebService PSDSCComplianceServer { Ensure = "Present" EndpointName = "PSDSCComplianceServer" Port = 9080 PhysicalPath = "$env:SystemDrive\inetpub\wwwroot\PSDSCComplianceServer" CertificateThumbPrint = "AllowUnencryptedTraffic" State = "Started" IsComplianceServer = $true DependsOn = @("[WindowsFeature]DSCServiceFeature","[xDSCWebService]PSDSCPullServer") } } }
Now you can create the MOF file:
CreatePullServer -certificateThumbPrint '476C7CA2756043ABD0035864564B1389E7539BBF' -PSDSCPullServer 'fabpullsrv.fabrikam.local' -Verbose -Wait -OutputPath C:\DSC\Config
Push the configuration to the node:
Start-DscConfiguration -Verbose -Wait -Path C:\DSC\Config -ComputerName 'localhost' -Force
Don’t forget to configure the Local Configuration Manager (LCM):
Set-DscLocalConfigurationManager -Path C:\DSC\Config -ComputerName 'localhost'
Now you should have a working pull server. Therefore I started to prepare the modules I would need to have them automatically deployed via the pull server. There are several articles how to do so. Here are some examples:
After a while I wanted to query the status of the nodes I have configured. You can do this by querying the PSDSCComplianceServer component on the server. There is a function provided called QueryNodeInformation. You can find the function on the following pages:
- Technet article (scroll down to the section Query the compliance status)
- MSDN blog post
On both you can also find the description for the status codes.
Sadly it didn’t work when I tried. I only got the following error:
Invoke-WebRequest : HTTP Error 401.2 – Unauthorized
You are not authorized to view this page due to invalid authentication headers.Most likely causes:
No authentication protocol (including anonymous) is selected in IIS.
Only integrated authentication is enabled, and a client browser was used that does not support integrated authentication.
Integrated authentication is enabled and the request was sent through a proxy that changed the authentication headers before they reach the Web server.
The Web server is not configured for anonymous access and a required authorization header was not received.
The “configuration/system.webServer/authorization” configuration section may be explicitly denying the user access.
Things you can try:
Verify the authentication setting for the resource and then try requesting the resource using that authentication method.
Verify that the client browser supports Integrated authentication.
Verify that the request is not going through a proxy when Integrated authentication is used.
Verify that the user is not explicitly denied access in the “configuration/system.webServer/authorization” configuration section.
Create a tracing rule to track failed requests for this HTTP status code. For more information about creating a tracing rule for failed requests, click here.
Detailed Error Information:
Module IIS Web Core
Notification AuthenticateRequest
Handler svc-Integrated-4.0
Error Code 0x80070005
Requested URL http://fabpullsrv.fabrikam.local:9080/PSDSCComplianceServer.svc/Status
Physical Path C:\inetpub\wwwroot\PSDSCComplianceServer\PSDSCComplianceServer.svc\Status
Logon Method Not yet determined
Logon User Not yet determined
More Information:
This error occurs when the WWW-Authenticate header sent to the Web server is not supported by the server configuration. Check the authentication method for
the resource, and verify which authentication method the client used. The error occurs when the authentication methods are different. To determine which type
of authentication the client is using, check the authentication settings for the client.
View more information »
Microsoft Knowledge Base Articles:
907273
253667
The error clearly states that there is a problem with the authentication. So I compared the configuration with the site of the pull server:
On the pull server is Anonymous Authentication enabled, while on the PSDSCComplianceServer component it was disabled:
After I enabled Anonymous Authentication I got a different error:
I compared the web.config files from both sites. There is also a hint in the comments section of the MSDN blog post:
The solution for me was to add the module of type ‘Microsoft.Powershell.DesiredStateConfiguration.PullServer.AuthenticationPlugin’ in the web.config and enable Anonymous Authentication:
Another way is to install and enable WindowsAuthentication on the IIS. The configuration for the first solution, which is the complex one, was to add to the configuration for the pull server some lines:
I used the DSC Script resource and parsed the web.config file to check the authentication and the module. It looks like this:
configuration CreatePullServer { param ( [string[]]$NodeName = 'localhost', [ValidateNotNullOrEmpty()] [string] $certificateThumbPrint, [ValidateNotNullOrEmpty()] [string] $PSDSCPullServer ) Import-DSCResource -ModuleName xPSDesiredStateConfiguration Node $NodeName { ... Script TweakPSDSCComplianceServerWebConfig { SetScript = { $webconfig = "$env:SystemDrive\inetpub\wwwroot\PSDSCComplianceServer\web.config" $xml = [xml](Get-Content $webconfig) $root = $xml.get_DocumentElement() $WebServer=$root.SelectNodes("//system.webServer") $currentDate = (get-date).tostring("MM_dd_yyyy-hh_mm_ss") $backup = $webconfig + "_$currentDate" + ".bak" #check auth If ($WebServer.security.authentication.anonymousAuthentication.enabled -ne 'true') { $WebServer.security.authentication.anonymousAuthentication.SetAttribute('enabled','true') #first create a backup $xml.Save($backup) $xml.Save($webconfig) } #check module If ($WebServer.modules -eq $null) { #first create a backup $xml.Save($backup) #create modules element $modules = $xml.CreateElement('modules') #create add element $add = $xml.CreateElement('add') $add.SetAttribute('name','AuthenticationModule') $add.SetAttribute('type','Microsoft.Powershell.DesiredStateConfiguration.PullServer.AuthenticationPlugin') #append modules $xml.SelectSingleNode('//system.webServer').AppendChild($modules) | Out-Null #append add to modules $xml.SelectSingleNode('//system.webServer/modules').AppendChild($add) | Out-Null $xml.Save($webconfig) } Else{ #get modules $modules = @($root.SelectNodes("//system.webServer/modules/add")) [int]$byname=($modules | ?{$_.name -eq 'AuthenticationModule'} | Measure-Object).Count [int]$bytype= ($modules | ?{$_.type -eq 'Microsoft.Powershell.DesiredStateConfiguration.PullServer.AuthenticationPlugin'} | Measure-Object).Count If ($bytype -gt '0') { break } ElseIf ($byname -gt '0') { ForEach ($module in $modules) { If ($module.name -eq 'AuthenticationModule') { If ($module.type -ne 'Microsoft.Powershell.DesiredStateConfiguration.PullServer.AuthenticationPlugin') { #first create a backup $xml.Save($backup) $module.SetAttribute('type','Microsoft.Powershell.DesiredStateConfiguration.PullServer.AuthenticationPlugin') $xml.Save($webconfig) } } } } Else { #first create a backup $xml.Save($backup) #create add element $add = $xml.CreateElement('add') $add.SetAttribute('name','AuthenticationModule') $add.SetAttribute('type','Microsoft.Powershell.DesiredStateConfiguration.PullServer.AuthenticationPlugin') #append add to modules $xml.SelectSingleNode('//system.webServer/modules').AppendChild($add) | Out-Null $xml.Save($webconfig) } } } TestScript = { $webconfig = "$env:SystemDrive\inetpub\wwwroot\PSDSCComplianceServer\web.config" $xml = [xml](Get-Content $webconfig) $root = $xml.get_DocumentElement() $WebServer=$root.SelectNodes("//system.webServer") #check auth If ($WebServer.security.authentication.anonymousAuthentication.enabled -ne 'true') { return @($False) break; } #get modules $modules = @($root.SelectNodes("//system.webServer/modules/add")) [int]$byname=($modules | ?{$_.name -eq 'AuthenticationModule'} | Measure-Object).Count #| Out-Null [int]$bytype= ($modules | ?{$_.type -eq 'Microsoft.Powershell.DesiredStateConfiguration.PullServer.AuthenticationPlugin'} | Measure-Object).Count If ($bytype -gt '0') { return @($True) } ElseIf ($byname -gt '0') { ForEach ($module in $modules) { If ($module.name -eq 'AuthenticationModule') { If ($module.type -eq 'Microsoft.Powershell.DesiredStateConfiguration.PullServer.AuthenticationPlugin') { return @($True) } } } } return @($False) } GetScript = { @{ TestScript = $TestScript SetScript = $SetScript GetScript = $GetScript } } DependsOn = "[xDscWebService]PSDSCComplianceServer" } } }
Or the easy way using DSC WindowsFeature resource (just install WindowsAuthentication on IIS):
configuration CreatePullServer { param ( [string[]]$NodeName = 'localhost', [ValidateNotNullOrEmpty()] [string] $certificateThumbPrint, [ValidateNotNullOrEmpty()] [string] $PSDSCPullServer ) Import-DSCResource -ModuleName xPSDesiredStateConfiguration Node $NodeName { ... WindowsFeature IISWindowsAuth { Ensure = "Present" Name = "Web-Windows-Auth" DependsOn = "[WindowsFeature]IISFeature" } WindowsFeature DSCServiceFeature { Ensure = "Present" Name = "DSC-Service" DependsOn = @("[WindowsFeature]IISMgmtFeature","[WindowsFeature]IISFeature","[WindowsFeature]IISWindowsAuth") } } }
After this I was able to query the status:
Now the only thing what bothered me was the fact that you only get IP address and the status code. But also here a solution already exist: Ashley McGlone and Jonathan Walz posted it in the comments section of the MSDN blog post. I just put everything together in a script, which could be downloaded here.
When you run the script you will get the IP address resolved and also the description of the status code:
The script accepts 2 parameters:
- PullServer: The DSC pull server name
- Port: The port on which the PSDSCComplianceServer component on the server is listening(default is 9080)
This was the first part. In the next I will cover how I configured the nodes LCM dynamically using AD’s object GUID and individual certificates.
Pingback: Automate Exchange installation and configuration with DSC Part 2:Configure node’s LCM | The clueless guy
Pingback: Automate Exchange installation and configuration with DSC Part 3:Automate creation and publishing to pull server | The clueless guy
Pingback: To run iPowershell Desired State Configuration(DSC):How to enforce a consistency check? | The clueless guy
Pingback: 获取DSC pull服务器节点信息 - Exchange中文站
http://dhjsdhv2667226ll.com
LikeLike