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:
Install PowerShell modules listed in C:\AzureData\CustomData.bin or in parameter $ModuleNames) on the new VMSS instances
Install AzCopy command line tool
Configure PowerShell script runtime Logging
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.
Register the VMSS instance as a Azure Automation Runbook Worker
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
Comments