Azure DSC extention via ARM template.

Hi, hoping someone can help me here.

I was receiving a parameter not found error for NodeConfigurationName prior to this so I removed it from the template. Now I’m getting another for AllowModuleOverwrite.

Last attempt to process request for sequence number 0 failed with error A parameter cannot be found that matches parameter name ‘AllowModuleOverwrite’.\n\nAnother common error is to specify parameters of type PSCredential without an explicit type. Please be sure to use a typed parameter in DSC Configuration, for example:\n\n configuration Example {\n param([PSCredential] $UserAccount)

According to this documentation both parameters exist.

Hey there Dan. Can you share the code on how you’re attempting to register the node?

 

Here is the DSC section. Thanks for looking.

{

"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('virtualMachineName'), '/Microsoft.Powershell.DSC')]",
"apiVersion": "2017-12-01",
"location": "eastus2",
"tags": {
"AutomationAccountARMID": "/subscriptions/..."
},
"scale": null,
"properties": {
"autoUpgradeMinorVersion": true,
"settings": {
"Properties": [
{
"Name": "RegistrationKey",
"Value": {
"UserName": "PLACEHOLDER_DONOTUSE",
"Password": "PrivateSettingsRef:registrationKeyPrivate"
},
"TypeName": "System.Management.Automation.PSCredential"
},
{
"Name": "RegistrationUrl",
"Value": "https://eus2-agentservice-prod-1.azure-automation.net/accounts/...",
"TypeName": "System.String"
},
{
"Name": "ConfigurationMode",
"Value": "applyAndMonitor",
"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"
}
],
"modulesUrl": "https://xxx.blob.core.windows.net/dscconfigs/xxx_servers.ps1.zip",
"configurationFunction": "[parameters('extensions_Microsoft.Powershell.DSC_configurationFunction')]",
"SasToken":"xxxsastokenxxx"
},
"publisher": "Microsoft.Powershell",
"type": "DSC",
"typeHandlerVersion": "2.76",
"protectedSettings": {
"Items": {
"registrationKeyPrivate": "xxxregistrationkeyxxx"
}
}
},
"dependsOn": [
"[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]"
]

 }

How were you passing the nodeConfigurationName? I can share with you a working example of mine. Perhaps try matching it up and see if it helps?

Variable Set:

    "dscLocalConfigurationManager": {
      "rebootNodeIfNeeded": true,
      "configurationMode": "ApplyAndAutocorrect",
      "actionAfterReboot": "ContinueConfiguration",
      "allowModuleOverwrite": true,
      "dscExtensionVersion": "2.72",
      "modulesURI": "https://azurestorage.blob.core.windows.net/automationdscpreview/RegistrationMetaConfigV2.zip",
      "configurationFunction": "RegistrationMetaConfigV2.ps1\\RegistrationMetaConfigV2"
    },

Resource Code:

    {
      "type": "Microsoft.Compute/virtualMachines/extensions",
      "apiVersion": "2015-06-15",
      "copy": {
        "count": "[variables('VMConfigReference').assignedConfig.instanceCount]",
        "name": "assignedConfigDSCBaseConfig"
      },
      "name": "[concat(variables('VMConfigReference').assignedConfig.Name,0,copyIndex(1),'/assignedConfigBaseDSC')]",
      "dependsOn": [
        "[resourceId('Microsoft.Compute/virtualMachines',concat(variables('VMConfigReference').assignedConfig.Name,0,copyIndex(1)))]"
      ],
      "location": "[resourceGroup().Location]",
      "properties": {
        "type": "DSC",
        "publisher": "Microsoft.Powershell",
        "typeHandlerVersion": "[variables('dscLocalConfigurationManager').dscExtensionVersion]",
        "settings": {
          "modulesUrl": "[variables('dscLocalConfigurationManager').modulesURI]",
          "configurationFunction": "[variables('dscLocalConfigurationManager').configurationFunction]",
          "Properties": [
            {
              "Name": "RegistrationKey",
              "Value": {
                "UserName": "PLACEHOLDER_DONOTUSE",
                "Password": "PrivateSettingsRef:registrationKeyPrivate"
              },
              "TypeName": "System.Management.Automation.PSCredential"
            },
            {
              "Name": "RegistrationUrl",
              "Value": "[parameters('automationRegistrationUrl')]",
              "TypeName": "System.String"
            },
            {
              "Name": "NodeConfigurationName",
              "Value": "[variables('VMConfigReference').assignedConfig.dscConfiguration]",
              "TypeName": "System.String"
            },
            {
              "Name": "ConfigurationMode",
              "Value": "[variables('dscLocalConfigurationManager').configurationMode]",
              "TypeName": "System.String"
            },
            {
              "Name": "RebootNodeIfNeeded",
              "Value": "[variables('dscLocalConfigurationManager').rebootNodeIfNeeded]",
              "TypeName": "System.Boolean"
            },
            {
              "Name": "ActionAfterReboot",
              "Value": "[variables('dscLocalConfigurationManager').actionAfterReboot]",
              "TypeName": "System.String"
            },
            {
              "Name": "AllowModuleOverwrite",
              "Value": "[variables('dscLocalConfigurationManager').allowModuleOverwrite]",
              "TypeName": "System.Boolean"
            }
          ],
          "wmfVersion": "5.1"
        },
        "autoUpgradeMinorVersion": true,
        "protectedSettings": {
          "Items": {
            "registrationKeyPrivate": "[parameters('automationRegistrationKey')]"
          }
        }
      }
    }

Thanks. I’m guessing the api version was the issue.

I took a stab at updating my code to the most current format but I can’t make sense of the instructions.

In some cases they use protectedsettings.items but in others they just put the values under protectedsettings.

Could very well be. I’ve also used 2017-03-30 in the same template (I should probably update them all), but I haven’t tried using the latest one (2017-12-XX).

oh my… I’m gonna lose it :slight_smile: Now I’m getting this.

The term ‘Get-AutomationPSCredential’ is not recognized as the name of a cmdlet, function, script file, or operable program.

If I connect the machine manually using the portal and assign the same configuration, this works fine.

 

Get-AutomationPSCredential doesn’t exist locally. You’ll leverage it in your configuration. When you deploy the config it’ll grab the cred with that cmdlet.

Configuration SomeComposite{

    Param(

        [parameter(Mandatory=$true)][string]$adminAccount


    )

    Import-DscResource -ModuleName 'xNetworking' -ModuleVersion '5.6.0.0'

    $AdminCred = Get-AutomationPSCredential -Name $adminAccount


    Node $AllNodes.NodeName
    {
        
    }
    Node ($AllNodes.Where{$_.Role -eq "TargetNode"}).NodeName
    {
        PSDscRunAsCredential = $AdminCred
    }

FYI - I did a blog series on this. You might find some of the info helpful. :slight_smile:

https://www.google.com/search?q=powershell.org+using+azure+desired+state+configuration&oq=powershell.org+using+azure+desired+state+configuration&aqs=chrome..69i57.12299j1j7&sourceid=chrome&ie=UTF-8

Ok, so when you compile the configuration that’s turned into the credential object.

How would we do this when copying just when publishing the configuration to blob storage?

Dan

Ahhh. You’re pushing the config. Have you looked at leveraging Azure Automation DSC? You can store the compiled mofs in an encrypted store and assign them out to your nodes for free (Azure nodes).

If you’re storing the config in blob store and pushing it to a machine, you’ll have to pass the credential during the template deployment. I might still have a template example laying around. I’ll have to look for it.

I was looking to incorporate the dsc registration process inside the arm template for building vm’s so the build person doesn’t have to connect and assign the configurations. Though it does seem like less work to give them the instructions on how to do it at this point:-)

I’ve seen a few posts on leveraging azure keyvaults but I was hoping to use the already stored credentials stored in the credential section of the automation account.

Yeah. The only time I did it with push in a template was by adding the extension as a child resource. You should be able to use it as a standalone resource though. Example code:

    {
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Compute/virtualMachines",
      "location": "[resourceGroup().location]",
      "name": "[variables('CMPrimaryName')]",
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageaccounts',variables('StorageAccountName'))]",
        "[resourceId('Microsoft.Network/networkInterfaces','cmprinif')]"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[variables('CMPrimaryVMSize')]"
        },
        "osProfile": {
          "computerName": "[variables('CMPrimaryName')]",
          "adminUsername": "[variables('adminUserName')]",
          "adminPassword": "[variables('adminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "[variables('imagePublisher')]",
            "offer": "[variables('imageOffer')]",
            "sku": "[variables('WindowsOSVersion')]",
            "version": "latest"
          },
          "osDisk": {
            "name": "[concat(variables('CMPrimaryName'),'-osdisk')]",
            "vhd": {
              "uri": "[concat('http://',variables('StorageAccountName'),'.blob.core.windows.net/vhds/',variables('CMPrimaryName'),'-osdisk.vhd')]"
            },
            "caching": "ReadWrite",
            "createOption": "FromImage"
          },
          "dataDisks": [
            {
              "name": "[concat(variables('CMPrimaryName'),'-data-disk1.vhd')]",
              "vhd": {
                "uri": "[concat('http://',variables('StorageAccountName'),'.blob.core.windows.net/vhds/',variables('CMPrimaryName'),'-data-disk1.vhd')]"
              },
              "caching": "None",
              "createOption": "Empty",
              "diskSizeGB": "1000",
              "lun": 0
            }
          ]
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces','cmprinif')]"
            }
          ]
        },
        "diagnosticsProfile": {
          "bootDiagnostics": {
            "enabled": true,
            "storageUri": "[concat('http://',variables('StorageAccountName'),'.blob.core.windows.net')]"
          }
        }
      },
      "resources": [
        {
          "type": "extensions",
          "apiVersion": "2015-05-01-preview",
          "name": "[concat(variables('CMPrimaryName'),'domainjoin')]",
          "location": "[resourceGroup().location]",
          "dependsOn": [
            "[resourceId('Microsoft.Compute/virtualMachines',variables('CMPrimaryName'))]",
            "[resourceId('Microsoft.Compute/virtualMachines',variables('DCVMName'))]"
          ],
          "properties": {
            "publisher": "Microsoft.PowerShell",
            "type": "DSC",
            "typeHandlerVersion": "2.8",
            "settings": {
              "ModulesUrl": "[concat(variables('ADAssetLocation'),'/Configuration.zip')]",
              "ConfigurationFunction": "Configuration.ps1\\DomainJoin",
              "Properties": {
                "DomainName": "[variables('ADDomainName')]",
                "AdminCreds": {
                  "UserName": "[variables('adminUserName')]",
                  "Password": "PrivateSettingsRef:adminPassword"
                }
              }
            },
            "protectedSettings": {
              "Items": {
                "adminPassword": "[variables('adminPassword')]"
              }
            }
          }
        }
      ]
    }

