Azure Arm Template – VM Domain Join Automation

2017, Nov 27    

Domain

In this post we’re going to look at the ARM template and steps needed to simply Domain Join a Windows VM using Powershell DSC and Azure Automation.

The Arm template will do the following;

  • Create an Automation Account
  • Add a Automation Credential
  • Add a Automation Variable
  • Import a Powershell module
  • Add a DSC Configuration

We’ll then use a simple powershell script to

  • Specify the ConfigData
  • Compile the DSC Node Configuration

Then we’ll take to the portal and tell a VM to use this config.

Arm template

Take particular note of the parameters. They set up the Automation variable that holds the domain name, and the credentials for the user account that has permission in the domain to perform domain join.

The script can also be found here on github: https://github.com/Gordonby/DscLab/blob/master/AutomationAccountDeploy/AzureDeploy.json

{
  "$schema": "http://schemas.microsoft.org/azure/deploymentTemplate?api-version=2015-01-01#",
  "contentVersion": "1.0",
  "parameters": {
      "AutomationAccountName": {
          "defaultValue": null,
          "type": "string"
      },
      "DomainName": {
          "defaultValue": "gazuredomain.byers.me",
          "type": "string"
      },
      "Username": {
          "type": "string",
          "defaultValue": "domjoiner@gazuredomain.byers.me",
          "metadata": {
              "description": "Domain username that has permission to join computers to domain"
          }
      },
      "Password": {
          "type": "string",
          "defaultValue": "myPassword"
      }
  },
  "variables": {
      "sku": "Free",
      "variables_DomainName": "gAzureDomainName",
      "credentials_Name": "GAzureDomainCreds",
      "xDSCDomainjoin110Name": "xDSCDomainjoin",
      "xDSCDomainjoin110Uri": "https://devopsgallerystorage.blob.core.windows.net/packages/xdscdomainjoin.1.1.0.nupkg"
  },
  "resources": [
      {
          "name": "[parameters('AutomationAccountName')]",
          "type": "Microsoft.Automation/automationAccounts",
          "apiVersion": "2015-10-31",
          "location": "[resourceGroup().location]",
          "dependsOn": [],
          "tags": {},
          "properties": {
              "sku": {
                  "name": "[variables('sku')]"
              }
          },
          "resources": [
              {
                  "name": "[variables('credentials_Name')]",
                  "type": "credentials",
                  "apiVersion": "2015-10-31",
                  "location": "[resourceGroup().location]",
                  "dependsOn": [
                      "[concat('Microsoft.Automation/automationAccounts/', parameters('AutomationAccountName'))]"
                  ],
                  "tags": {},
                  "properties": {
                      "userName": "[parameters('UserName')]",
                      "password": "[parameters('Password')]"
                  }
              },
              {
                  "name": "[variables('variables_DomainName')]",
                  "type": "variables",
                  "apiVersion": "2015-10-31",
                  "location": "[resourceGroup().location]",
                  "dependsOn": [
                      "[concat('Microsoft.Automation/automationAccounts/', parameters('AutomationAccountName'))]"
                  ],
                  "tags": {},
                  "properties": {
                      "isEncrypted": false,
                      "type": "String",
                      "value": "[concat('\"',parameters('DomainName'),'\"')]",
                      "description": ""
                  }
              },
              {
                "name": "[variables('xDSCDomainjoin110Name')]",
                "type": "modules",
                "apiVersion": "2015-10-31",
                "location": "[resourceGroup().location]",
                "dependsOn": [
                    "[concat('Microsoft.Automation/automationAccounts/', parameters('AutomationAccountName'))]"
                ],
                "tags": {},
                "properties": {
                    "contentLink": {
                        "uri": "[variables('xDSCDomainjoin110Uri')]"
                    }
                }
            },
            {
              "name": "GDomain",
              "type": "configurations",
              "apiVersion": "2015-10-31",
              "properties": {
                "logVerbose": false,
                "logProgress": false,
                "source":{
                  "type":"embeddedContent",
                  "value":"Configuration GDomain \r\n { \r\n Import-DscResource -ModuleName 'PSDesiredStateConfiguration' \r\n Import-DscResource -ModuleName 'xDSCDomainjoin' \r\n $dscDomainAdmin = Get-AutomationPSCredential -Name 'GAzureDomainCreds' \r\n $dscDomainName = Get-AutomationVariable -Name  'GAzureDomainName' \r\n \r\n node $AllNodes.NodeName \r\n { \r\n xDSCDomainjoin JoinDomain \r\n { \r\n Domain = $dscDomainName \r\n Credential = $dscDomainAdmin \r\n  } \r\n } \r\n }"
               },
                "parameters": {},
                "description": "Domain join config"
              },
              "location": "[resourceGroup().location]",
              "dependsOn": [
                "[concat('Microsoft.Automation/automationAccounts/', parameters('AutomationAccountName'))]"
            ]
            }
          ]
      }
  ],
  "outputs": {}
}

