top of page
Writer's pictureGeorge Lin

Create AAA Hybrid Worker Group On VMSS With PowerShell - Part 4


Step 24: Configure VMSS OS custom data

First, create a template custom data file named VMSSConfig.txt in $ENV:TEMP and add the content below

ConfigFile = C:\AzureData\CustomData.bin

LocalLogFilePath = D:\Log

InstallPath = D:\Downloads

MountPoint = L

LogFileNamePrefix = \AddWindowsMachineToHybridWorkerGroup

RemoveHybridWorerkLogFileNamePrefix = \RemoveHybridWorkerUponShutdown

ConfigAzureFileShareLogFileNamePrefix = \ConfigAzureFileShareWindows

AutoAcctUrl = https://*.agentsvc.eus2.azure-automation.net/accounts/*  

AutoAcctKey = *

scheduledEventsUrl = http://169.254.169.254/metadata/scheduledevents?api-version=2019-01-01

HybridWorkerGroupName = WorkerGroup01

SMTPServerName = mail.*.com

ConfigAzureFileShareScriptName = D:\Scripts\ConfigAzureFileShareWindows.ps1

ConfigAzureFileShareBlobURL = https://*.blob.core.windows.net/fileshare/ConfigAzureFileShareWindows.ps1?sp=*

RepetitionDurationDays = 3650

RepetitionIntervalMinutes = 1

StartAt = 12am

RemoveHybridWorkerUponshutdownScriptName = D:\Scripts\RemoveHybridWorkerUponshutdownV4.ps1

RemoveHybridWorkerUponshutdownBlobURL = https://*.blob.core.windows.net/fileshare/RemoveHybridWorkerUponshutdownV4.ps1?sp=*

HybridRunbookModulePath = C:\Program Files\Microsoft Monitoring Agent\Agent\AzureAutomation\7.3.837.0\HybridRegistration

HybridRumbookModuleName = HybridRegistration.psd1

ModuleNames = Az,SqlServer,AzTable

AzureFileTarget = *.file.core.windows.net

AzureFileUser = Azure\glintesteus2sa01

AzureFilePass = *

AzureFileShareRoot = \\*.file.core.windows.net\autorunbookworkspace

AzCopyDownloadURL = https://aka.ms/downloadazcopy-v10-windows

Second, populate or update the confidential parameter values in VMSSConfig.txt

$VMSSConfigFileName = "VMSSConfig.txt"
$VMSSConfigFile = ''
$VMSSConfigFilePath = Join-Path $env:TEMP $VMSSConfigFileName
$VMSSConfigFile = Get-Content $VMSSConfigFilePath