Bummer… I like the dynamic way of storing the credentials in the automation account so the configuration script doesn’t have to be modified if the credentials change. Variable would work but it’ll still deploy the resources if the password supplied is wrong.

I can use get-automationpscredential inside the configuration when deploying manually in the portal after connecting a vm and assigning a configuration.

Choosing the vm and adding the dsc extension and supplying a configuration (zipped) does not work.

Why these methods don’t work the same is beyond me.

You can use Get-AzureRmAutomationCredential and pass the object into the template via parameter.

I think I should mention I’m using the ‘Templates’ service within the portal. So the process for the person building a server would be to open the shared template and click deploy. They fill out the servername and password fields and then click purchase. I guess in a sense a static arm template, we’re not building a new template for each deployment.

Does this sound like it would work with Get-AzureRmAutomationCredential?

 

So the trick was to join to the domain via template vs. DSC. I’m slowly wrapping my head around this :slight_smile:

Now I want the dsc extension to dependon the domain join. Does this look correct?

"[resourceId('Microsoft.Compute/virtualMachines/extensions',concat(parameters('virtualMachineName'),'extensions/joindomain'))]"

This is the resource ID of the last machine I deployed.

Microsoft.Compute/virtualMachines/GSS-AZURE-T7/extensions/joindomain

Thaaaat looks right? I always go with ‘test it and see if it breaks’ when it comes to updating my JSON templates. :wink:

