Desired State Configuration (DSC) resources for VMware allows you to apply standard configuration management processes through PowerShell DSC and PowerCLI. DSC can manage and monitor a system’s configuration based on configuration files.
The image below from VMware’s website provides a good overview of the layout :
DSC Resources for VMware is a little different than a standard DSC configuration, using a proxy because the LCM cannot run on VCSA or ESXi host.
Setting Up Azure Automation Configuration Management as a pull server
First of all we need to create an Automation Account.
- Log into Azure, do a search for Automation Accounts.
- Then select Add to create an Automation account.
- Next, fill out the blade with your Name, Subscription, Resource group and Location.
Note: Keep the option for Azure Run As Account on Yes and select Create:
The Run As account expires one year from the date of creation. At some point before your Run As account expires, you must renew the certificate. You can renew it any time before it expires.
Now we need two modules (AzureRM.Automation & xPSDesiredStateConfiguration) which can be installed on the LCM node itself or on the management workstation.
Install-Module AzureRM.Automation -Force
Install-Module -name xPSDesiredStateConfiguration -Force
We will need to fill out the parameters to match our newly created automation account.
# Define the parameters for Get-AzureRmAutomationDscOnboardingMetaconfig using PowerShell Splatting
$Params = @{
ResourceGroupName = 'BogdanLabDSC'; # The name of the Resource Group that contains your Azure Automation Account
AutomationAccountName = 'BogdanLabDSC'; # The name of the Azure Automation Account where you want a node on-boarded to
ComputerName = @('computername'); # The names of the computers that the meta configuration will be generated for
OutputFolder = "C:\DSCConfigs";
}
# Use PowerShell splatting to pass parameters to the Azure Automation cmdlet being invoked
# For more info about splatting, run: Get-Help -Name about_Splatting
Get-AzureRmAutomationDscOnboardingMetaconfig @Params
Note: Make sure the computername field matches the name of the LCM node.
Connect to Azure by typing in: connect-azurermaccount
A prompt window to log in to Azure will appear asking for your credentials.
Once logged in, we’ll run the code mentioned above generating our meta.mof file, the file that configures the LCM engine. In other words, this tell our vSphere DSC node to report into Azure for its config. file.
Copy the .meta.mof file to the LCM node itself and run Set-DSLOcalConfigurationManager locally (also you can use PSRemoting to push the configuration)
Set-DSCLocalConfigurationManager -path "C:\DscConfigs\DscMetaConfigs" -Computername computername -credential $creds -verbose
Note: Make sure the computername field matches the name of the LCM node.
Now, when we take a peek at our Automation Account in Azure, and select State Configuration (DSC), we can see our added node and a pretty graph for managing all of our nodes:
Note that the LCM node cannot be a part of a domain.
For VMware VMs deployment I will use an existing VM templates from my LAB, called TestVM and a Custom OS configuration.
Now we need to create DSC Resource module and upload it into created Automation Account. DSC Custom Resource for deploying VM’s will consist of a module file (.psm1) and a manifest file (.psd1)
DeployVM.psm1
[DscResource()]
class DeployVM {
[DscProperty(key)]
[String]$VMname
[DscProperty(Mandatory)]
[String]$VCenter
[DscProperty(Mandatory)]
[PSCredential]$Credentials
[DSCProperty()]
[String]$Template
[DscProperty(Mandatory)]
[String]$Customization
[DscProperty()]
[int]$CPU
[DscProperty()]
[int]$MemoryGB
[DscProperty(Mandatory)]
[String]$VMhost
[DscProperty(Mandatory)]
[String]$datastore
[DscProperty(Mandatory)]
[String]$cluster
hidden [PSObject] $Connection
#Create VM or update with settings if its already created
[void] Set(){
Try{
$this.ConnectVIServer()
$vm = $this.getvm()
if ($null -eq $vm){
#if VM doesnt exist, create it, if it does check Mem and CPU
Write-Verbose "Creating $($this.VMname)"
$result = $this.CreateVM()
if ($result -eq $true){
Write-Verbose "$($this.VMname) has been created "
} else {
throw "There was an issue creating the VM"
}
}else{
#Set Memory
if ($vm.MemoryGB -ne $this.MemoryGB) {
#verify if VM is powered off or on if so check for hot add
if ($vm.PowerState -eq 'Poweredon'){
Write-Verbose "$($this.VMname) is powered on, checking for Hot Add"
#if hot add is enabled and memory is less than what is declared
if($vm.ExtensionData.Config.MemoryHotAddEnabled -eq $true -and $vm.MemoryGB -lt $this.MemoryGB){
Write-Verbose "Hot add is enabled, adding memory"
$this.UpdateMemory()
} else {
Write-Error "Cannot set Memory while VM is powered on"
}
} Else{
$this.UpdateMemory()
}else {Write-error "Unable to set memory while VM is powered on"}
}
#Set CPU
if ($vm.NumCpu -ne $this.CPU) {
#verify if VM is powered off or on if so check for hot add
if ($vm.PowerState -eq 'Poweredon'){
Write-Verbose "$($this.VMname) is powered on, checking for Hot Add"
#if hot add is enabled and CPU is less than what is declared
if($vm.ExtensionData.Config.CpuHotAddEnabled -eq $true -and $vm.NumCpu -lt $this.CPU){
Write-Verbose "Hot add is enabled, adding CPU"
$this.UpdateCPU()
} else {
Write-Error "Cannot increase CPU while VM is powered on"
}
} Else {
$this.UpdateCPU()
}
}
}
} Catch{
Write-Verbose "There was an issue with setting the resource: $($_.Exception.Message)"
}
}
#Check if current settings of VM equal settings of the DSC config
[bool] Test() {
$this.ConnectVIServer()
Write-Verbose "Looking for VM: $($this.VMname)"
$VMConfig = $this.getvm()
return $this.Equals($VMConfig)
}
#Get the current settings of the VM
[DeployVM] Get() {
$result = [DeployVM]::new()
$this.ConnectVIServer()
Write-Verbose "Looking for VM: $($this.VMname)"
$vm = $this.getvm()
$result.VMname = $vm.name
$result.VCenter = $this.VCenter
$result.Credentials = $this.Credentials
$result.template = $this.Template
$result.Customization = $this.Customization
$result.CPU = $vm.NumCpu
$result.MemoryGB = $vm.MemoryGB
$result.VMhost = $vm.VMHost
$result.datastore = (get-datastore | where-object {$_.id -eq $vm.DatastoreIdList}).Name
$result.cluster = (get-cluster -vm $vm).name
return $result
}
#Helpers
#Import modules and connect to VC
[void] ConnectVIServer() {
$savedVerbosePreference = $global:VerbosePreference
$global:VerbosePreference = 'SilentlyContinue'
Import-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue
$global:VerbosePreference = $savedVerbosePreference
if ($null -eq $this.Connection) {
try {
$this.Connection = Connect-VIServer -Server $this.vcenter -Credential $this.Credentials -ErrorAction Stop
}
catch {
throw "Cannot establish connection to server $($this.vcenter). For more information: $($_.Exception.Message)"
}
}
}
#create VM if doesnt esxist, if it does set the CPU and Memory
[bool] CreateVM() {
$VMcluster = Get-Cluster -Name $this.cluster
$props = @{
Name = $this.VMname
template = $this.Template
OSCustomizationSpec = $this.Customization
VMhost = (Get-VMHost -name $this.vmhost)
Datastore = $this.datastore
Server = $this.VCenter
resourcepool = $VMcluster
}
$VM = New-VM @props
if($vm.NumCpu -ne $this.CPU){ set-vm $vm -NumCpu $this.cpu -Confirm:$false }
if($vm.MemoryGB -ne $this.MemoryGB){ set-vm $vm -MemoryGB $this.MemoryGB -confirm:$false}
Start-vm $vm
if ($null -ne $vm){
return $true
} else {
return $false
}
}
[PSObject] GetVM(){
try{
$VM = Get-VM -Name $this.VMName -verbose:$false -ErrorAction SilentlyContinue | select -First 1
return $vm
}
catch{
write-verbose "VM is not there"
return $null
}
}
[void] UpdateMemory(){
Try{
set-vm $this.VMname -MemoryGB $this.MemoryGB -confirm:$false
}
Catch{
Throw "there is an issue setting the Memory"
}
}
[bool] Equals($VMConfig) {
$vm = $this.getvm()
#Check if VM exists
if ($null -eq $vm){
Write-Verbose "$($this.VMname) does not exist"
return $false
}
#Check CPU
if ($VMConfig.NumCpu -ne $this.CPU){
Write-Verbose "$($this.VMname) has $($vmconfig.NumCpu) vCPUs and should have $($this.CPU)"
return $false
}
#check Memory
if ($VMConfig.MemoryGB -ne $this.MemoryGB){
Write-Verbose "$($this.VMname) has $($vmconfig.MemoryGB) Memory and should have $($this.MemoryGB)"
return $false
}
return $true
}
[void] UpdateCPU(){
Try{
set-vm $this.VMname -NumCpu $this.CPU -confirm:$false
}
Catch{
Throw "there is an issue setting the CPU"
}
}
}
DeployVM.psd1
#
# Module manifest for module 'DeployVM'
#
# Generated by: Bogdan
#
# Generated on: 21/09/2021
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'DeployVM.psm1'
# Version number of this module.
ModuleVersion = '2.0'
# Supported PSEditions
# CompatiblePSEditions = @()
# ID used to uniquely identify this module
GUID = '184099d3-bb59-49f8-a4dc-f0f0gfg8b5cb'
# Author of this module
Author = 'Bogdan'
# Company or vendor of this module
CompanyName = 'Lab'
# Copyright statement for this module
Copyright = '(c) 2021 Bogdan. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Deploy VMwawre VM Module'
# Minimum version of the Windows PowerShell engine required by this module
# PowerShellVersion = ''
# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# CLRVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = '*'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = '*'
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = '*'
# DSC resources to export from this module
DscResourcesToExport = 'DeployVM'
Zip the two files together and upload it into Azure. Navigate to your Azure Automation account and select Modules and choose the Add a module button. Upload the .zip file created and choose Ok.
DeployVM module has “available” status now.
We’ll create a configuration to deploy two VM’s “TestVM1” and “TestVM2”.
Configuration DeployVM {
Import-DscResource -ModuleName DeployVM
Node LCMNODE {
#Credentials from Azure
$Cred = Get-AutomationPSCredential 'VMware'
$vccreds = New-Object System.Management.Automation.PSCredential ("administrator@vsphere.local", $cred.password)
#Apply Config to each host
foreach ($VMname in @("TestVM1","TestVM2")) {
DeployVM "VMConfig_$($VMName)" {
VMName = $VMName
VCenter = "192.168.0.111"
Credentials = $vccreds
Template = "TestVM"
Customization = "TestVM"
CPU = 2
MemoryGB = 4
VMhost = "192.168.0.103"
Datastore = "datastore1"
Cluster = "Cluster"
}
}
}
}
Make sure you specify the name of the credential you are storing, for example, mine is “VMware”.
Save the config file to a .ps1 and upload it as a configuration into Azure DSC. Under the Automation Account, select State Configuration (DSC) and select the configurations tab. Then click the Add button and upload the configuration file (.ps1). Click refresh and it will appear as a list of configurations. Select Compose Configuration to create our MOF file for our node:
Now we are ready to assign the new compiled configuration to our LCM node. Select the LCM node under the Nodes tab:
Select Assign Node Configuration and we’ll choose our “DeployVM.LCMNODE” configuration
Remote into the node and run the following command update the configuration:
Update-DscConfiguration -wait -verbose
We can see our VMs declared in the config file now exist:
If we delete a VM, change the CPU, or modify the Memory it will automatically get recreated/reconfigured again in 15 minutes during the next poll. This can be extremely powerful and we can get as granular with the configuration file and custom resources as we want.
Unique Visitors