# Create a storage account SAS token
$SASStart = (Get-Date).ToUniversalTime()
$SASExpires = (Get-Date).AddYears(2).ToUniversalTime()
$VMSSInstConfigScript01SAS = New-AzStorageContainerSASToken -Container $SAContainerName `
    -StartTime $SASStart `
    -ExpiryTime $SASExpires `
    -Permission rl `
    -FullUri `
    -Context $MySAContext

$TheParaName = "SAContainerURLSAS"
$TheParaValue = $VMSSInstConfigScript01SAS
$TheLine = $VMSSConfigFile | Select-String $TheParaName | Select-Object -ExpandProperty Line
$TheNewLine = $TheParaName + " = " + $TheParaValue
if($null -eq $TheLine){
   Write-Output "String Not Found"
}else {
    Write-Output "Modifying the value of '$TheParaName' in the configure file"
     $VMSSConfigFile = $VMSSConfigFile | ForEach-Object { $_ -replace [RegEx]::Escape($TheLine), $TheNewLine }
}

$AutoAcctRegInfo = $MyTestAutoAcct | Get-AzAutomationRegistrationInfo

$TheParaName = "AutoAcctUrl"
$TheParaValue = $AutoAcctRegInfo.Endpoint
$TheLine = $VMSSConfigFile | Select-String $TheParaName | Select-Object -ExpandProperty Line
$TheNewLine = $TheParaName + " = " + $TheParaValue
if($null -eq $TheLine){
     Write-Output "String Not Found"
}else {
    Write-Output "Modifying the value of '$TheParaName' in the configure file"
     $VMSSConfigFile = $VMSSConfigFile | ForEach-Object { $_ -replace [RegEx]::Escape($TheLine), $TheNewLine }
}

$TheParaName = "AutoAcctKey"
$TheParaValue = $AutoAcctRegInfo.PrimaryKey
$TheLine = $VMSSConfigFile | Select-String $TheParaName | Select-Object -ExpandProperty Line
$TheNewLine = $TheParaName + " = " + $TheParaValue
if($null -eq $TheLine){
     Write-Output "String Not Found"
}else {
    Write-Output "Modifying the value of '$TheParaName' in the configure file"
     $VMSSConfigFile = $VMSSConfigFile | ForEach-Object { $_ -replace [RegEx]::Escape($TheLine), $TheNewLine }
}

$MyTestSAKey = (Get-AzStorageAccountKey -ResourceGroupName $RGName -Name $StorageAcctname).Value | Select-Object -First 1

$TheParaName = "AzureFilePass"
$TheParaValue = $MyTestSAKey
$TheLine = $VMSSConfigFile | Select-String $TheParaName | Select-Object -ExpandProperty Line
$TheNewLine = $TheParaName + " = " + $TheParaValue
if($null -eq $TheLine){
     Write-Output "String Not Found"
}else {
    Write-Output "Modifying the value of '$TheParaName' in the configure file"
     $VMSSConfigFile = $VMSSConfigFile | ForEach-Object { $_ -replace [RegEx]::Escape($TheLine), $TheNewLine }
}

$MySAFileShareInfo = $MySAFileShare | Select-Object -ExpandProperty ShareClient

$TheParaName = "AzureFileTarget"
$TheParaValue = $MySAFileShareInfo.AccountName + ".file.core.windows.net"
$TheLine = $VMSSConfigFile | Select-String $TheParaName | Select-Object -ExpandProperty Line
$TheNewLine = $TheParaName + " = " + $TheParaValue
if($null -eq $TheLine){
     Write-Output "String Not Found"
}else {
    Write-Output "Modifying the value of '$TheParaName' in the configure file"
     $VMSSConfigFile = $VMSSConfigFile | ForEach-Object { $_ -replace [RegEx]::Escape($TheLine), $TheNewLine }
}

$TheParaName = "AzureFileUser"
$TheParaValue = "Azure\"+$MySAFileShareInfo.AccountName
$TheLine = $VMSSConfigFile | Select-String $TheParaName | Select-Object -ExpandProperty Line
$TheNewLine = $TheParaName + " = " + $TheParaValue
if($null -eq $TheLine){
     Write-Output "String Not Found"
}else {
    Write-Output "Modifying the value of '$TheParaName' in the configure file"
     $VMSSConfigFile = $VMSSConfigFile | ForEach-Object { $_ -replace [RegEx]::Escape($TheLine), $TheNewLine }
}

$TheParaName = "AzureFileShareRoot"
$TheParaValue = ($MySAFileShareInfo.Uri -replace "https://", "\\") -replace "/","\"
$TheLine = $VMSSConfigFile | Select-String $TheParaName | Select-Object -ExpandProperty Line
$TheNewLine = $TheParaName + " = " + $TheParaValue
if($null -eq $TheLine){
     Write-Output "String Not Found"
}else {
    Write-Output "Modifying the value of '$TheParaName' in the configure file"
     $VMSSConfigFile = $VMSSConfigFile | ForEach-Object { $_ -replace [RegEx]::Escape($TheLine), $TheNewLine }
}

$VMSSConfigFile | Set-Content $VMSSConfigFilePath

Finally, inject the VMSS config file 'VMSSConfig.txt' as 'Custom data' into the VMSS instances at provisioning time. Custom data is only made available to the VM during first boot/initial setup. Custom data is placed in %SYSTEMDRIVE%\AzureData\CustomData.bin as a binary file. In our case, this file is a plain text file.

$VMSSConfigFileBytes = Get-Content $VMSSConfigFilePath -AsByteStream

$VMSSConfigFileEncoded = [system.convert]::ToBase64String($VMSSConfigFileBytes)

Update-AzVmss -ResourceGroupName $RGName -VMScaleSetName $VMSSName -CustomData $VMSSConfigFileEncoded

Step 25: Upload the PowerShell scripts to Azure blob container

The solution runs three PowerShell scripts on a new Azure VMSS instance to automatically transform it to an Azure Automation Hybrid Runbook Worker. All three scripts need to be uploaded to a storage blob container where VMSS is able to access them when provisioning a new instance.


The first script, named "AddWindowsMachineToHybridWorkerGroup.ps1", is automatically downloaded and executed by a Custom Script Extension on VMSS instances. This is the main script that performs the following configuration tasks in the listed order:

  1. Install PowerShell modules listed in C:\AzureData\CustomData.bin or in parameter $ModuleNames) on the new VMSS instances

  2. Install AzCopy command line tool

  3. Configure PowerShell script runtime Logging

  4. Download the PS script named "ConfigAzureFileShareWindows.ps1" from the blob container to the local drive and register a window scheduled task to run it once at VMSS instance creation time.

  5. Register the VMSS instance as a Azure Automation Runbook Worker

  6. Download the PS script named "RemoveHybridWorkerUponshutdown.ps1" from the blob container to the local drive and register another window scheduled task to run it every one minuet for 10 years (Repetition duration and interval are configured in C:\AzureData\CustomData.bin or in parameter $RepetitionDurationDays and $RepetitionIntervalMinutes)

The second script, named "ConfigAzureFileShareWindows.ps1", is scheduled to run once at instance creation time to mount a Azure storage file share to a local drive. The mount point (drive letter) and the credentials needed for accessing Azure storage file share are all retrieved from the Custom data file C:\AzureData\CustomData.bin.


The third script, named "RemoveHybridWorkerUponshutdown.ps1", is scheduled to run every minute to detect the VM termination event through calling a REST endpoint which is hard coded in C:\AzureData\CustomData.bin, for example, the full endpoint for the latest version of Scheduled Events is:

'http://169.254.169.254/metadata/scheduledevents?api-version=2019-01-01'

Once a termination (may due to VMSS scale in or manual VM shutdown) event received for the VM (VMSS instance), the script first remove/unregister the instance from the Automation Hybrid Worker Group, send email alert about the event, then approve the termination event.


To upload the scripts to storage blob container, change directory to where you saved the script files, for example, $ENV:TEMP, then run:

$VMSSCustomScriptName = "AddWindowsMachineToHybridWorkerGroup.ps1"

$VMSSInstConfigScriptName01 = "ConfigAzureFileShareWindows.ps1"

$VMSSInstConfigScriptName02 = "RemoveHybridWorkerUponshutdown.ps1"

$VMSSCustomScriptFilePath = Join-Path $env:TEMP $VMSSCustomScriptName

$VMSSCustomScriptBlobName = $BlobFolderName + "/" + $VMSSCustomScriptName

Set-AzStorageBlobContent -File $VMSSCustomScriptFilePath -Container $SAContainerName -Blob $VMSSCustomScriptBlobName -Context $MySAContext

$VMSSInstConfigScript01FilePath = Join-Path $env:TEMP $VMSSInstConfigScriptName01

$VMSSInstConfigScript01BlobName = $BlobFolderName + "/" + $VMSSInstConfigScriptName01

Set-AzStorageBlobContent -File $VMSSInstConfigScript01FilePath -Container $SAContainerName -Blob $VMSSInstConfigScript01BlobName -Context $MySAContext

$VMSSInstConfigScript02FilePath = Join-Path $env:TEMP $VMSSInstConfigScriptName02

$VMSSInstConfigScript02BlobName = $BlobFolderName + "/" + $VMSSInstConfigScriptName02

Set-AzStorageBlobContent -File $VMSSInstConfigScript02FilePath -Container $SAContainerName -Blob $VMSSInstConfigScript02BlobName -Context $MySAContext

Step 26: Add a Custom Script Extension to VMSS

$VMSSCustomScriptBlob = Get-AzStorageBlob -Context $MySAContext -Container $SAContainerName -Blob $VMSSCustomScriptBlobName

$VMSSCustomScriptBlobUri = ($VMSSCustomScriptBlob | Select-Object -ExpandProperty BlobBaseClient).Uri.AbsoluteUri

$ExtensionName = "CustomScriptExtension"
$ExtensionType = "CustomScriptExtension"
$Publisher = "Microsoft.Compute"
$ExtVer = "1.10"

$MyTestSAKey = (Get-AzStorageAccountKey -ResourceGroupName $RGName -Name $StorageAcctname).Value | Select-Object -First 1

$FileUri = @($VMSSCustomScriptBlobUri)

$PublicSettings = @{"fileUris" = $FileUri;"commandToExecute" = "powershell -ExecutionPolicy Unrestricted -File azautomaton\AddWindowsMachineToHybridWorkerGroup.ps1"}

$ProtectedSettings = @{"storageAccountName" = $StorageAcctname; "storageAccountKey" = "$MyTestSAKey"}; 

# Add the Custom Script extension to the scale set model

$MyTestVMSS = Get-AzVmss -VMScaleSetName $VMSSName -ResourceGroupName $RGName

$MyTestVMSSInstances = Get-AzVmssVM -ResourceGroupName $RGName -VMScaleSetName $VMSSName

if ($null -ne ($MyTestVMSS.VirtualMachineProfile.ExtensionProfile.Extensions | Where-Object Name -eq $ExtensionName -ErrorAction SilentlyContinue)){
    $MyTestVMSS | Remove-AzVmssExtension -Name $ExtensionName
    Update-AzVmss -ResourceGroupName $RGName -VMScaleSetName $VMSSName -VirtualMachineScaleSet $MyTestVMSS
}

Add-AzVMSSExtension -VirtualMachineScaleSet $MyTestVMSS `
    -Name $ExtensionName `
    -Publisher $Publisher `
    -Type $ExtensionType `
    -TypeHandlerVersion $ExtVer `
    -Setting $PublicSettings `
    -ProtectedSetting $ProtectedSettings `
    -Verbose
    
# Update the scale set

Update-AzVmss -ResourceGroupName $RGName -VMScaleSetName $VMSSName -VirtualMachineScaleSet $MyTestVMSS

Step 27: Test

Manually scale out/in the VMSS and observe the Hybrid Runtime Worker groups in the Automation account to verify if the end result same as what expected:
  • when a new VMSS instance is created, it should be automatically added to the Hybrid Runtime Worker group

  • when an existing VMSS instance is decommissioned , it should be automatically removed/unregistered from the Hybrid Runtime Worker group





Recent Posts

See All

Comments


bottom of page