Powershell DSC is, from my perspective, the best way to bootstrap a Windows VM in Azure mainly because:

  • it’s regular Powershell DSC and you can pick and choose Push and/or Pull
  • it doesn’t require any agent to work since it’s Windows native (DSC)
  • it’s able to manage reboots (everybody in the Windows world knows that’s pretty impossible to complete configuration without having to reboot right in the middle of the process)

And here with you, the extension!

Azure Desired State Configuration extension can be consumed by any of your prefered methods in Azure: Portal, ARM Template, Azure Powershell, AZ CLI. Although it’s Azure native, it’s not fully Powershell DSC. A good thing about it is you don’t need to worry about certificates to encrypt credentials since Azure will manage all that for you as long as you transmit them in a secure manner (using SecureString parameters and protectedSettings in ARM).

How-to use it in Push Mode

For the sake of understanding how the extension actually works, let’s use the following DSC Configuration as an example and to guide the whole explanation:


Configuration DSCPush {
Node localhost {
LocalConfigurationManager {
ConfigurationMode = 'ApplyAndMonitor'
RebootNodeIfNeeded = $true
ActionAfterReboot = 'ContinueConfiguration'
}

File SomeRandomFile {
DestinationPath = "C:\Temp\RandomFile.txt"
Contents = "Hello World!"
Type = "File"
}

Script ForceReboot {
GetScript = {
Try{
Get-ItemProperty -Path "HKLM:\Software\DSCPushProcess\" -Name Reboot -ErrorAction Stop
Return @{"Result"=$true}
}
Catch{
Return @{"Result"=$false}
}
}
TestScript = {
Try{
Get-ItemProperty -Path "HKLM:\Software\DSCPushProcess\" -Name Reboot -ErrorAction Stop
Return $true
}
Catch{
Return $false
}
}
SetScript = {
New-Item -Path "HKLM:\Software\DSCPushProcess" -Force
New-ItemProperty -Path "HKLM:\Software\DSCPushProcess\" -Name Reboot -PropertyType Dword -Value 1
$Global:DSCMachineStatus = 1
}
DependsOn = "[File]SomeRandomFile"
}

File AnotherRandomFile {
DestinationPath = "C:\Temp\AnotherRandomFile.txt"
Contents = "Bye-bye World!"
Type = "File"
DependsOn = "[Script]ForceReboot"
}
}
}

As you can see, this configuration doesn’t do any useful: It creates a file in C:\Temp, then reboots the machine and after that it creates another file in C:\Temp; but it meets its purpose. So, let’s say this configuration is stored in a file called “DSCPush.ps1” for which next thing you need to do is compress it in order to be “consumable” by the extension. In this zip file, configuration ps1 should be at the root and any subfolder will be considered a module and, in consequence, they will get installed before using the ps1:

MyDSCConfiguration.zip
├── DSCConfiguration.ps1
├── Module_1
│   ├── 1.0.0.0
│   │   ├── Module_1.psd1
│   │   ├── Module_1.psm1
│   │   └── DSCResources/
└── Module_2/
    ├── 1.0.0.0
        ├── Module_2.psd1
        ├── Module_2.psm1
        └── DSCResources/

 

Once you have this zip file ready, now you go to the Azure Portal (or whatever method you prefer) and specify the configuration to use:

azure-dsc-portal.png

Now, let’s see what happens within the VM when this configuration is applied

Internals

As every Azure extension, there are two folders within the VM that play a keep role:

  • C:\Packages\Plugins\{extension}\{version}: This folder contains any files required by the extension to work as well as it’s the scratching area of the extension. In this particular case, the full path is: “C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0”
    azure-dsc-package-folder
  • C:\WindowsAzure\Logs\Plugins\{extension}\{version}: Here is where logs generated by the extesion are stored. In this particular case, the full path is: “C:\WindowsAzure\Logs\Plugins\Microsoft.Powershell.DSC\2.77.0.0”azure-dsc-logs
    The logs you actually care are the ones starting with DscExtensionHandler.

– Notice at the time I’m writting this article, latest DSC extesion version is 2.77 –

