Onboard Azure VMs to Update Management using runbook

折月煮酒 提交于 2021-01-29 10:34:21

问题


I've created a runbook to onboard Azure VMs to Update Management. The idea was taken from the MS provided runbook (https://github.com/azureautomation/runbooks/blob/master/Utility/ARM/Enable-AutomationSolution.ps1). The MS runbook uses old AzureRM modules, doesn't meet my needs and didn't work straight out of the box anyway.

My runbook in essence does the same thing, looks for VMs with a tag, installs the Microsoft Monitoring Agent and configures it to report to the workspace.

It then updates the query in the workspace to include the VM.

All this works and completes successfully but there is a messge in the Update Management portal saying "1 machine does not have 'Update Management' enabled" and "These machines are reporting to the Log Analytics workspace 'workspacename', but they do not have 'Update Management' enabled on them."

I'm not sure what other steps I am missing or whether something has changed and I can't see what the MS runbook does that mine doesn't.

Modules in runbook:

Az.Accounts
    
1/6/2021, 3:15 PM
    
Available
    
2.2.3
Az.Compute
    
1/6/2021, 3:17 PM
    
Available
    
4.8.0
Az.OperationalInsights
    
1/6/2021, 3:17 PM
    
Available
    
2.3.0
Az.Resources
    
1/6/2021, 3:17 PM
    
Available
    
3.1.1
Az.Storage
    
1/6/2021, 3:18 PM
    
Available
    
3.2.0

My runbook:

#Import-module -Name Az.Profile, Az.Automation, Az.OperationalInsights, Az.Compute, Az.Resources
#exit
#if ($oErr)
#{
#    Write-Error -Message "Failed to load needed modules for Runbook, check that Az.Automation, Az.OperationalInsights, Az.Compute and Az.Resources are imported into the Automation Account" -ErrorAction Stop
#}

# Fetch AA RunAs account detail from connection object asset
$ServicePrincipalConnection = Get-AutomationConnection -Name "AzureRunAsConnection" -ErrorAction Stop
$Connection = Add-AzAccount  -ServicePrincipal -TenantId $ServicePrincipalConnection.TenantId `
-ApplicationId $ServicePrincipalConnection.ApplicationId -CertificateThumbprint $ServicePrincipalConnection.CertificateThumbprint -ErrorAction Continue -ErrorVariable oErr
if ($oErr)
{
    Write-Error -Message "Failed to connect to Azure" -ErrorAction Stop
}
else
{
    write-output "connected to Azure"
}


#get the LA subscription
$LogAnalyticsSolutionSubscriptionId = Get-AutomationVariable -Name "LASolutionSubscriptionId"
#get the LA workspace RG
$LogAnalyticsSolutionWorkspaceRG = Get-AutomationVariable -Name "LASolutionWorkspaceRG"
#get the LA workspace Name
$LogAnalyticsSolutionWorkspaceName = Get-AutomationVariable -Name "LASolutionWorkspaceName"
#get the LA workspace Id
$LogAnalyticsSolutionWorkspace = Get-AzOperationalInsightsWorkspace -Name $LogAnalyticsSolutionWorkspaceName -ResourceGroupName $LogAnalyticsSolutionWorkspaceRG
#$LogAnalyticsSolutionWorkspaceId = Get-AutomationVariable -Name "LASolutionWorkspaceId"
#get the LA workspace key
$LogAnalyticsSolutionWorkspaceKey = AzOperationalInsightsWorkspaceSharedKey -ResourceGroupName $LogAnalyticsSolutionWorkspaceRG -Name $LogAnalyticsSolutionWorkspaceName

#$PublicSettings=@{"workspaceId" = $LogAnalyticsSolutionWorkspace.CustomerId};
#write-output "public settings are: "
#write-output $PublicSettings

write-output "sid is $LogAnalyticsSolutionSubscriptionId, wid is $($LogAnalyticsSolutionWorkspace.CustomerId), key is $($LogAnalyticsSolutionWorkspaceKey.PrimarySharedKey)"

if ($null -eq $LogAnalyticsSolutionSubscriptionId -or $Null -eq $LogAnalyticsSolutionWorkspace -or $null -eq $LogAnalyticsSolutionWorkspaceKey)
{
    Write-Error -Message "Unable to retrieve variables from automation account."
    exit
}



#get VM list

$VMsWantingPatching=Get-AzResource -TagName "patching" -TagValue "true" -ResourceType "Microsoft.Compute/virtualMachines"  -ErrorAction Continue -ErrorVariable oErr
if ($oErr)
{
    Write-Error -Message "Could not retrieve VM list" -ErrorAction Stop
}
elseif ($Null -eq $VMsWantingPatching)
{
    Write-Error -Message "No VMs need patching"  -ErrorAction Stop
}
else
{
    write-output "Successfully retrieved VM list"
}

foreach ($VM in $VMsWantingPatching)
{

Try
{
#Configure MMA on the VM to report to the UM LA workspace, unfortunately the workspace ID and key need to be hardcoded
#as passing a parameter does not work.
#Using this method preserves existing workspaces on the MMA agent, this method only adds, not replaces like using ARM would do
$CurrentVM=Get-AzVM -name $VM.name
Set-AzVMExtension -ExtensionName "MicrosoftMonitoringAgent" `
                -ResourceGroupName $VM.ResourceGroupName `
                -VMName $VM.Name `
                -Publisher "Microsoft.EnterpriseCloud.Monitoring" `
                -ExtensionType "MicrosoftMonitoringAgent" `
                -TypeHandlerVersion "1.0" `
                -Settings @{"workspaceId" = "xxx" } `
                -ProtectedSettings @{"workspaceKey" = "xxx"} `
                -Location $VM.location
#Add VM to string list for LA function
$VMList += "`"$($CurrentVM.vmid)`", "

}
catch
{
write-error -Message $_.Exception.message
}
<#        if ($VMAgentConfig.StatusCode -ne 0)
        {
            Write-Error -Message "Failed to add workspace to  $($VM.Name)"
            exit
        }
        else
        {
            write-output "Successfully added the workspace to  $($VM.Name)"
        }#>

}

write-output "VMList to use is: " $($VMList)
#Get the saved queries in LA
$SavedGroups = Get-AzOperationalInsightsSavedSearch -ResourceGroupName $LogAnalyticsSolutionWorkspaceRG `
                -WorkspaceName $LogAnalyticsSolutionWorkspaceName  -AzureRmContext $LASubscriptionContext -ErrorAction Continue -ErrorVariable oErr
#Find the Update Management saved query from the entire list and put it into a variable
$UpdatesGroup = $SavedGroups.Value | Where-Object {$_.Id -match "MicrosoftDefaultComputerGroup" -and $_.Properties.Category -eq "Updates"}

write-output "group is " $UpdatesGroup | out-string -Width 1000
#set the query/function
#remove trailing comma from list of VMs
$VMList = $($VMList).TrimEnd(', ')
#we use Resource as UUID is not useful when troubleshooting the query and computer gets truncated
$NewQuery="Heartbeat | where VMUUID in ( $($VMList) ) | distinct Computer"
write-output "Newquery is" $($NewQuery)

#just left in for info but no longer use Powershell to update LA query, using ARM as this has etag parameter, which is needed to avoid 409 conflict.
#New-AzOperationalInsightsSavedSearch -ResourceGroupName $LogAnalyticsSolutionWorkspaceRG -WorkspaceName $LogAnalyticsSolutionWorkspaceName `
#                                     -SavedSearchID "Updates|MicrosoftDefaultComputerGroup" -Category "Updates" -FunctionAlias "Updates__MicrosoftDefaultComputerGroup" `
#                                     -Query $NewQuery -DisplayName "MicrosoftDefaultComputerGroup" -Force


#write arm template to a file then make a new resource deployment to update the LA function

