Managing Hybrid Runbook Worker At Scale

Hello readers :smile:,

The Azure question of today is: how can we manage (registration or de-registration) the Hybrid Runbook Workers (HRW) at scale?

In this automated world, it is fool to think that we need to go each computer one by one and run a given script to accomplish the task for the question above. My customers were struggling to find a solution to the question.

For those of you which are not aware of what an HRW is, let me give a brief description of it: Hybrid Runbook Worker feature of Azure can be used to run runbooks directly on the machine (read: inside the guest OS) that's hosting the role and against resources in the environment to manage those local resources. Hence, this feature can enable the interaction with any physical or virtual on-prem server, Azure VM as well as with any 3rd party cloud VMs.

You can read more on the official documentation page at https://docs.microsoft.com/en-us/azure/automation/automation-hybrid-runbook-worker

HRW.png

Coming back to the question, I found some methods to manage your HRWs at scale. The choice, which is always up to you, could be influenced by the products and the configurations already in use in your data center.

Below, I am going to describe 3 common ways:

Let's start then …

Using Azure Automation State Configuration

As first method presented here, we can use Azure Automation State Configuration to manage our HRW at scale. Thanks to my colleague @Anthony Watherston, I was able to provide my customer with a file to fulfill the request.

In this case, the given computer(s), has to be already onboarded as DSC node (see Enable Azure Automation State Configuration | Microsoft Docs for more details).

So, assuming that DSC nodes are configured correctly, you can move on adding your configuration and then compiling it to make it usable by the nodes. Last step will be to assign a compiled configuration to a node (or nodes) to be applied to.

I will not dig into all necessary phases (adding, compiling, and assigning configuration). I'll just focus on the method and will provide you with the sample configuration file (basically a .ps1 file) that you can compile and use to onboard the HRW.

NOTE: This script can onboard either a single HRW or a group, depending on the optional parameter hrwName. If you pass an empty value, then it will be assigned to the NetBIOS computer name, thus creating a single HRW.