Config. Compliation script

This Powershell script will connect to Azure and submit a compilation of the DSC Configuration defined in the Arm template.

#This initiates a compilation of a DSC Configuration.
#The portal doesn't allow these params to be set
#So we have to do it by PowerShell.

param(
    $AutomationAccountName="dscplay8",
    $AutomationAccountResourceGroup="DSCPlay8",
    $ConfigurationName = "GDomain"
)

Login-AzureRmAccount

#Define the configuration object
$ConfigData = @{
    AllNodes = @(
        @{
            NodeName = "SimpleDomJoin" 
            PSDscAllowPlainTextPassword = $True
            PSDscAllowDomainUser = $True
        }
    )
}

#Submit the Configuration for compilation
Start-AzureRmAutomationDscCompilationJob -ResourceGroupName $AutomationAccountResourceGroup -AutomationAccountName $AutomationAccountName -ConfigurationName $ConfigurationName -ConfigurationData $ConfigData

Assign configuration to existing VM.

Now that the Configuration has compiled, it’s ready to be used.
To assign it to a VM that already exists, the quickest way is to use the Portal.
Follow the wizard instructions to complete the enrolment, wait whilst the config is applied.
DSC Nodes

Applying VM Config during VM Arm template build

If you wanted to domain join a VM or VM Scaleset which is created from an Arm template then you can leverage this script. The 3 variables that need to be set;

  1. automationRegistrationUrl – Registration url from your azure automation account
  2. automationKey – access key from your azure automation account
  3. automationConfiguration – name of the configuration to apply.
"extensions": [
   {
    "name": "Microsoft.Powershell.DSC",
    "properties": {
        "publisher": "Microsoft.Powershell",
        "type": "DSC",
        "typeHandlerVersion": "2.19",
        "autoUpgradeMinorVersion": true,
        "protectedSettings": {
        "Items": {
            "registrationKeyPrivate": "[variables('automationKey')]"
        }
        },
        "settings": {
        "ModulesUrl": "https://github.com/Gordonby/DscLab/blob/master/VMDSCExtension/UpdateLCMforAAPull.zip?raw=true",
        "SasToken": "",
        "ConfigurationFunction": "UpdateLCMforAAPull.ps1\\ConfigureLCMforAAPull",
        "Properties": [
            {
            "Name": "RegistrationKey",
            "Value": {
                "UserName": "PLACEHOLDER_DONOTUSE",
                "Password": "PrivateSettingsRef:registrationKeyPrivate"
            },
            "TypeName": "System.Management.Automation.PSCredential"
            },
            {
            "Name": "RegistrationUrl",
            "Value": "[variables('automationRegistrationUrl')]",
            "TypeName": "System.String"
            },
            {
            "Name": "NodeConfigurationName",
            "Value": "[variables('automationConfiguration')]",
            "TypeName": "System.String"
            },
            {
            "Name": "ConfigurationMode",
            "Value": "ApplyAndAutoCorrect",
            "TypeName": "System.String"
            },
            {
            "Name": "ConfigurationModeFrequencyMins",
            "Value": 15,
            "TypeName": "System.Int32"
            },
            {
            "Name": "RefreshFrequencyMins",
            "Value": 30,
            "TypeName": "System.Int32"
            },
            {
            "Name": "RebootNodeIfNeeded",
            "Value": true,
            "TypeName": "System.Boolean"
            },
            {
            "Name": "ActionAfterReboot",
            "Value": "ContinueConfiguration",
            "TypeName": "System.String"
            },
            {
            "Name": "AllowModuleOverwrite",
            "Value": false,
            "TypeName": "System.Boolean"
            },
            {
            "Name": "Timestamp",
            "Value": "MM/dd/yyyy H:mm:ss tt",
            "TypeName": "System.String"
            }
        ]
        }
    }
}]