edit.

I managed to update it to the latest format and it attempts to deploy but in the VM dsc logs I see that it’s not appending the sas token to the configuration url. I have a ticket open with MS but no reply for three days. Is there anything you can see wrong with this?

{

"type": "Microsoft.Compute/virtualMachines/extensions",
"name": "[concat(parameters('vmName'),'/Microsoft.Powershell.DSC')]",
"apiVersion": "2017-12-01",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('vmName'), variables('extensionname'))]"
],
"properties": {
"publisher": "Microsoft.Powershell",
"type": "DSC",
"typeHandlerVersion": "2.76",
"autoUpgradeMinorVersion": true,
"settings": {
"wmfVersion": "latest",
"configuration": {
"url": "https://...storage.blob.core.windows.net/dscconfigs/servers.ps1.zip",
"script": "servers.ps1",
"function": "[parameters('extensions_Microsoft.Powershell.DSC_configurationFunction')]"
},
"protectedSettings": {
"Items": {
"registrationKeyPrivate": "123456"
},
"configurationUrlSasToken": "?sv=2017-07-29..."

},
"publicSettings": {
"configurationArguments": [{
"Name": "RegistrationKey",
"Value": {
"UserName": "PLACEHOLDER_DONOTUSE",
"Password": "PrivateSettingsRef:registrationKeyPrivate"
}
},
{
"RegistrationUrl": "https://eus2-agentservice-prod-1.azure-automation.net/accounts/...",
"NodeConfigurationName": "servers.localhost",
"ConfigurationMode": "ApplyAndAutocorrect",
"RebootNodeIfNeeded": true,
"ActionAfterReboot": "ContinueConfiguration",
"AllowModuleOverwrite": true
}]
}
}
}

}