This is the third 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 to create MOF files reading a Configuration, Environment data and one CSV file.
In general you create a Configuration and an Environment Data file. In the Environment Data file you will define dedicated properties for each node if necessary.
Here is an example, which contains 4 nodes, a DAG configuration and regional namespaces:
@{ AllNodes = @( #Settings under 'NodeName = *' apply to all nodes. @{ NodeName = '*' ProductKey = 'HWY43-FY882-FM8YD-GR2XV-QH6DA' DefaultOAB = 'Default Offline Address Book' PagefileSize = '32778' #PSDscAllowPlainTextPassword = $true #The paths to the CSV files generated by the Server Role Requirements Calculator ServersCsvPath = "C:\DSC\CSVs\Servers.csv" MailboxDatabasesCsvPath = "C:\DSC\CSVs\MailboxDatabases.csv" MailboxDatabaseCopiesCsvPath = "C:\DSC\CSVs\MailboxDatabaseCopies.csv" } @{ Nodename = 'fabex01' FQDN = 'fabex01.fabrikam.local' Location = 'emea' DAGID = 'dag01' Role = 'FirstDAGMember' Mode = 'operation' CertificateFile = 'C:\DSC\Certs\fabex01.cer' Thumbprint = '2BD755C186CAB212B90ADAA48A3F27BA06473BBD' } @{ Nodename = 'fabex02' FQDN = 'fabex02.fabrikam.local' Location = 'emea' DAGID = 'dag01' Role = 'FirstDAGMember' Mode = 'operation' CertificateFile = 'C:\DSC\Certs\fabex02.cer' Thumbprint = '0720CA37DF4A7CD82EF0719D7670E1857D6D6AA5' } @{ Nodename = 'fabex03' FQDN = 'fabex03.fabrikam.local' Location = 'amer' DAGID = 'dag01' Role = 'FirstDAGMember' Mode = 'operation' CertificateFile = 'C:\DSC\Certs\fabex03.cer' Thumbprint = '8740BD236C35E4168DBE70469A8FF1203DBC55A4' } @{ Nodename = 'fabex04' FQDN = 'fabex04.fabrikam.local' Location = 'amer' DAGID = 'dag01' Role = 'FirstDAGMember' Mode = 'operation' CertificateFile = 'C:\DSC\Certs\fabex04.cer' Thumbprint = 'FA266475142CBCBF30764D435C5C7E72D899EE24' } ); #Settings that are unique per DAG will go in separate hash table entries. DAG01 = @( @{ ###DAG Settings### DAGName = 'DAG01' AlternateWitnessDirectory = $null AlternateWitnessServer = $null AutoDagAutoReseedEnabled = $true AutoDagDatabaseCopiesPerDatabase = '4' AutoDagDatabaseCopiesPerVolume = '4' AutoDagDatabasesRootFolderPath = 'C:\ExchangeDatabases' AutoDagDiskReclaimerEnabled = $true AutoDagTotalNumberOfDatabases = '16' AutoDagTotalNumberOfServers = '4' AutoDagVolumesRootFolderPath = 'C:\ExchangeVolumes' DatabaseAvailabilityGroupIpAddresses = '255.255.255.255' DatacenterActivationMode = 'DagOnly' #DomainController = $null ManualDagNetworkConfiguration = $false NetworkCompression = 'Enabled' NetworkEncryption = 'Enabled' ReplayLagManagerEnabled = $true ReplicationPort = '64327' WitnessDirectory = 'C:\FSW' WitnessServer = 'fabsrv01.fabrikam.local' SkipDagValidation = $True DbNameReplacements = @{"nn" = "01"} #database settings AllowServiceRestart = $false AutoDagExcludeFromMonitoring = $false BackgroundDatabaseMaintenance = '' CalendarLoggingQuota = '6GB' CircularLoggingEnabled = $true DatabaseCopyCount = '4' DataMoveReplicationConstraint = 'none' DeletedItemRetention = '30.00:00:00' DomainController = $null EventHistoryRetentionPeriod = '7.00:00:00' IndexEnabled = $true #IsExcludedFromProvisioning = $false IssueWarningQuota = '1700MB' #IsSuspendedFromProvisioning = $false #JournalRecipient = $null MailboxRetention = '30.00:00:00' MountAtStartup = $true ProhibitSendQuota = '2GB' ProhibitSendReceiveQuota = '2.25GB' RecoverableItemsQuota = '6GB' RecoverableItemsWarningQuota = '6227702784' RetainDeletedItemsUntilBackup = $false #AdServerSettingsPreferredServer = $null SkipInitialDatabaseMount = $false } ); #CAS settings that are unique per site will go in separate hash table entries as well. EMEA = @( @{ InternalNamespace = 'mail.fabrikam.local' ExternalNamespace = 'mail.fabrikam.com' #ClientAccessServer Settings AutoDiscoverSiteScope = 'HQ-Site' } ); AMER = @( @{ InternalNamespace = 'mailamer.fabrikam.local' ExternalNamespace = 'mailamer.fabrikam.com' #ClientAccessServer Settings AutoDiscoverSiteScope = 'HQ-Site' } ); }
As you can see those nodes have different settings. Assuming now you have not only 4 servers; maybe 40 or 80. That means you have to add for each node a block.
This causes the Environmetal Data file to grow and could be somehow error-prone. Especially when you cannot use the same certificate on all nodes in order to secure your MOF files.
Solution
As a solution I created a script, which imports the Configuration and Environmental Data files. It’s not needed anymore to have for your nodes a block defined. But you will need to provide an additional CSV file, which has at least a column called ServerName ( as in my previous script to configure the LCM described here).
The script will read the specified CSV file and will convert the values into a hashtable and add this one to the array AllNodes in the Environmental Data. The CSV file for the example above looks like this:
ServerName,FQDN,Location,DAGID,Role,Mode
fabex01,fabex01.fabrikam.local,emea,dag01,FirstDAGMember,operation
fabex02,fabex02.fabrikam.local,emea,dag01,AdditionalDAGMember,operation
fabex03,fabex03.fabrikam.local,amer,dag01,AdditionalDAGMember,operation
fabex04,fabex04.fabrikam.local,amer,dag01,AdditionalDAGMember,operation
Now you will ask me: “Where is the column for the NodeName, CertificateFile and Thumbprint?”
Well, that’s part of the script. The script won’t change anything of your files. An additional task is to read the CSV file, retrieve and export the certificate from the nodes and finally add the complete node data to the Environmental Data.
But this is not the only advantage. The script will also publish the created files to the directory of your pull server and creates a new checksum in order to let the node know that there is a new config waiting for it. It uses the objectguid of the computer account in AD as GUID. Thus means you don’t have to worry about keeping track of GUIDs to corresponding nodes.
This simplifies and automates the process of creating and publishing MOF files dramatically.
How does it looks like?
As a best practice I created a folder structure like this:
Under DSC there are several folders:
- Certs: Contains the exported certificates from the nodes
- Config: Contains the created MOF files, before they get copied to the pull server
- CSVs: Contains your self-created CSV file like in the example above and the output from the Exchange 2013 Server Role Requirements Calculator
- Scripts: Contains all your scripts and your Configuration and Environmental Data files
I run the script with the following parameters to create and publish the MOF files:
.\New-DSCConfigsFromFiles.ps1 -ServerCSVFile C:\DSC\CSVs\LabServers.csv -DSCConfig C:\DSC\Scripts\DemoConfig.ps1 -DSCConfigSettings C:\DSC\Scripts\DemoConfig-Config.psd1 -MOFsPath C:\DSC\Config -CertPath C:\DSC\Certs -PublishToPull -SkipPing -Verbose
When you use the -Verbose switch the script will tell you more info about what was found and what it is currently working on. As an example:
The script found a certificate with the thumbprint 2BD755C186CAB212B90ADAA48A3F27BA06473BBD on node fabex01 and exported it to C:\DSC\Certs\fabex01.cer. Then it used the GUID 9516aede-a6dd-4d63-ac18-a93fb1e48bd0, which was taken from the attribute objectguid from AD of this computer account, and copied the MOF file to the target folder C:\Program Files\WindowsPowerShell\DscService\Configuration.
When you look into the Certs folder you’ll find the exported certificates, which are used to encrypt the credentials:
All created MOF files before they got published in the Config folder:
And finally the published MOF files:
New-DSCConfigsFromFiles
You can download the script here. The script accepts the following parameters:
Parameter |
Description |
---|---|
ServerCSVFile | Full path to the CSV file containing the nodes |
DSCConfig | Path to your DSC Configuration file |
DSCConfigSettings | Path to your DSC Environment Data file |
MOFsPath | Path to a folder to store the generated MOF files |
CertPath | Path to a folder where all the certificates will be stored |
MOFsTargetPath | Path to the folder on the pull server to publish the files. Default is ‘C:\Program Files\WindowsPowerShell\DscService\Configuration’ |
PublishToPull | Switch to publish the files to the folder defined in MOFsTargetPath. Default is $true |
SkipPing | By default the script uses Test-Connection to check the availability of a node. If you use this switch this test will be bypassed. Default is $false |
SkipCert | Switch to bypass the certificate export. This is useful when you are using the same certificate on all nodes. Note: If you are going not to encrypt your credentials, make sure you have set the property PSDscAllowPlainTextPassword to $true! |
This was the third part of my journey. I hope you find it useful and I could help you to simplify things.
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 1:Pull server | The clueless guy
Pingback: To run iPowershell Desired State Configuration(DSC):How to enforce a consistency check? | The clueless guy
Pingback: Powershell DSC – Desired State Control – Resourses | DevOpsAut.com
Pingback: What is uploadReadAheadSize? | The clueless guy
Pingback: 文件配置DSC - Exchange中文站
Have been playing around with DSC and these scripts. I can get the new-dscconfigsfromfiles.ps1 to work but only if I run the configuration script first to generate the .mof file. I don’t get an error when running the script but it doesn’t seem to generate the .mof on it’s own. Everything else works nicely:)
LikeLike
Hi Jonathan, what command did you exactly use? Did you feed the script with a CSV file, which has a column “ServerName”?
LikeLike
Yes I did, found the issue actually it was a silly mistake. My DSCConfig wasn’t using the variable from the script for servername. Great articles by the way!!
LikeLike
Glad to hear that you could solve the issue! Thanks by the way!
LikeLike
Hi, i want to try your Script but i don’t know, what is the content of DemoConfig.ps1 and DemoConfig-Config.psd1.
Can you give an example?
LikeLike
Hi Timo,
sorry for the delay! Here is an example from my lab:
Configuration DemoConfig
{
Import-DscResource -Module xExchange 1.26.0.0
#settings for all nodes
Node $AllNodes.NodeName
{
xExchClientAccessServer CAS
{
Identity = $Node.NodeName
#AlternateServiceAccountCredential = $ASACreds
Credential = $ShellCreds
#CleanUpInvalidAlternateServiceAccountCredentials = $true
#AutoDiscoverSiteScope = “Site1″,”Site2”
AutoDiscoverServiceInternalUri = “https://autodiscover.fabrikam.local/autodiscover/autodiscover.xml”
#RemoveAlternateServiceAccountCredentials = $true
}
xExchTransportService TransportService
{
Identity = $Node.NodeName
Credential = $ShellCreds
AllowServiceRestart = $true
MaxPerDomainOutboundConnections = ’50’
}
xExchActiveSyncVirtualDirectory EASVdir
{
Identity = “$($Node.NodeName)\Microsoft-Server-ActiveSync (Default Web Site)”
Credential = $ShellCreds
Domaincontroller = “fabdc01.fabrikam.local”
#ActiveSyncServer = “https://bla/Microsoft-Server-ActiveSync”
BadItemReportingEnabled =$true
BasicAuthEnabled = $true
ClientCertAuth = “Ignore”
CompressionEnabled = $true
ExtendedProtectionFlags = ‘none’ #@(‘AllowDotlessSPN’) #@(‘none’)#@(“AllowDotlessSPN”,”NoServicenameCheck”)
ExtendedProtectionSPNList = @(“http/mail.fabrikam.com”) #@(“http/mail.fabrikam.com”,”http/mail.fabrikam.local”)
ExtendedProtectionTokenChecking = “Allow”
ExternalAuthenticationMethods = @(“Basic”,”Kerberos”)
ExternalUrl = “https://mail.fabrikam.com/Microsoft-Server-ActiveSync”
InstallIsapiFilter = $true
InternalAuthenticationMethods = @(“Basic”,”Kerberos”)
InternalUrl = “https://mail.fabrikam.local/Microsoft-Server-ActiveSync”
MobileClientCertificateAuthorityURL = “http://whatever.com/CA”
MobileClientCertificateProvisioningEnabled = $false
MobileClientCertTemplateName = “MyTemplateforEAS”
#Name = “$($Node.NodeName) EAS Site”
RemoteDocumentsActionForUnknownServers = “Block”
RemoteDocumentsAllowedServers = @(“AllowedA”,”AllowedC”)
RemoteDocumentsBlockedServers = @(“BlockedA”,”BlockedC”)
RemoteDocumentsInternalDomainSuffixList = @(“InternalA”,”InternalB”)
SendWatsonReport = $false
WindowsAuthEnabled = $true
}
xExchAutodiscoverVirtualDirectory AutoD
{
Identity = “$($Node.NodeName)\Autodiscover (Default Web Site)”
Credential = $ShellCreds
Domaincontroller = “fabdc01.fabrikam.local”
BasicAuthentication = $true
DigestAuthentication = $true
ExtendedProtectionFlags = @(“AllowDotlessSPN”,”NoServicenameCheck”)
ExtendedProtectionSPNList = @(“http/mail.fabrikam.com”,”http/mail.fabrikam.local”,”http/wxweqc”)
ExtendedProtectionTokenChecking = “Allow”
OAuthAuthentication = $false
WindowsAuthentication = $true
WSSecurityAuthentication = $true
}
xExchWebServicesVirtualDirectory EWS
{
Identity = “$($Node.NodeName)\EWS (Default Web Site)”
Credential = $ShellCreds
#CertificateAuthentication = $true
Domaincontroller = “fabdc01.fabrikam.local”
BasicAuthentication = $true
DigestAuthentication = $true
ExtendedProtectionFlags = @(“AllowDotlessSPN”,”NoServicenameCheck”)
ExtendedProtectionSPNList = @(“http/mail.fabrikam.com”,”http/mail.fabrikam.local”,”http/wxweqc”)
ExtendedProtectionTokenChecking = “Allow”
ExternalUrl = “https://mail.fabrikam.com/EWS/Exchange.asmx”
GzipLevel = “High”
InternalNLBBypassUrl = “https://$($Node.FQDN)/EWS/Exchange.asmx”
InternalUrl = “https://mail.fabrikam.local/EWS/Exchange.asmx”
OAuthAuthentication = $false
WindowsAuthentication = $true
WSSecurityAuthentication = $true
}
}
}
if ($ShellCreds -eq $null)
{
#$ShellCreds = Get-Credential -Message ‘Enter credentials for establishing Remote Powershell sessions to Exchange’
$User = “Fabrikam\administrator”
$PWord = ConvertTo-SecureString –String ‘Pa$$w0rd’ –AsPlainText -Force
$ShellCreds = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
}
if ($ASACreds -eq $null)
{
#$ShellCreds = Get-Credential -Message ‘Enter credentials for establishing Remote Powershell sessions to Exchange’
$UserASA = “ASA@fabrikam.com”
$PWordASA = ConvertTo-SecureString –String ‘Pa$$w0rd!’ –AsPlainText -Force
$ASACreds = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $UserASA, $PWordASA
}
LikeLike
Are these files available somewhere else? It seems the link is now broken.
LikeLike
Hi Dave,
sorry for the delay! I’ve uploaded the scripts to my GitHub repository:
https://github.com/IngoGege/DSC
Ciao,
Ingo
LikeLike