Blog Post

Core Infrastructure and Security Blog
6 MIN READ

Use Azure Automation to onboard VMs to Log Analytics

wallenc's avatar
wallenc
Icon for Microsoft rankMicrosoft
Nov 26, 2019

 

Hello, Chris Wallen here and in this post, I'm going to show how you can use an automation runbook to onboard Windows and Linux VMs to a Log Analytics workspace.

 

Before we get started with the code portion, there are a few 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. We'll do this by using a foreach loop to loop through each of the collected VMs.

 

 

$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)

{    

    $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 "MicrosoftMonitoringAgent" `

                -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"

        }

    }

}   

 

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)

{    

    $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 "MicrosoftMonitoringAgent" `

                -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"

        }

    }

}  

 

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!

 

 

 

 

 

 

 

Updated Jan 07, 2020
Version 4.0