Microsoft Azure IoT and Cloud Solutions and Lessons Learned
Category: Powershell Desired State Configuration DSC
Desired State Configuration DSC is a management platform in Microsoft Windows Powershell that allows you to declaratively configure the servers in your data center or the cloud and allows you to maintain that configuration.
In my last post I described how to create a DC, in Azure, using DSC and ARM Templates. In this post, we will discuss how to automate the creation of a local – on premises – Acitve Directory Domain Services (ADDS) Domain Controller (DC) using DSC.
Overview
DSC has two modes: push mode and pull mode.
In push mode, you will author the configuration. You will then stage the configuration by creating MOF files. And finally, you will manually push the desired configuration onto the target server or node. The target server can be the local server or a remote server.
On the other hand, in DSC PULL mode, you author the configuration and stage it onto a designated Pull server. The target nodes contact the central pull server at regular intervals to obtain their desired configuration.
In our scenario, we will be using DSC in push mode. We will author the configuration and push it onto the local Windows server (not remotely).
Details
Prerequisites
On the target Windows Server (Windows Server 2008 R2 SP1 and Windows Server 2012 or 2012 R2):
Download and install Windows Management Framework (WMF). This WMF 5.0 is currently available and is the recommended version.
Copy the script below to the target server
Open the script below in Powershell ISE as administrator
Install the required Powershell modules using install-module: xActiveDirectory, xComputerManagement, xNetworking and xStorage.
Run the script
Run the script in Powershell ISE. The first command creates the .mof files which contain the desired configuration. The second command actually applies the configuration to the local server. After about half an hour and one reboot, you will have a fully functional Domain Controller with a new user (domain admin).
# Configure all of the settings we want to apply for this configuration
$ConfigData = @{
AllNodes = @(
@{
NodeName = 'localhost'
MachineName = 'spfarm-ad'
IPAddress = '10.0.0.4'
InterfaceAlias = 'Ethernet'
DefaultGateway = '10.0.0.1'
PrefixLength = '24'
AddressFamily = 'IPv4'
DNSAddress = '127.0.0.1', '10.0.0.4'
PSDscAllowPlainTextPassword = $true
PSDscAllowDomainUser = $true
}
)
}
Configuration BuildADDC {
param (
[Parameter(Mandatory)]
[String]$FQDomainName,
[Parameter(Mandatory)]
[PSCredential]$DomainAdminstratorCreds,
[Parameter(Mandatory)]
[PSCredential]$AdmintratorUserCreds,
[Int]$RetryCount=5,
[Int]$RetryIntervalSec=30
)
Import-DscResource -ModuleName PSDesiredStateConfiguration
Import-DscResource -ModuleName xActiveDirectory, `
xComputerManagement, `
xNetworking,
xStorage
Node $AllNodes.NodeName
{
LocalConfigurationManager
{
ActionAfterReboot = 'ContinueConfiguration'
ConfigurationMode = 'ApplyOnly'
RebootNodeIfNeeded = $true
}
# Change Server Name
xComputer SetName {
Name = $Node.MachineName
}
# Networking
xDhcpClient DisabledDhcpClient
{
State = 'Disabled'
InterfaceAlias = $Node.InterfaceAlias
AddressFamily = $Node.AddressFamily
}
xIPAddress NewIPAddress
{
IPAddress = $Node.IPAddress
InterfaceAlias = $Node.InterfaceAlias
PrefixLength = $Node.PrefixLength
AddressFamily = $Node.AddressFamily
}
xDefaultGatewayAddress SetDefaultGateway
{
Address = $Node.DefaultGateway
InterfaceAlias = $Node.InterfaceAlias
AddressFamily = $Node.AddressFamily
DependsOn = '[xIPAddress]NewIPAddress'
}
xDNSServerAddress SetDNS {
Address = $Node.DNSAddress
InterfaceAlias = $Node.InterfaceAlias
AddressFamily = $Node.AddressFamily
}
# Install the Windows Feature for AD DS
WindowsFeature ADDSInstall {
Ensure = 'Present'
Name = 'AD-Domain-Services'
}
# Make sure the Active Directory GUI Management tools are installed
WindowsFeature ADDSTools
{
Ensure = 'Present'
Name = 'RSAT-ADDS'
}
# Create the ADDS DC
xADDomain FirstDC {
DomainName = $FQDomainName
DomainAdministratorCredential = $DomainAdminstratorCreds
SafemodeAdministratorPassword = $DomainAdminstratorCreds
DependsOn = '[xComputer]SetName','[xDefaultGatewayAddress]SetDefaultGateway','[WindowsFeature]ADDSInstall'
}
$domain = $FQDomainName.split('.')[0]
xWaitForADDomain DscForestWait
{
DomainName = $domain
DomainUserCredential = $DomainAdminstratorCreds
RetryCount = $RetryCount
RetryIntervalSec = $RetryIntervalSec
DependsOn = '[xADDomain]FirstDC'
}
#
xADRecycleBin RecycleBin
{
EnterpriseAdministratorCredential = $DomainAdminstratorCreds
ForestFQDN = $domain
DependsOn = '[xADDomain]FirstDC'
}
# Create an admin user so that the default Administrator account is not used
xADUser FirstUser
{
DomainAdministratorCredential = $DomainAdminstratorCreds
DomainName = $domain
UserName = $AdmintratorUserCreds.UserName
Password = $AdmintratorUserCreds
Ensure = 'Present'
DependsOn = '[xADDomain]FirstDC'
}
xADGroup AddToDomainAdmins
{
GroupName = 'Domain Admins'
MembersToInclude = $AdmintratorUserCreds.UserName
Ensure = 'Present'
DependsOn = '[xADUser]FirstUser'
}
}
}
# Build MOF (Managed Object Format) files based on the configuration defined above
# (in folder under current dir)
# Local Admin is assigned
BuildADDC -ConfigurationData $ConfigData `
-FQDomainName 'spdomain.local' `
-DomainAdminstratorCreds (get-credential -Message "Enter Admin Credentials" -UserName "Administrator" ) `
-AdmintratorUserCreds (get-credential -Message "Enter New Admin User Credentials" -UserName "admin1" )
# We now enforce the configuration using the command syntax below
Start-DscConfiguration -Wait -Force -Path .\BuildADDC -Verbose -Debug
Lessons Learned
Since the Powershell xActiveDirectory module is being updated all the time, a DSC script that worked a year ago needs to be updated to work with WMF 5.0 (in the last quarter of 2016).
WMF 5.0 is included in the latest version of Windows 10 and included on Windows Server 2016.
Issues and Solutions
With some of the xActiveDirectory resources, the use of the fully qualified domain name (FQDN) produced an error: “Could not find mandatory property DomainName. Add this property and try again.”
Sometimes we may need to create an Active Directory Domain Services Domain Controller (DC) in an Azure IAAS Virtual Machine (VM). For example, I recently set up an IAAS SharePoint farm in the cloud. A DC on an Azure IAAS VM was a natural fit.
This post will discuss the steps required to deploy and provision such a DC in Azure – using Azure Resource Manager (ARM) templates combined with Powershell Desired State Configuration (DSC).
Brief Introduction to ARM templates and to Powershell Desired State Configuration (DSC)
ARM and DSC are tools that allow the description of infrastructure as code. Infrastructure described in code allows you to deploy infrastructure consistently. And it makes the build and deployment process repeatable.
ARM templates
ARM templates are JSON files that describe the resources (infrastructure components) that you need to deploy to Azure. For example, a VM, a Web App, a VNET or a NIC etc.
DSC
On the other hand, Powershell DSC allows you to describe the end state of how you like your – on premises or Cloud – server to look like. Once you have described your configuration with all the needed roles and features, DSC goes ahead and “makes it so”. In other words, it provisions the server per your specifications.
ARM Templates & DSC
Both ARM templates and Powershell DSC provide the necessary tools for consistently deploying applications. And consistently deployed applications are critical factors needed for DevOps.
Steps to create a DC in Azure
Overview of the Process
Create a new Azure Resource Group project is VS.
Add the Windows Virtual Machine template to the project.
Subsequently, add the DSC Extension resource.
A new DSC configuration data file (.psd1 aka Powershell Script data file) is added to the project. Alternatively, the PS data file may be hosted online.
Customize (edit) your JSON template files and your DSC files.
Deploy the solution to Azure.
Check on your Virtual Machine in Azure. Remote Desktop to the VM and verify that ADDS, DNS and ADDS Recycle Bin roles and features are enabled.
Detailed Steps:
1. I created a New Project in Visual Studio and chose Azure Resource Group for the type:
2. I chose Windows Virtual Machine as the Template:
choose Azure Virtual Machine
3. I opened the windowsvirtualmachine.json file. In the JSON outline (left-hand side), I right-clicked on Resources and added a new resource – PowerShell DSC Extension:
4. I added the Powershell Data file (.psd1). However, I ended up not using it because the deployment script did not find it (see details below). Instead, I uploaded the data file on Github and used a link (URL) to it in the JSON template.
5. I modified the WindowsVirtualMachine.json file adding the parameters and variables that will be used in the JSON document:
"parameters": {
"adminUsername": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Username for the Virtual Machine."
}
},
"adminPassword": {
"type": "securestring",
"metadata": {
"description": "Password for the Virtual Machine."
}
},
"dnsNameForPublicIP": {
"type": "string",
"minLength": 1,
"metadata": {
"description": "Globally unique DNS Name for the Public IP used to access the Virtual Machine."
}
},
"windowsOSVersion": {
"type": "string",
"defaultValue": "2012-R2-Datacenter",
"allowedValues": [
"2008-R2-SP1",
"2012-Datacenter",
"2012-R2-Datacenter"
],
"metadata": {
"description": "The Windows version for the VM. This will pick a fully patched image of this given Windows version. Allowed values: 2008-R2-SP1, 2012-Datacenter, 2012-R2-Datacenter."
}
},
"_artifactsLocation": {
"type": "string",
"metadata": {
"description": "Auto-generated container in staging storage account to receive post-build staging folder upload"
}
},
"_artifactsLocationSasToken": {
"type": "securestring",
"metadata": {
"description": "Auto-generated token to access _artifactsLocation"
}
},
"DSC-VMDCUpdateTagVersion": {
"type": "string",
"defaultValue": "1.0",
"metadata": {
"description": "This value must be changed from a previous deployment to ensure the extension will run"
}
},
"sizeOfDataDiskInGB": {
"type": "int",
"defaultValue": 100,
"metadata": {
"description": "Size of each data disk in GB"
}
},
"FQDomainName": {
"type": "string",
"metadata": {
"description": "Domain name to give to new ADDS Domain"
}
},
"AdminUser1CredsParam": {
"type": "securestring",
"metadata": {
"description": "Admin User password"
}
}
},
"variables": {
"imagePublisher": "MicrosoftWindowsServer",
"imageOffer": "WindowsServer",
"OSDiskName": "osdiskforwindowssimple",
"DataDiskName": "datadisk1nocache",
"nicName": "myVMNic",
"addressPrefix": "10.0.0.0/16",
"subnetName": "Subnet",
"subnetPrefix": "10.0.0.0/24",
"vhdStorageType": "Standard_LRS",
"publicIPAddressName": "myPublicIP",
"publicIPAddressType": "Dynamic",
"vhdStorageContainerName": "vhds",
"vmName": "ARMDCVM",
"vmSize": "Standard_A2",
"virtualNetworkName": "MyVNET",
"vnetId": "[resourceId('Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]",
"subnetRef": "[concat(variables('vnetId'), '/subnets/', variables('subnetName'))]",
"vhdStorageName": "[concat('vhdstorage', uniqueString(resourceGroup().id))]",
"diagnosticsStorageAccountName": "[variables('vhdStorageName')]",
"diagnosticsStorageAccountResourceGroup": "[resourcegroup().name]",
"accountid": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', variables('diagnosticsStorageAccountResourceGroup'), '/providers/', 'Microsoft.Storage/storageAccounts/', variables('diagnosticsStorageAccountName'))]",
"wadmetricsresourceid": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', variables('diagnosticsStorageAccountResourceGroup'), '/providers/', 'Microsoft.Compute/virtualMachines/', variables('vmName'))]",
"DSC-VMDCArchiveFolder": "DSC",
"DSC-VMDCArchiveFileName": "armdscvmdc.zip",
"DSC-ConfigFile": "armdscvmdc.ps1",
"DSC-ConfigDataFile": "armdscvmdc.psd1"
},
6. I edited the Virtual Machine resource section to add a Data Disk. A second disk (with caching off) is required for Domain Controllers in Azure. ADDS database and SYSVOL must NOT be stored on an Azure OS disk type.
7. Subsequently, I edited DSC section of the JSON template to include: configurationArguments (parameters passed to the DSC script) and ConfigurationData (URL to .psd1 file on Github)
9. When the DSC Extension is added to your project, the DSC script provided contains an example configuration. Since we are creating our own configuration, that example was removed and the following was added. The following DSC configuration provisions the DC and adds an administrative user account.
Configuration Main
{
Param(
[string] $NodeName,
[Parameter(Mandatory)]
[String]$FQDomainName,
[Parameter(Mandatory)]
[PSCredential]$DomainAdmin1Creds,
[Parameter(Mandatory)]
[PSCredential]$AdminUser1Creds,
[Int]$RetryCount=5,
[Int]$RetryIntervalSec=30
)
Import-DscResource -ModuleName PSDesiredStateConfiguration
Import-DscResource -ModuleName xActiveDirectory, `
xComputerManagement, `
xNetworking,
xStorage
Node $AllNodes.Where{$_.Role -eq "DC"}.Nodename
{
LocalConfigurationManager
{
ActionAfterReboot = 'ContinueConfiguration'
ConfigurationMode = 'ApplyOnly'
RebootNodeIfNeeded = $true
AllowModuleOverwrite = $true
}
xWaitforDisk Disk2
{
DiskNumber = 2
RetryIntervalSec =$RetryIntervalSec
RetryCount = $RetryCount
}
xDisk ADDataDisk
{
DiskNumber = 2
DriveLetter = 'F'
}
# Add DNS
WindowsFeature DNS
{
Ensure = "Present"
Name = "DNS"
}
# Install the Windows Feature for AD DS
WindowsFeature ADDSInstall {
Ensure = 'Present'
Name = 'AD-Domain-Services'
}
# Make sure the Active Directory GUI Management tools are installed
WindowsFeature ADDSTools
{
Ensure = 'Present'
Name = 'RSAT-ADDS'
}
# Create the ADDS DC
xADDomain FirstDC {
DomainName = $FQDomainName
DomainAdministratorCredential = $DomainAdmin1Creds
SafemodeAdministratorPassword = $DomainAdmin1Creds
DatabasePath = 'F:\NTDS'
LogPath = 'F:\NTDS'
SysvolPath = 'F:\SYSVOL'
DependsOn = '[WindowsFeature]ADDSInstall'
}
xWaitForADDomain DscForestWait
{
DomainName = $FQDomainName
RetryCount = $RetryCount
RetryIntervalSec = $RetryIntervalSec
DependsOn = '[xADDomain]FirstDC'
}
#
xADRecycleBin RecycleBin
{
EnterpriseAdministratorCredential = $DomainAdmin1Creds
ForestFQDN = $FQDomainName
DependsOn = '[xADDomain]FirstDC'
}
# Create an admin user so that the default Administrator account is not used
xADUser FirstUser
{
DomainAdministratorCredential = $DomainAdmin1Creds
DomainName = $FQDomainName
UserName = $AdminUser1Creds.UserName
Password = $AdminUser1Creds
Ensure = 'Present'
DependsOn = '[xADDomain]FirstDC'
}
xADGroup AddToDomainAdmins
{
GroupName = 'Domain Admins'
MembersToInclude = $AdminUser1Creds.UserName
Ensure = 'Present'
DependsOn = '[xADUser]FirstUser'
}
}
}
10. The DSC configuration data file (.psd1) on Github contains:
# Configure all of the settings we want to apply for this configuration
@{
AllNodes = @(
@{
NodeName = '*'
PSDscAllowPlainTextPassword = $true
PSDscAllowDomainUser = $true
},
@{
Nodename = "localhost"
Role = "DC"
}
)
}
11. Once all the JSON and DSC files are customized, the project can be deployed to Azure. Right-click on the project and select Deploy-New. Choose your Azure account and subscription. Verify the parameters entered and click OK to deploy to the Azure Resource Group.
12. After about 30 minutes, the deployment will be completed. Log in to the Azure portal and verify your DC is up and running. You can Connect to the DC via Remote Desktop to view your newly created DC
Lessons learned
Why use Visual Studio?
Microsoft Visual Studio 2015 Community Edition is a wonderful development IDE and it is quite good for authoring ARM templates and the DSC scripts. It goes ahead and deploys the whole solution to Azure and allows you to monitor the progress and catch any errors.
The advantage of VS for ARM and DSC
You can author the ARM templates and DSC scripts in any text editor or IDE and then deploy them using Powershell. However, in my experience, deploying the templates and DSC code from VS provided more feedback about the progress of the deployment. I do not have to add any parameters for verbose output and I do not have to check any log files.
Furthermore, VS is suitable for PowerShell DSC because you can add a VS Powershell extension (from the Visual studio Marketplace), that allows you to author and edit PS code.
Issues and Solutions
DSC Configuration Data File not being found during deployment
As mentioned above the .psd1 was originally added to the VS project. Once it is added to VS, you have to right-click on the .psd1 file and select Properties. For All Configurations select:
Build Action : Always
Copy to Ouput Directory: Copy Always
When I tried to change the Build Action, I received an error: An error occurred while saving the edited properties listed below: Build Action. One or more values are invalid. Mismatched PageRule with the wrong ItemType.
The workaround is to apply the second setting first (Copy Always). Then apply the first setting (Build Action).
The above workaround was good and allowed the .psd1 file to be copied to the correct location in the staging area along with the .ps1 file and the Zip archive. However, the deployment process still complained that the .psd1 file was not found “after 19 attempts”.
Online search on this error resulted in posts suggesting that a duplicate path in the PSModulePath environment variable may be the culprit. However, none of the workarounds and suggestions allowed the .psd1 file to be found.
Workaround: place the .psd1 file online. I placed the file on Github in order to get it deployed.