This article is contributed. See the original author and article here.

Hello readers :smile:,


The Azure Automation 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 Automation 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 DSC configuration 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 Automation 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 "<<End of script>>"

 


 


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


 


 

.Manage-HybridWorker-At-Scale.ps1 -AutomationAccountURL "<PUT_YOUR_AUTOMATIONACCOUNT_URL>" -AutomationAccountKey "<PUT_YOUR_AUTOMATIONACCOUNT_PRIMARYACCESSKEY>"

 


 


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 SCOM 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 SCOM Management view folder, expand it and select SCOM 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 "<YourAutomationAccountUrl>" -Token "<YourAutomationAccountPrimaryAccessKey>”

 



  • To un-register:


 

Remove-HybridRunbookWorker -url "<YourAutomationAccountUrl>" -key "<YourAutomationAccountPrimaryAccessKey>"

 


 


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.

Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.