patterns/avd/templates/modules/run.ps1 (261 lines of code) (raw):

# 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!" } # USER DEFINED INFORMATION REQUIRED # Initial Subscription for getting Authentication Token # Tag used for LogAnalytics and HostPool Workspaces $subscriptionName = $env:SubscriptionName $subscriptionid = $env:subscriptionID $LAWName = $env:LogAnalyticsWorkSpaceName $resourceGroups = $env:HostPoolResourceGroupNames | convertfrom-json # Write an information log with the current time. Write-Host "PowerShell timer trigger function ran! TIME: $currentUTCtime" Function Create-AccessToken { param($resourceURI) If ($null -eq $env:MSI_ENDPOINT) { # Connect-AzAccount -Identity $token = Get-AzAccessToken Return $token.Token } Else { $tokenAuthURI = $env:MSI_ENDPOINT + "?resource=$resourceURI&api-version=2017-09-01" $headers = @{'Secret'="$env:MSI_SECRET"} try { $tokenResponse = Invoke-RestMethod -Method Get -header $headers -Uri $tokenAuthURI -ErrorAction:stop return $tokenResponse.access_token } catch { write-error "Unable to retrieve access token $error" exit 1 } } } Function Query-Azure { param($query,$accesstoken) $url = $ManageURL + $query $headers = @{'Authorization' = "Bearer $accessToken"} try { $response = Invoke-RestMethod -Method 'Get' -Uri $url -Headers $headers -ErrorAction:stop Return $response } catch { write-error "Unable to query Azure RestAPI: $error" } } Function DimensionSpliter { param($dimensiongroup) $dims = $dimensiongroup.split(";") [System.Collections.Generic.List[System.Object]]$dimobject = @() foreach ($dim in $dims) { $obj = [pscustomobject]@{ name = $dim.split(":")[0] value = $dim.split(":")[1] } $dimobject.Add($obj) } return $dimobject } Function Calculate-Metric { param($metric,$sessions,$hosts,$vms,$hostpool) $dimvalues = DimensionSpliter $metric.Dimensions | Foreach-Object {$_.Value} try { [int]$imetricresult = Invoke-Command -Scriptblock $metric.Query } catch { Write-Warning ("Metric query failed for: [{0}] - {1}" -f $metric.Namespace,$metric.Metric) Write-Warning ("{0}" -f $_.exception.message) return $False } $metricdata = [pscustomobject]@{ dimValues = $dimvalues min = $imetricresult max = $imetricresult sum = $imetricresult count = 1 } return $metricdata } Function POST-CustomMetric{ param($custommetricjson,$accesstoken,$targetresourceid,$region) $url = "https://$region.monitoring.azure.com$targetresourceid/metrics" # Write-Host "--> URL: $url" $headers = @{'Authorization' = "Bearer $accessToken" 'Content-Type' = "application/json"} try { $metricapiresponse = Invoke-RestMethod -Method 'Post' -Uri $url -Headers $headers -body $custommetricjson -ErrorAction:stop } catch { write-warning "Unable POST metric $error" return $false } return $true } Function Publish-Metric{ param($metric,$sessions,$hosts,$vms,$hostpool,$azmontoken,$targetresourceid,$region) $dimnames = DimensionSpliter $metric.Dimensions | Select-Object -ExpandProperty Name $series = [System.Collections.Generic.List[System.Object]]@() $metricresult = Calculate-Metric $metric $sessions $hosts $vms $hostpool If($metricresult -eq $False) { return $False } Else { $series.Add($metricresult) } $custommetric = [PSCustomObject]@{ time = (Get-Date -Format 'o') data = [PSCustomObject]@{ baseData = [PSCustomObject]@{ metric = $metric.metric namespace = $metric.namespace dimNames = $dimnames series = $series } } } $custommetricjson = $custommetric | convertto-json -depth 10 -compress write-output ("Publishing to Azure Monitor for Namespace:{0} Metric:{1}" -f $metric.namespace,$metric.metric) $Postresult = POST-CustomMetric $custommetricjson $azmontoken $targetresourceid $region return $Postresult } # URL(s) for creating access tokens $Environment = (Get-AzContext).Environment.Name $ManageURL = (Get-AzEnvironment | Where-Object {$_.Name -eq $Environment}).ResourceManagerUrl $WVDResourceURI = $ManageURL $AZMonResourceURI = "https://monitoring.azure.com/" # $WVDResourceURI = "https://management.core.windows.net/" # $AZMonResourceURI = "https://monitoring.azure.com/" Write-Output ("Creating Access Token for Azure Management ({0})" -f $WVDResourceURI) $token = Create-AccessToken -resourceURI $WVDResourceURI Write-Output ("Creating Access Token for Azure Monitor ({0})" -f $AZMonResourceURI) $azmontoken = Create-AccessToken -resourceURI $AZMonResourceURI Write-Output ("Collecting AVD Azure Subscriptions") $subscriptionQuery = "/subscriptions?api-version=2016-06-01" $subscription = (Query-Azure $subscriptionQuery $token).Value.Where{$_.displayName -eq $subscriptionName} # foreach ($subscription in $subscriptions) { Write-Output ("Working on '{0}' Subscription Resources" -f $subscription.displayName) $subscriptionid = $subscription.subscriptionid # $resourceGroupQuery = ("/subscriptions/{0}/resourcegroups/?api-version=2019-10-01" -f $subscriptionid) # $resourceGroups = (Query-Azure $resourceGroupQuery $token).Value.Where{$_.Tags.$tagName -eq $tagValue} # Write-Output ("Found {0} Host Pool Resource Groups in '{1}'" -f $resourceGroups.Count,$subscription.displayName) $logAnalyticsQuery = ("/subscriptions/{0}/providers/Microsoft.OperationalInsights/workspaces?api-version=2015-11-01-preview" -f $subscriptionid) $logAnalyticsWorkspace = (Query-Azure $logAnalyticsQuery $token).Value.Where{$_.name -eq $LAWName} <# $logAnalyticsWorkspace = (Query-Azure $logAnalyticsQuery $token).Value.Where{$_.Tags.$tagName -eq $tagValue} If ($logAnalyticsWorkspace.Count -gt 1) { Write-Warning ("Found {0} Log Analytics Workspaces in the {1} Subscription" -f $logAnalyticsWorkspace.Count,$subscription.displayName) Write-Warning ("Review the Azure Query and ensure only 1 Log Analytics Workspace is returned") Exit 1 }#> If (!$logAnalyticsWorkspace) { Write-Warning ("Unable to find a Log Analytics Workspace: $LAWName") Exit 1 } Else { # Write-Host ("-------> Log Analytics Workspace: {0}" -f $logAnalyticsWorkspace.Name) # Write-Host ("-------> Log Query: {0}" -f $logAnalyticsQuery) $workspaceId = $logAnalyticsWorkspace.Id $workspaceRegion = $logAnalyticsWorkspace.Location $workspaceName = $logAnalyticsWorkspace.Name } foreach($resourceGroup in $resourceGroups) { $resourceGroupQuery = ("/subscriptions/{0}/resourcegroups/{1}?api-version=2021-04-01" -f $subscriptionid,$resourceGroup) # Moved inside foreach $resourceGroup = Query-Azure $resourceGroupQuery $token # Moved inside foreach Write-Output ("Working on '{0}' Resources" -f $resourceGroup.Name) $resourceGroupName = $resourceGroup.Name $wvdapi = '2019-12-10-preview' $hostPoolsQuery = ("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostPools?api-version={2}" -f $subscriptionid,$resourceGroupName,$wvdapi) $hostPools = (Query-Azure $hostPoolsQuery $token).Value If ($hostPools.Count -gt 0) { Write-Output ("Found {0} Host Pool(s) in {1} Resource Group" -f $hostPools.Count,$resourceGroupName) foreach ($hostPool in $hostPools) { Write-Output ("Working on '{0}' Resources" -f $hostPool.Name) $poolName = $hostPool.Name # $workspaceRegion = $hostPool.Location Write-Output ("Querying Azure for AVD Resource data (Virtual Machines, Session Hosts, Sessions)") $sessionsquery = ("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostPools/{2}/userSessions?api-version={3}" -f $subscriptionid,$resourceGroupName,$poolName,$wvdapi) $hostsquery = ("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostPools/{2}/SessionHosts?api-version={3}" -f $subscriptionid,$resourceGroupName,$poolName,$wvdapi) $vmquery = ("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/virtualMachines?api-version=2020-06-01" -f $subscriptionid,$resourceGroupName) $sessions = (Query-Azure $sessionsquery $token).Value $hosts = (Query-Azure $hostsquery $token).Value $vms = (Query-Azure $vmquery $token).Value Write-Output ("Creating Azure Monitor Metric Definitions") [System.Collections.Generic.List[System.Object]]$metricDefinitions = @( [pscustomobject]@{ NameSpace = "AVD" Metric = "Active Sessions" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { ($sessions.properties | Where-Object {$_.sessionstate -eq "Active" -AND $_.userprincipalname -ne $null}).Count } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Disconnected Sessions" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { ($sessions.properties | Where-Object {$_.sessionState -eq "Disconnected" -AND $_.userPrincipalName -ne $null}).Count } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Total Sessions" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { ($sessions.properties | Where-Object {$_.userPrincipalName -ne $null}).Count } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Draining Hosts" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { ($hosts.properties | Where-Object {$_.allowNewSession -eq $false}).Count } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Unhealthy Hosts" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { ($hosts.properties | Where-Object {$_.allowNewSession -eq $true -AND $_.status -ne "Available"}).Count } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Healthy Hosts" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { ($hosts.properties | Where-Object {$_.allowNewSession -eq $true -AND $_.status -eq "Available"}).Count } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Max Sessions in Pool" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { $healthyHosts = ($hosts.properties | Where-Object {$_.allowNewSession -eq $true -AND $_.status -eq "Available"}).Count $healthyHosts * $hostpool.properties.maxSessionLimit } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Available Sessions in Pool" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { $healthyHosts = ($hosts.properties | Where-Object {$_.allowNewSession -eq $true -AND $_.status -eq "Available"}).Count $totalSessions = ($hosts.properties | Where-Object {$_.allowNewSession -eq $true -AND $_.status -eq "Available"} | Measure-Object -Property Sessions -Sum).Sum $maxSessions = $healthyHosts * $hostpool.properties.maxSessionLimit $maxSessions - $totalSessions } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Session Load (%)" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { $healthyHosts = ($hosts.properties | Where-Object {$_.allowNewSession -eq $true -AND $_.status -eq "Available"}).Count $totalSessions = ($hosts.properties | Where-Object {$_.allowNewSession -eq $true -AND $_.status -eq "Available"} | Measure-Object -Property Sessions -Sum).Sum $maxSessions = $healthyHosts * $hostpool.properties.maxSessionLimit If ($maxSessions -eq 0) { $maxSessions } Else { [math]::Ceiling($totalSessions / $maxSessions * 100) } } }, [pscustomobject]@{ NameSpace = "AVD" Metric = "Session Hosts in Maintenance" Dimensions = "Workspace:$workspacename;Pool:$poolname" Query = { ($vms.Tags | Where-Object {$_.'WVD-Maintenance' -eq $true}).Count } } ) Foreach ($metric in $metricDefinitions) { Write-Output ("Publishing Metric: [{0}] - {1} ({2})" -f $metric.Namespace,$metric.Metric,$workspaceRegion) $metricPosted = Publish-Metric $metric $sessions $hosts $vms $poolName $azmontoken $workspaceId $workspaceRegion If ($metricPosted -eq $false) { Write-Warning ("Failed to Publish Metric: [{0}] - {1}" -f $metric.Namespace,$metric.Metric) } } } } Else { Write-Warning ("No Host Pools found in {0} Resource Group" -f $resourceGroupName) } } # }