Use Azure Automation to Install and Configure the Log Analytics Extension

This post has been republished via RSS; it originally appeared at: New blog articles in Microsoft Tech Community.

 

Hello, Chris Wallen here and in this post, I'm going to show how you can use an Azure Automation runbook to deploy and configure the Log Analytics extension to a group of virtual machines running either Windows or Linux. 

 

Before we get started with the code portion, there are a couple of important things to note. 

 

1.     This script requires the virtual machines to be powered on and will skip any VMs that are deallocated. The output will show any skipped VMs. 

2.    The virtual machine guest agent must be in a good state. Check this by looking at the Agent Status under the Properties blade of the Virtual Machine

3.    This script uses the AzureRM modules, but can easily be converted to use the new Az modules

 

Now, on to the fun part: The code. 

 

Below are the required parameters for the runbook: 

·           azureSubscriptionId - The unique identifier of the subscription you want to use 

·           azureEnvironment - The Azure cloud environment to use. e.g, AzureCloudAzureUSGovernment 

·           LogAnalyticsWorkspaceName - The name of the Log Analytics workspace to connect the virtual machines 

·           LAResourceGroup - The resource group that contains the Log Analytics workspace 

 

The following parameters are not required. If specified, they limit the scope of the runbook to only the resource groups or virtual machines that you want to configure. 

 

·           ResourceGroupNames - If this parameter is specified the Log Analytics extension will be deployed to all virtual machines in the resource group. The list of resource groups should be specified in JSON format - ['rg1','rg2'] 

·           VMNames - If this is specified, the Log Analytics extension will only be deployed to the provided virtual machines. This variable should be provided in JSON format - ['vm1','vm2'] 

 

Note: You can visit my Github repo to download the full script. 

 

First, we’ll define our parameters 

 

Param

(

    [parameter(mandatory)]

    [string]

    $azureSubscriptionID,

 

    [parameter(mandatory)]

    [string]

    $azureEnvironment,

 

    [parameter(mandatory)]

    [string]

    $WorkspaceName,

 

    [parameter(mandatory)]

    [string]

    $LAResourceGroup,

 

    [string[]]

    $ResourceGroupNames,

 

    [string[]]

    $VMNames

)

 

 

In the next section, we need to configure our runbook to use our AzureRunAsAccount. This will use the credentials that are automatically created when you first create an automation account. 

 

 

$connectionName = "AzureRunAsConnection"

 

# Get the connection "AzureRunAsConnection "

$servicePrincipalConnection = Get-AutomationConnection `

    -Name $connectionName -ErrorAction Stop

 

Write-Output "Logging in to Azure..."

 

Add-AzureRmAccount `

    -ServicePrincipal `

    -TenantId $servicePrincipalConnection.TenantId `

    -ApplicationId $servicePrincipalConnection.ApplicationId `

    -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint `

    -EnvironmentName $azureEnvironment `

    -ErrorAction Stop

 

$azContext = Select-AzureRmSubscription `

    -subscriptionId $azureSubscriptionID -ErrorAction Stop

 

Next, we'll build the list of Virtual Machine objects using the provided resource groups or virtual machine names to which we want to deploy the extension. This will look for unique virtual machine names. If objects with identical names are found, they will be skipped to ensure we're only installing the extension on the desired machines. 

 

 

$vms = @()

 

if (-not $ResourceGroupNames -and -not $VMNames)

{

    Write-Output "No resource groups or VMs specified. Collecting all VMs"

    $vms = Get-AzureRmVM

}

elseif ($ResourceGroupNames -and -not $VMNames)

{

    foreach ($rg in $ResourceGroupNames)

    {

        Write-Output "Collecting VM facts from resource group $rg"

        $vms += Get-AzureRmVM -ResourceGroupName $rg

    }

}

else

