Skip to content

Instantly share code, notes, and snippets.

@MarcelMeurer
Created September 19, 2022 16:52
Show Gist options
  • Save MarcelMeurer/8347eb527a6fdb4839ee428bf0e6eee0 to your computer and use it in GitHub Desktop.
Save MarcelMeurer/8347eb527a6fdb4839ee428bf0e6eee0 to your computer and use it in GitHub Desktop.
Azure Virtual Desktop - Behind the senes: Log Session Host information each minute to log analytics
{
"bindings": [
{
"name": "Timer",
"schedule": "*/60 * * * * *",
"direction": "in",
"type": "timerTrigger"
}
]
}
# Azure Functions profile.ps1
# Authenticate with Azure PowerShell using MSI.
# Remove this if you are not planning on using MSI or Azure PowerShell.
if ($env:MSI_SECRET) {
Disable-AzContextAutosave -Scope Process | Out-Null
Connect-AzAccount -Identity
}
# This file enables modules to be automatically managed by the Functions service.
# See https://aka.ms/functionsmanageddependency for additional information.
#
@{
# For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'.
# To use the Az module in your function app, please uncomment the line below.
# 'Az' = '8.*'
'Az.Accounts' = '2.10.0'
'Az.DesktopVirtualization' = '3.1.1'
}
# Use the script with an Azure function. The Azure function must have a managed identity (System).
# Give the managed identity read access to the resource group containing the host pools (or on the pools themselves).
# Additionally, the managed identity needs read access to key vault secrets containing the WorkspaceId and WorkspaceKey.
# Create both secrets with the information about the log analytics workspace. Reference both secrets in the appsettings of the function app:
# WorkspaceId=@Microsoft.KeyVault(SecretUri=https://xxxx.vault.azure.net/secrets/WorkspaceId/) and WorkspaceKey=@Microsoft.KeyVault(SecretUri=https://xxxx.vault.azure.net/secrets/WorkspaceKey/)
# Input bindings are passed in via param block.
param($Timer)
# Get the current universal time in the default string format.
$currentUTCtime = (Get-Date).ToUniversalTime()
# The 'IsPastDue' property is 'true' when the current function invocation is later than scheduled.
if ($Timer.IsPastDue) {
Write-Host "PowerShell timer is running late!"
}
# Write an information log with the current time.
Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime"
# Variables
$WorkspaceId=$env:WorkspaceId
$WorkspaceKey=$env:WorkspaceKey
$LogTypeName="AvdBehind_v2"
$TimeStampField="TimeStamp"
$ResourceGroup="WVD.Design2" # works on all pools in the resoruce group
# Functions
# Source: https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api
Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
$xHeaders = "x-ms-date:" + $date
$stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource
$bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
$keyBytes = [Convert]::FromBase64String($sharedKey)
$sha256 = New-Object System.Security.Cryptography.HMACSHA256
$sha256.Key = $keyBytes
$calculatedHash = $sha256.ComputeHash($bytesToHash)
$encodedHash = [Convert]::ToBase64String($calculatedHash)
$authorization = 'SharedKey {0}:{1}' -f $customerId,$encodedHash
return $authorization
}
# Source: https://learn.microsoft.com/en-us/azure/azure-monitor/logs/data-collector-api
Function Post-OMSData($customerId, $sharedKey, $body, $logType)
{
$method = "POST"
$contentType = "application/json"
$resource = "/api/logs"
$rfc1123date = [DateTime]::UtcNow.ToString("r")
$contentLength = $body.Length
$signature = Build-Signature -customerId $customerId -sharedKey $sharedKey -date $rfc1123date -contentLength $contentLength -method $method -contentType $contentType -resource $resource
$uri = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"
$headers = @{
"Authorization" = $signature;
"Log-Type" = $logType;
"x-ms-date" = $rfc1123date;
"time-generated-field" = $TimeStampField;
}
$response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
return $response.StatusCode
}
# Main
$pools=Get-AzWvdHostPool -ResourceGroupName $ResourceGroup
$now=(Get-Date).ToString("o")
Write-Host "Starting iteration at $now"
Write-Host "Get $($pools.Count) host pools"
$pools | ForEach {
$hostPoolName=$_.Name
Write-Host "`nWorking on pool $hostPoolName"
$rg=$_.Id.Split("/")[4]
$sessionHosts=Get-AzWvdSessionHost -HostPoolName $_.Name -ResourceGroupName $rg
$sessionHosts | Add-Member -MemberType NoteProperty -Name "TimeStamp" -Value $now
Write-Host "Number of session hosts: $($sessionHosts.Count)"
# Send to log analytics
if ($sessionHosts.Count -gt 0) {
$response=Post-OMSData -customerId $WorkspaceId -sharedKey $WorkspaceKey -body ([System.Text.Encoding]::UTF8.GetBytes(($sessionHosts | ConvertTo-Csv|ConvertFrom-Csv|ConvertTo-Json -Depth 5))) -logType $LogTypeName
Write-Host "Data uploaded to $WorkspaceId. Response code: ",$response
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment