使用 Runbook 载入 Azure VM 以更新管理

Onboard Azure VMs to Update Management using runbook

我已经创建了一个 runbook 来载入 Azure VM 以进行更新管理。 这个想法取自 MS 提供的运行手册 (https://github.com/azureautomation/runbooks/blob/master/Utility/ARM/Enable-AutomationSolution.ps1)。 MS Runbook 使用旧的 AzureRM 模块,不能满足我的需求,而且开箱即用。

我的 runbook 本质上做同样的事情,查找带有标签的 VM,安装 Microsoft Monitoring Agent 并将其配置为向工作区报告。

然后更新工作区中的查询以包含 VM。

所有这一切都有效并成功完成,但更新管理门户中有一条消息说“1 台机器没有 'Update Management' 启用”和“这些机器正在向 Log Analytics 工作区报告 'workspacename',但他们没有启用 'Update Management'。"

我不确定我遗漏了哪些其他步骤,或者是否发生了某些变化,而且我看不到 MS Runbook 做了哪些我的没有做的事情。

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

我的操作手册:

#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

正如我所说,运行手册似乎成功完成,但我想知道有人能告诉我我没有执行什么任务吗? 谢谢, 尼尔.

好的,我想我已经整理好了。 答案就在洛杉矶查询中。

我的查询没有使用计算机作为搜索字段,因为它被截断为 15 个字符,我只使用了 VMUUID。即使结果集相同,除非您的查询正确,否则 UM 将失败。

正确的查询应该包括它,即使它没有被使用。您还需要波浪号,因为更新管理显然需要非常精确的查询语法。

这是一个有效查询的示例:

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

然后你必须在盒子上重新启动 MMA,然后等待主机更新。