{

    foreach ($VMName in $VMNames)

    {

        $azureResource = Get-AzureRmResource -Name $VMName `

                -ResourceType 'Microsoft.Compute/virtualMachines'

 

        if ($azureResource.Count -lt 1)

        {

            Write-Error -Message "Failed to find $VMName"

        }

        elseif ($azureResource.Count -gt 1)

        {

            Write-Error -Message "Found multiple VMs with the name $VMName. Unable to configure extension"            

        }

 

        $vms += Get-AzureRmVM -Name $VMName -ResourceGroupName $azureResource.ResourceGroupName

    }

}

 

 

Now we need to add the code that deploys and configures the extension. Inside the foreach loop, you'll notice a call to Start-Job. This will create a new job for each VM in the array and allows for parallel processing, which significantly speeds up the runbook. 

 

 

$workspace = Get-AzureRmOperationalInsightsWorkspace -Name $WorkspaceName `

        -ResourceGroupName $LAResourceGroup -ErrorAction Stop

$key = (Get-AzureRmOperationalInsightsWorkspaceSharedKeys -ResourceGroupName $LAResourceGroup `

        -Name $WorkspaceName).PrimarySharedKey

 

$PublicSettings = @{"workspaceId" = $workspace.CustomerId }

$ProtectedSettings = @{"workspaceKey" = $key }

 

#Loop through each VM in the array and deploy the extension

foreach ($vm in $vms)

{    

    Start-Job -ArgumentList $azContext, $vm, $workspace, $key, $PublicSettings, $ProtectedSettings -ScriptBlock {

        

        Param 

        (

            $azContext,

            $vm,

            $workspace,

            $key,

            $PublicSettings,

            $ProtectedSettings

        )

 

        $vmStatus = (Get-AzureRmVM -ResourceGroupName $vm.ResourceGroupName `

                -Name $vm.Name -Status).Statuses.DisplayStatus[-1]

 

        Write-Output "Processing VM: $($vm.Name)"

 

        if ($vmStatus -ne 'VM running')

        {

            Write-Warning -Message "Skipping VM as it is not currently powered on"

        }

 

        #Check to see if Linux or Windows

        if ($vm.StorageProfile.OsDisk.OsType -eq 'Windows')

        {

            $extensions = Get-AzureRmVMExtension -ResourceGroupName $vm.ResourceGroupName `

                    -VMName $vm.Name -Name 'Microsoft.EnterpriseCloud.Monitoring' `

                    -ErrorAction SilentlyContinue            

            

            #Make sure the extension is not already installed before attempting to install it

            if (-not $extensions)

            {

                Write-Output "Adding MicrosoftMonitoringAgent extension to VM: $($vm.Name)"

 

                $result = Set-AzureRmVMExtension -ExtensionName "Microsoft.EnterpriseCloud.Monitoring" `

                    -ResourceGroupName $vm.ResourceGroupName `

                    -VMName $vm.Name `

                    -Publisher "Microsoft.EnterpriseCloud.Monitoring" `

                    -ExtensionType "MicrosoftMonitoringAgent" `

                    -TypeHandlerVersion 1.0 `

                    -Settings $PublicSettings `

                    -ProtectedSettings $ProtectedSettings `

                    -Location $vm.Location

            }

            else

            {

                Write-Output "Skipping VM - Extension already installed"

            }

        }

        elseif($vm.StorageProfile.OsDisk.OsType -eq 'Linux')

        {

            $extensions = Get-AzureRmVMExtension -ResourceGroupName $vm.ResourceGroupName `

                    -VMName $vm.Name -Name 'OmsAgentForLinux' -ErrorAction SilentlyContinue

 

            #Make sure the extension is not already installed before attempting to install it

            if (-not $extensions)

            {

                Write-Output "Adding OmsAgentForLinux extension to VM: $($vm.Name)"

                $result = Set-AzureRmVMExtension -ExtensionName "OmsAgentForLinux" `

                    -ResourceGroupName $vm.ResourceGroupName `

                    -VMName $vm.Name `

                    -Publisher "Microsoft.EnterpriseCloud.Monitoring" `

                    -ExtensionType "OmsAgentForLinux" `

                    -TypeHandlerVersion 1.0 `

                    -Settings $PublicSettings `

                    -ProtectedSettings $ProtectedSettings `

                    -Location $vm.Location

            }

            else

            {

                Write-Output "Skipping VM - Extension already installed"

            }

        }

    }  

}

 

 

Finally, we add a while loop at the end of the script to continue writing output until the final job has completed 

 

 

$runningJobs = Get-Job -State Running

While ($runningJobs.Count -gt 0)

{

    foreach ($job in $runningJobs)

    {

        Receive-Job $job.Id

    }

    $runningJobs = Get-Job -State Running

}

 

 

 

Now putting all of this together, the completed runbook looks like the following: 

 

<#

    .SYNOPSIS

        Installs the OMS Agent to Azure VMs with the Guest Agent

 

    .DESCRIPTION

        Traverses an entire subscription / resource group/ or list of VMs to

        install and configure the Log Analytics extension. If no ResourceGroupNames

        or VMNames are provided, all VMs will have the extension installed.

        Otherwise a superset of the 2 parameters is used to determine VM list.

 

    .PARAMETER azureSubscriptionID

        ID of Azure subscription to use

 

    .PARAMETER azureEnvironment

        The Azure Cloud environment to use, i.e. AzureCloud, AzureUSGovernment

 

    .PARAMETER LogAnalyticsWorkspaceName

        Log Analytic workspace name

 

    .PARAMETER LAResourceGroup

        Resource Group of Log Analytics workspace

 

    .PARAMETER ResourceGroupNames

        List of Resource Groups. VMs within these RGs will have the extension installed

        Should be specified in format ['rg1','rg2']

 

    .PARAMETER VMNames

        List of VMs to install OMS extension to

        Specified in the format ['vmname1','vmname2']

 

    .NOTES

        Version:        1.0

        Author:         Chris Wallen

        Creation Date:  09/10/2019

#>

Param

(

    [parameter(mandatory)]

    [string]

    $azureSubscriptionID,

 

    [parameter(mandatory)]

    [string]

    $azureEnvironment,

 

    [parameter(mandatory)]

    [string]

    $WorkspaceName,

 

    [parameter(mandatory)]

    [string]

    $LAResourceGroup,

 

    [string[]]

    $ResourceGroupNames,

 

    [string[]]

    $VMNames

)

 

$connectionName = "AzureRunAsConnection"

 

# Get the connection "AzureRunAsConnection "

$servicePrincipalConnection = Get-AutomationConnection `

    -Name $connectionName -ErrorAction Stop

 

Write-Output "Logging in to Azure..."

 

Add-AzureRmAccount `

    -ServicePrincipal `

    -TenantId $servicePrincipalConnection.TenantId `

    -ApplicationId $servicePrincipalConnection.ApplicationId `

    -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint `

    -EnvironmentName $azureEnvironment `

    -ErrorAction Stop

 

$azContext = Select-AzureRmSubscription `

    -subscriptionId $azureSubscriptionID -ErrorAction Stop

 

$vms = @()

 

if (-not $ResourceGroupNames -and -not $VMNames)

{

    Write-Output "No resource groups or VMs specified. Collecting all VMs"

    $vms = Get-AzureRmVM

}

elseif ($ResourceGroupNames -and -not $VMNames)

{

    foreach ($rg in $ResourceGroupNames)

    {

        Write-Output "Collecting VM facts from resource group $rg"

        $vms += Get-AzureRmVM -ResourceGroupName $rg

    }

}

else

{

    foreach ($VMName in $VMNames)

    {

        $azureResource = Get-AzureRmResource -Name $VMName `

                -ResourceType 'Microsoft.Compute/virtualMachines'

 

        if ($azureResource.Count -lt 1)

        {

            Write-Error -Message "Failed to find $VMName"

        }

        elseif ($azureResource.Count -gt 1)

        {

            Write-Error -Message "Found multiple VMs with the name $VMName. Unable to configure extension"            

        }

 

        $vms += Get-AzureRmVM -Name $VMName -ResourceGroupName $azureResource.ResourceGroupName

    }

}

 

$workspace = Get-AzureRmOperationalInsightsWorkspace -Name $WorkspaceName `

        -ResourceGroupName $LAResourceGroup -ErrorAction Stop

$key = (Get-AzureRmOperationalInsightsWorkspaceSharedKeys -ResourceGroupName $LAResourceGroup `

        -Name $WorkspaceName).PrimarySharedKey

 