I’ll not spend time on how the extension gets actually enabled but I will focus and you should care. The only thing you should know is this whole process is started by a batch file (C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\bin\enable.cmd) which executes a powershell script (powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\bin\enable.ps1 -StartAsyncProcess) which runs your configuration. (Yes, ironically enough, DSC in Azure is executed by a batch file)

After the setup is completed and WMF as been upgraded (if you chose that), your ZIP file will be expanded into “C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\DSCWork{zip_file_name}.0″

azure-dsc-config-folder

If there are modules in the folder, they will get installed and the configuration will be compiled using “C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\DSCWork{zip_file_name}.0″ as the working folder, for which the configuration will be stored in “C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\DSCWork{zip_file_name}.0{configuration_name}”

azure-dsc-configuration-folder

Once the configuration has been locally compiled, the extension will reconfigure LCM (if you specify any specific settings, they won’t be taken into consideration at this point):

azure-dsc-lcm

After that, your configuration will be applied by using “Start-DscConfiguration” cmdlet and output of the configuration status will be logged as well:

azure-dsc-first-run

Now, this is the fun part, if LCM is configured to not reboot automatically and to not continue configuration after reboot, what do you think it will happen if my configuration requires a reboot in the middle of the process?

Well, LCM will finish and will remain in “PendingReboot” state waiting for the reboot to occur and will return control to the calling object, in this case “C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\bin\enable.ps1”. This script will analyze the status of LCM and act in consequence

azure-dsc-lcm-pendingreboot

After reboot,  “C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\bin\enable.cmd” will be executed again which will again execute “powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\Packages\Plugins\Microsoft.Powershell.DSC\2.77.0.0\bin\enable.ps1 -StartAsyncProcess” which will execute Start-DscConfiguration cmdlet with -UseExisting switch; in this whole process a new set of log files will be generated at “C:\WindowsAzure\Logs\Plugins\Microsoft.Powershell.DSC\2.77.0.0”.

azure-dsc-post-reboot

And the configuration will continue, however, when LCM continues the configuration, it doesn’t just pick up from where it left, the configuration will be evaluated all over from the begining expecting that YOU have properly managed the test conditions in each one of the resource:

azure-dsc-re-execution

The process will continue enacting the configuration, rebooting when needed and enacting again until the configuration is completed. At that point, LCM will be reconfigured again:

azure-dsc-lcm-completed

and extension installation will be marked as success. If for whatever reason it fails, you can go and check the same log files I showed you to see the details or you can manually repro the same behaviour as everything is left behind.

Trick, gotcha and more!

Use DSC Push in your advantage

One of the biggest down falls in DSC is its lack of facts (as Puppet does) which gives you the ability to adapt the configuration at runtime based on the machine you are running. DSC configurations are just MOF file which are not script files. However, since in the extension you don’t provide the MOF but you provide the PS1 file and this file gets compiled in the target machine, you can use this in your advantage by using conditions that you wouldn’t be able to apply in DSC Pull:

azure-dsc-push-example

It’s just a powershell script

The fact your configuration script is a DSC configuration, it doesn’t mean it’s no longer a powershell script which you can use for other things too. For example, if because of your configuration length you need to modify WSMan envelope size or configure the network profile, you can do that in the same script:

azure-dsc-push-example-2

Powershell Execution Policy precedence

You should not overlook this since, as I showed you, the extesion is actually a batch file calling a powershell script which calls your configuration. In order to do this execution, the batch file calls the powershell script using -ExecutionPolicy ByPass parameter

azure-dsc-executionpolicy

However, if you set the execution policy by Group Policies (either Machine or User, either local or by AD or just setting the registry entries) to a restrictive configuration, then the extension won’t be able to execute and it will hang since Group Policies have precedence over anything else. Powershell documentation states:

“…When determining the effective execution policy for a session, PowerShell evaluates the execution policies in the following precedence order:

  • Group Policy: Computer Configuration
  • Group Policy: User Configuration
  • Execution Policy: Process (or powershell.exe -ExecutionPolicy)
  • Execution Policy: CurrentUser
  • Execution Policy: LocalMachine…”

Useful Links