#configure arm template

        $ArmTemplate = @'
{
    "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "location": {
            "type": "string",
            "defaultValue": ""
        },
        "id": {
            "type": "string",
            "defaultValue": ""
        },
        "resourceName": {
            "type": "string",
            "defaultValue": ""
        },
        "category": {
            "type": "string",
            "defaultValue": ""
        },
        "displayName": {
            "type": "string",
            "defaultValue": ""
        },
        "query": {
            "type": "string",
            "defaultValue": ""
        },
        "functionAlias": {
            "type": "string",
            "defaultValue": ""
        },
        "etag": {
            "type": "string",
            "defaultValue": ""
        },
        "apiVersion": {
            "defaultValue": "2017-04-26-preview",
            "type": "String"
        }
    },
    "resources": [
        {
            "apiVersion": "2017-04-26-preview",
            "type": "Microsoft.OperationalInsights/workspaces/savedSearches",
            "location": "[parameters('location')]",
            "name": "[parameters('resourceName')]",
            "id": "[parameters('id')]",
            "properties": {
                "displayname": "[parameters('displayName')]",
                "category": "[parameters('category')]",
                "query": "[parameters('query')]",
                "functionAlias": "[parameters('functionAlias')]",
                "etag": "[parameters('etag')]",
                "tags": [
                    {
                        "Name": "Group", "Value": "Computer"
                    }
                ]
            }
        }
    ]
}
'@
        #Endregion
        # Create temporary file to store ARM template in
        $TempFile = New-TemporaryFile -ErrorAction Continue -ErrorVariable oErr
        if ($oErr)
        {
            Write-Error -Message "Failed to create temporary file for solution ARM template" -ErrorAction Stop
        }
        Out-File -InputObject $ArmTemplate -FilePath $TempFile.FullName -ErrorAction Continue -ErrorVariable oErr
        if ($oErr)
        {
            Write-Error -Message "Failed to write ARM template for solution onboarding to temp file" -ErrorAction Stop
        }
        # Add all of the parameters
        $QueryDeploymentParams = @{}
        $QueryDeploymentParams.Add("location", $($LogAnalyticsSolutionWorkspace.Location))
        $QueryDeploymentParams.Add("id", $UpdatesGroup.Id)
        $QueryDeploymentParams.Add("resourceName", ($LogAnalyticsSolutionWorkspaceName+ "/Updates|MicrosoftDefaultComputerGroup").ToLower())
        $QueryDeploymentParams.Add("category", "Updates")
        $QueryDeploymentParams.Add("displayName", "MicrosoftDefaultComputerGroup")
        $QueryDeploymentParams.Add("query", $NewQuery)
        $QueryDeploymentParams.Add("functionAlias", $SolutionType + "__MicrosoftDefaultComputerGroup")
        $QueryDeploymentParams.Add("etag", "*")#$UpdatesGroup.ETag) etag is null for the query and referencing null property doesn't work so * instead
        #$QueryDeploymentParams.Add("apiVersion", $SolutionApiVersion)

        # Create deployment name
        $DeploymentName = "EnableMultipleAutomation" + (Get-Date).ToFileTimeUtc()

        $ObjectOutPut = New-AzResourceGroupDeployment -ResourceGroupName $LogAnalyticsSolutionWorkspaceRG -TemplateFile $TempFile.FullName `
            -Name $DeploymentName `
            -TemplateParameterObject $QueryDeploymentParams `
            -AzureRmContext $SubscriptionContext -ErrorAction Continue -ErrorVariable oErr
        if ($oErr)
        {
            Write-Error -Message "Failed to add VM: $VMName to solution: $SolutionType" -ErrorAction Stop
        }
        else
        {
            Write-Output -InputObject $ObjectOutPut
            Write-Output -InputObject "VM: $VMName successfully added to solution: $SolutionType"
        }

        # Remove temp file with arm template
        Remove-Item -Path $TempFile.FullName -Force

Like I say, the runbook seems to complete successfully, but I wonder could anyone advise what task I am not performing, please? Thanks, Neil.


回答1:


OK, so I think I have sorted it. The answer lies in the LA query.

My query did not use Computer as a search field as it truncates at 15 characters, I only used VMUUID. Even if the result set is identical, unless your query is correct then UM will fail.

The correct query should include this even though it is unused. You also need the tildes as clearly Update Management expects a very precise query syntax.

Here is an example of a working query:

Heartbeat | where Computer in~ ("") or VMUUID in~ ("xxxxx") | distinct Computer

Then you must restart MMA on the box and then wait ages for the console to update.



来源:https://stackoverflow.com/questions/65610363/onboard-azure-vms-to-update-management-using-runbook

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!