$PublicSettings = @{"workspaceId" = $workspace.CustomerId }

$ProtectedSettings = @{"workspaceKey" = $key }

 

#Loop through each VM in the array and deploy the extension

foreach ($vm in $vms)

{    

    Start-Job -ArgumentList $azContext, $vm, $workspace, $key, $PublicSettings, $ProtectedSettings -ScriptBlock {

        

        Param 

        (

            $azContext,

            $vm,

            $workspace,

            $key,

            $PublicSettings,

            $ProtectedSettings

        )

 

        $vmStatus = (Get-AzureRmVM -ResourceGroupName $vm.ResourceGroupName `

                -Name $vm.Name -Status).Statuses.DisplayStatus[-1]

 

        Write-Output "Processing VM: $($vm.Name)"

 

        if ($vmStatus -ne 'VM running')

        {

            Write-Warning -Message "Skipping VM as it is not currently powered on"

        }

 

        #Check to see if Linux or Windows

        if ($vm.StorageProfile.OsDisk.OsType -eq 'Windows')

        {

            $extensions = Get-AzureRmVMExtension -ResourceGroupName $vm.ResourceGroupName `

                    -VMName $vm.Name -Name 'Microsoft.EnterpriseCloud.Monitoring' `

                    -ErrorAction SilentlyContinue            

            

            #Make sure the extension is not already installed before attempting to install it

            if (-not $extensions)

            {

                Write-Output "Adding MicrosoftMonitoringAgent extension to VM: $($vm.Name)"

 

                $result = Set-AzureRmVMExtension -ExtensionName "Microsoft.EnterpriseCloud.Monitoring" `

                    -ResourceGroupName $vm.ResourceGroupName `

                    -VMName $vm.Name `

                    -Publisher "Microsoft.EnterpriseCloud.Monitoring" `

                    -ExtensionType "MicrosoftMonitoringAgent" `

                    -TypeHandlerVersion 1.0 `

                    -Settings $PublicSettings `

                    -ProtectedSettings $ProtectedSettings `

                    -Location $vm.Location

            }

            else

            {

                Write-Output "Skipping VM - Extension already installed"

            }

        }

        elseif($vm.StorageProfile.OsDisk.OsType -eq 'Linux')

        {

            $extensions = Get-AzureRmVMExtension -ResourceGroupName $vm.ResourceGroupName `

                    -VMName $vm.Name -Name 'OmsAgentForLinux' -ErrorAction SilentlyContinue

 

            #Make sure the extension is not already installed before attempting to install it

            if (-not $extensions)

            {

                Write-Output "Adding OmsAgentForLinux extension to VM: $($vm.Name)"

                $result = Set-AzureRmVMExtension -ExtensionName "OmsAgentForLinux" `

                    -ResourceGroupName $vm.ResourceGroupName `

                    -VMName $vm.Name `

                    -Publisher "Microsoft.EnterpriseCloud.Monitoring" `

                    -ExtensionType "OmsAgentForLinux" `

                    -TypeHandlerVersion 1.0 `

                    -Settings $PublicSettings `

                    -ProtectedSettings $ProtectedSettings `

                    -Location $vm.Location

            }

            else

            {

                Write-Output "Skipping VM - Extension already installed"

            }

        }

    }  

}

 

$runningJobs = Get-Job -State Running

While ($runningJobs.Count -gt 0)

{

    foreach ($job in $runningJobs)

    {

        Receive-Job $job.Id

    }

    $runningJobs = Get-Job -State Running

}

 

 

 

And that's it! Now that you have the runbook created, I recommend running a few tests to ensure you're seeing the expected behavior.  

 

Once you've tested and verified the runbook, the only things left to do are to publish it and set a recurring schedule. 

 

I hope you find this useful. Please comment if you find any issues!

 

 

 

 

 

 

 

REMEMBER: these articles are REPUBLISHED. Your best bet to get a reply is to follow the link at the top of the post to the ORIGINAL post! BUT you're more than welcome to start discussions here:

This site uses Akismet to reduce spam. Learn how your comment data is processed.