Configuration OnboardHRW
{
Param(
[Parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[string]$primaryKey,

[Parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[string]$endPointUrl,

[Parameter(Mandatory=$false)]
[string]$hrwName
)

Import-DscResource -ModuleName ‘PSDesiredStateConfiguration'

Node localhost
{
Script onboard
{
GetScript = { return @{Present = $true } }
TestScript = {
if (Test-Path HKLM:SOFTWAREMicrosoftHybridRunbookWorker)
{
$epURL = $using:endPointUrl
$dir = Get-ChildItem HKLM:SOFTWAREMicrosoftHybridRunbookWorker | Where-Object PSChildName -eq $epURL.Split(“/”)[-1]
if ($dir)
{
if((Get-ChildItem HKLM:SOFTWAREMicrosoftHybridRunbookWorker$($epURL.Split(“/”)[-1])).Name.Count -gt 1 )
{
return $true
}
else
{
return $false
}
}
else
{
return $false
}
}
else
{
return $false
}
}
SetScript = {
# Getting the AutomationAccount version directly from the folder inside the server. Saving 1 parameter.
$AzureAutomationVersion = Get-ChildItem -LiteralPath “C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation” | Select -ExpandProperty Name

# Validating group name paramter or setting the HRW name to be the NetBIOS computer name
$myHrwName = $using:hrwName

if(([string]::IsNullOrEmpty($myHrwName)) -or ([string]::IsNullOrWhitespace($myHrwName)))
{
$myHrwName = $env:COMPUTERNAME
}
else
{
$myHrwName = $myHrwName.Trim()
}

Import-Module -FullyQualifiedName “C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation$AzureAutomationVersionHybridRegistrationHybridRegistration.psd1”
Add-HybridRunbookWorker -GroupName $myHrwName -Url $using:endPointUrl -Key $using:primaryKey
}
}
}
}

You can modify the above configuration to be compiled using PowerShell instead of using the Azure State Configuration from the portal or to unregister the HRW. Give the Azure State Configuration enough time (based on the node onboarding settings you have specified) and you will get your HRW registered on all assigned nodes.

The ‘PowerShell Remoting' way

Another good method that I followed during some of my on-sites, is based on a PowerShell script that I created to make the HRW management at scale possible.

I came up with the idea of having a list of HRW, for instance in a csv file (in this case the content is supposed to be a 1:1 match of the HRW names with the computer names plus the necessary action) and passing each record as well as the necessary additional parameters to the script. This script could be executed from one computer to manage (just) one or more servers, according to what's in the list. In case of multiple entries, we could leverage the PowerShell Remoting.

The structure of the csv is quite simple, it's just a list of names followed by the corresponding action and separated by a comma (“,”) like the one in the screenshot below:

ServerList.PNG

The script will read the list and for every record will perform the registration:

  • Locally if the local computer name is equal to one of the records.
  • Remotely using PowerShell Remoting, if none of the records is matching the local computer name.

NOTE: Actions have been defined according to those available in the HybridRegistration PowerShell module. Refresh is an additional one which just execute Remove and Add in sequence.

The remote installation will be done using a dedicated remote session established every time with the New-PSSession cmdlet.

You can leverage my sample script below or create a brand new one if you like. The code I used is:

param(
[Parameter(Mandatory=$True,
ValueFromPipelineByPropertyName=$false,
HelpMessage='Insert the automation account endpoint from the Azure Portal –> Automation Account –> Keys –> URL',
Position=0)]
[string]$AutomationAccountURL,
[Parameter(Mandatory=$True,
ValueFromPipelineByPropertyName=$false,
HelpMessage='Insert the automation account key from the Azure Portal –> Automation Account –> Keys –> Primary Access Key',
Position=0)]
[string]$AutomationAccountKey
)

#Region Functions

Function doAction ([string]$fAction, [string]$fAutomationAccountURL, [string]$fAutomationAccountKey, [string]$fAzureAutomationVersion, [string]$fRegKey)
{
#Logging action and computer
Write-Host “Performing action <$fAction> on server <$env:COMPUTERNAME>”

Switch($fAction)
{
“Remove”
{
try
{
Remove-HybridRunbookWorker -url $fAutomationAccountURL -key $fAutomationAccountKey -ErrorAction Stop
write-host “The hybrid worker <$env:COMPUTERNAME> has been succesfully de-registered.” -ForegroundColor Green
}
catch
{
Write-Host “The hybrid worker <$env:COMPUTERNAME> was not registered.”
}

if (Test-Path $fRegKey)
{
write-host “Deleting the corresponding registry key <$fRegKey> and all its subkeys.”
Remove-Item $fRegKey -Recurse
write-host “Registry key <$fRegKey> and all its subkeys have been successfully deleted.” -ForegroundColor Green
}
else
{
write-host “The corresponding registry key <$fRegKey> was not existing or has been succesfully removed by the de-registration process” -ForegroundColor Yellow
}

# Restarting the service
Restart-Service healthservice
}

“Add”
{
if(Test-Path “C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation$fAzureAutomationVersionHybridRegistration”)
{
cd “C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation$fAzureAutomationVersionHybridRegistration”
Import-Module .HybridRegistration.psd1
try
{
Add-HybridRunbookWorker -Name $env:COMPUTERNAME -EndPoint $fAutomationAccountURL -Token $fAutomationAccountKey -ErrorAction Stop
write-host “The hybrid worker <$env:COMPUTERNAME> has been succesfully registered.” -ForegroundColor Green
}
catch
{
Write-Host “Exception generated while registering hybrid worker <$env:COMPUTERNAME>. The error is: $($_.exception)” -ForegroundColor Red
}
}
else
{
write-host “Path ‘C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation$fAzureAutomationVersionHybridRegistration' does not exist on computer <$env:COMPUTERNAME>. Check if the MMA is installed and that the AzureAutomation version folder is correct.” -ForegroundColor Red
}
}

“Refresh”
{
# Performing a remove operation
try
{
Remove-HybridRunbookWorker -url $fAutomationAccountURL -key $fAutomationAccountKey -ErrorAction Stop

if (Test-Path $fRegKey)
{
Remove-Item $fRegKey -Recurse
}

# Restarting the service
Restart-Service healthservice

# Performing an Add operation
if(Test-Path “C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation$fAzureAutomationVersionHybridRegistration”)
{
cd “C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation$fAzureAutomationVersionHybridRegistration”
Import-Module .HybridRegistration.psd1
try
{
Add-HybridRunbookWorker -Name $env:COMPUTERNAME -EndPoint $fAutomationAccountURL -Token $fAutomationAccountKey -ErrorAction Stop
write-host “The hybrid worker <$env:COMPUTERNAME> has been succesfully re-registered (refresh).” -ForegroundColor Green
}
catch
{
Write-Host “Exception generated while registering hybrid worker <$env:COMPUTERNAM>. The error is: $($_.exception)” -ForegroundColor Red
}
}
else
{
write-host “Path ‘C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation$fAzureAutomationVersionHybridRegistration' does not exist. Check if the MMA is installed and that the AzureAutomation version folder is correct.” -ForegroundColor Red
}
}
catch
{
Write-Host “Exception generated while removing hybrid worker <$env:COMPUTERNAME>. The error is: $($_.exception)” -ForegroundColor Red
}
}
}
}

#endregion

$currentLocation = Get-Location
$regKey = “HKLM:SOFTWAREMICROSOFTHybridRunbookWorker”
$AzureAutomationVersion = Get-ChildItem -LiteralPath “C:Program FilesMicrosoft Monitoring AgentAgentAzureAutomation” | Select -ExpandProperty Name

$srvList = Get-Content “.ServerList.csv”
ForEach ($srv in $srvList)
{
If (!($srv -like “#*”)-and !([string]::IsNullOrEmpty($srv)))
{
#parsing file
[string]$srvName = $srv.Split(“,”)[0].trim(“””,”””);
[string]$Action = $srv.Split(“,”)[1].trim(“””,”””).trim(” “,” “);

Set-Location $currentLocation
Start-Transcript -Path “.Manage-HybridWorker-At-Scale-$srvName.Log”

if($srvName -eq $env:COMPUTERNAME)
{
#Installing locally
Write-Host “======================================================” -ForegroundColor Magenta
Write-Host “======================================================” -ForegroundColor Magenta
write-host “Executing locally on server <$srvName>.” -ForegroundColor Cyan
doAction $Action $AutomationAccountURL $AutomationAccountKey $AzureAutomationVersion $regKey
}
else
{
#Installing remotely
Write-Host “======================================================” -ForegroundColor Magenta
Write-Host “======================================================” -ForegroundColor Magenta
write-host “Connecting to server <$srvName> using PowerShell Remote session.” -ForegroundColor Cyan
$psrSess = New-PSSession -ComputerName $srvName

If($psrSess.State -eq “Opened”)
{
Invoke-Command -Session $psrSess -ScriptBlock ${function:doAction} -Args $Action, $AutomationAccountURL, $AutomationAccountKey, $AzureAutomationVersion, $regKey
#write-host “Installation on server <$srvName>, finished.” -ForegroundColor Green
}
else
{
write-host “Error opening the remote session on server <$srvName>” -ForegroundColor Green
}
Remove-PSSession $psrSess
}

Stop-Transcript
}
}

Set-Location $currentLocation

#Logging and of script
Write-Host “All the servers in the file have been processed.”
Write-Host “<>”

To run it, you can use the command below. Make sure you replace the highlighted values:

.Manage-HybridWorker-At-Scale.ps1 -AutomationAccountURL "" -AutomationAccountKey ""

Leveraging SCOM

There's also another way to achieve our goal. During a customer engagement, something shone a light in me. This customer was still using and he had the SCOM Management management pack (aka mp) from Kevin Holman imported. Should you want to know more about importing management packs, please refer the How to Import, Export, and Remove an Operations Manager Management Pack | Microsoft Docs page.

One of the greatest features in this MP is that you can execute any PowerShell script. Here comes the idea. Since all the agents were directly connected with Log Analytics and the necessary solution in place (see Deploy a Windows Hybrid Runbook Worker in Azure Automation | Microsoft Docs), why not using this agent task passing a one-line script to it? Doing this way, we could make multiple selection, meaning that we will configure at scale.

By now, you should have everything necessary in place to go ahead with this proposed approach. If so:

  1. Open the Operations Console (see How to Connect to the Operations and Web Console | Microsoft Docs).
  2. In the bottom-left corner, select Monitoring.
Monitoring.png

  1. In the folder list, navigate to the Management view folder, expand it and select Agents.
ScomAgents.png

  1. In the center pane, select the agents you want to register as HRW.
ScomAgents_2.png

  1. In the Task pane on the right, under the SCOM Management Base Class Tasks, click on the task called Execute any PowerShell.
ExecuteAnyPowerShell.png

  1. Click on Override.
Override.png

  1. Configure the ScriptBody parameter with one of the commands below according to the purpose and click Override:
    • To register:

Add-HybridRunbookWorker -Name “$env:COMPUTERNAME” -EndPoint “” -Token “

  • To un-register:

Remove-HybridRunbookWorker -url “” -key “

ScriptBody.png

NOTE: You should replace the values in the above command with those pertaining to your environment.

 

  1. Click Run.
Run.png

These 3 methods are surely not the only ones existing, but they're a good starting point to start thinking AT SCALE. Isn't it :happyface:?

Thanks for reading,

Bruno.

 

This article was originally published by Microsoft's Secure Blog. You can find the original article here.