diff --git a/docs/content/patterns/specialized/avd/Known-Issues.md b/docs/content/patterns/specialized/avd/Known-Issues.md index 5cc92a95e..356f1736f 100644 --- a/docs/content/patterns/specialized/avd/Known-Issues.md +++ b/docs/content/patterns/specialized/avd/Known-Issues.md @@ -4,9 +4,17 @@ geekdocCollapseSection: true weight: 100 --- -## Host Pool Capacity Remaining not reporting +## VM in Separate Resource Group option fails deployment (Issue #457) +Updated the custom UI definition to no longer hide the AVD Resource Resource Group selection when selecting option for VMs in seperate resource group(s). This will ensure the value is passed into the ARM template for processing verses the previously noted place holder value of 'Resource Group' with brackets. +(Fixed on 12/18/24 - v2.2.0) + +## Action Group in subsequent deployment fails (Issue #315) +Action Group creation now gets appended with a unique 13 character value based on the hash of the Subscription Name and current time. This will allow for subsequent deployments to ensure the action group name is unique across subscriptions. +(Fixed on 12/18/24 - v2.1.8) + +## Host Pool Capacity Remaining not reporting (Issue #288) After 3/21/24 there was a permission missing that did not allow the automation account to gather the information. -(Fixed on: 12/10/2024) +(Fixed on: 12/10/2024 - v2.1.7) Fix without Redeploying: Simply assigning the identity tied to the Automation Account the Virtual Desktop Reader role on the Resource Group that houses the Host Pools. diff --git a/docs/content/patterns/specialized/avd/Whats-New.md b/docs/content/patterns/specialized/avd/Whats-New.md index 5750dd2b5..567bd4f0e 100644 --- a/docs/content/patterns/specialized/avd/Whats-New.md +++ b/docs/content/patterns/specialized/avd/Whats-New.md @@ -8,6 +8,10 @@ For information on what's new please refer to the [Releases](https://github.com/ To update your current deployment with the content from the latest release, please refer to the [Update to new release](Update-to-new-Release.md) page. +## 2024-12-18 +Bug fixes [(See Known Issues Section)](Known-Issues.md) +Added option to select alternate subscription for Log Analytics and Storage as well as an initial prerequisites note on the first screen. + ## 2024-12-10 ### After 3/21/2024 Host Pool Capacity Alert Rule would not fire An issue was discovered where the Automation Account Identity was not being assigned the Virtual Desktop Reader role on the Resource Group hosting all the AVD Resources. Thus, the output of the script was null which yielded no alerts, regardless of what the host pool capacity currently was. There was a condition on the role assignment that was adjusted so that it will be added at deployment. diff --git a/docs/content/patterns/specialized/avd/_index.md b/docs/content/patterns/specialized/avd/_index.md index fc44de063..7bff6bfd4 100644 --- a/docs/content/patterns/specialized/avd/_index.md +++ b/docs/content/patterns/specialized/avd/_index.md @@ -8,7 +8,7 @@ geekdocCollapseSection: true This solution provides a baseline of alerts for AVD that are disabled by default and for ensuring administrators and staff get meaningful and timely alerts when there are problems related to an AVD deployment. The deployment has been tested in Azure Global and Azure US Government and will incorporate storage alerts for either or both Azure Files and/or Azure Netapp Files. This solution initially was part of the Azure Virtual Desktop Solution Accelerator as a brownfield and moved to this location. **Current Version:** -v2.1.7 (Dec 10, 2024) +v2.2.0 (Dec 18, 2024) ## Alerts Table diff --git a/patterns/avd/avdArm.json b/patterns/avd/avdArm.json index 35e2a7f61..45e2903e0 100644 --- a/patterns/avd/avdArm.json +++ b/patterns/avd/avdArm.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.31.92.45157", - "templateHash": "17094575244347820739" + "templateHash": "9307847103098609977" } }, "parameters": { @@ -148,13 +148,13 @@ "variables": { "copy": [ { - "name": "StorAcctRGsAll", + "name": "StorAcctsAll", "count": "[length(parameters('StorageAccountResourceIds'))]", - "input": "[split(parameters('StorageAccountResourceIds')[copyIndex('StorAcctRGsAll')], '/')[4]]" + "input": "[format('{0},{1}', split(parameters('StorageAccountResourceIds')[copyIndex('StorAcctsAll')], '/')[2], split(parameters('StorageAccountResourceIds')[copyIndex('StorAcctsAll')], '/')[4])]" } ], - "ActionGroupName": "[format('ag-avdmetrics-{0}-{1}', parameters('Environment'), parameters('Location'))]", - "AlertDescriptionHeader": "Automated AVD Alert Deployment Solution (v2.1.7)\n", + "ActionGroupName": "[format('ag-avdmetrics-{0}-{1}-{2}', parameters('Environment'), parameters('Location'), uniqueString(subscription().displayName, parameters('time')))]", + "AlertDescriptionHeader": "Automated AVD Alert Deployment Solution (v2.2.0)\n", "AutomationAccountName": "[format('aa-avdmetrics-{0}-{1}-{2}', parameters('Environment'), parameters('Location'), parameters('AlertNamePrefix'))]", "CloudEnvironment": "[environment().name]", "ResourceGroupCreate": "[if(equals(parameters('ResourceGroupStatus'), 'New'), true(), false())]", @@ -162,7 +162,7 @@ "RunbookNameGetHostPool": "AvdHostPoolLogData", "RunbookScriptGetStorage": "[format('Get-StorAcctInfo.ps1{0}', parameters('_ArtifactsLocationSasToken'))]", "RunbookScriptGetHostPool": "[format('Get-HostPoolInfo.ps1{0}', parameters('_ArtifactsLocationSasToken'))]", - "StorAcctRGs": "[union(variables('StorAcctRGsAll'), createArray())]", + "StorAcctsSubRGs": "[union(variables('StorAcctsAll'), createArray())]", "RoleAssignments": { "DesktopVirtualizationRead": { "Name": "Desktop-Virtualization-Reader", @@ -6879,12 +6879,13 @@ { "copy": { "name": "roleAssignment_Storage", - "count": "[length(variables('StorAcctRGs'))]" + "count": "[length(variables('StorAcctsSubRGs'))]" }, "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('c_StorAcctContrib_{0}', variables('StorAcctRGs')[copyIndex()])]", - "resourceGroup": "[variables('StorAcctRGs')[copyIndex()]]", + "name": "[format('c_StorAcctContrib_{0}', split(variables('StorAcctsSubRGs')[copyIndex()], ',')[1])]", + "subscriptionId": "[split(variables('StorAcctsSubRGs')[copyIndex()], ',')[0]]", + "resourceGroup": "[split(variables('StorAcctsSubRGs')[copyIndex()], ',')[1]]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -6904,7 +6905,7 @@ "value": "ServicePrincipal" }, "resourceGroupName": { - "value": "[variables('StorAcctRGs')[copyIndex()]]" + "value": "[split(variables('StorAcctsSubRGs')[copyIndex()], ',')[1]]" } }, "template": { diff --git a/patterns/avd/avdCustomUi.json b/patterns/avd/avdCustomUi.json index a16fbadf7..62373b4a6 100644 --- a/patterns/avd/avdCustomUi.json +++ b/patterns/avd/avdCustomUi.json @@ -19,6 +19,15 @@ } } }, + { + "name": "infoMessagePrereqs", + "type": "Microsoft.Common.InfoBox", + "visible": true, + "options": { + "text": "This page is for the Subscription and region to deploy the Alerts Solution in. The next page will prompt for alternate subscriptions for Log Analytics if it is NOT within this subscription. You will need to ensure you are deploying with an Account that has full Owner over the Resource Group with this resource to allow the proper role assignments for the Automation Account!", + "style": "Warning" + } + }, { "name": "HostPoolsApi", "type": "Microsoft.Solutions.ArmApiControl", @@ -42,14 +51,6 @@ "method": "GET", "path": "[concat(steps('basics').resourceScope.subscription.id, '/providers/Microsoft.Storage/storageAccounts?api-version=2022-09-01')]" } - }, - { - "name": "LogAnalyticsApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').resourceScope.subscription.id, '/providers/microsoft.operationalinsights/workspaces?api-version=2021-06-01')]" - } } ] }, @@ -250,16 +251,58 @@ "visible": true }, { - "name": "LogAnalyticsWorkspaceResource", - "type": "Microsoft.Solutions.ResourceSelector", - "label": "Insights Log Analytics Workspace", - "toolTip": "Log Analytics Workspace in which AVD Insigts and diagnostics data resides in.", - "resourceType": "Microsoft.OperationalInsights/workspaces", - "constraints": { - "required": true - }, - "infoMessages": [], - "visible": true + "name": "LAWSection", + "type": "Microsoft.Common.Section", + "label": "Log Analytics Selection", + "elements": [ + { + "type": "Microsoft.Common.SubscriptionSelector", + "name": "LAWsubscription", + "label": "Log Analytics Subscription", + "resourceProviders": [ + "Microsoft.OperationalInsights" + ] + }, + { + "name": "LogAnalyticsApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat('/subscriptions/', steps('AlertsConfig').LAWSection.LAWsubscription.subscriptionId, '/providers/microsoft.operationalinsights/workspaces?api-version=2021-06-01')]", + "transforms": { + "list": "value[*].{label:name, value:id, description:location}" + } + } + }, + { + "name": "LogAnalyticsWorkspaceResource", + "type": "Microsoft.Common.DropDown", + "label": "Insights Log Analytics Workspace NEW", + "toolTip": "Log Analytics Workspace in which AVD Insigts and diagnostics data resides in.", + "placeholder": "", + "defaultValue": "", + "multiselect": false, + "selectAll": false, + "filter": true, + "filterPlaceholder": "Filter items ...", + "multiLine": true, + "defaultDescription": "A value for selection", + "constraints": { + "allowedValues": "[steps('AlertsConfig').LAWSection.LogAnalyticsApi.transformed.list]", + "required": true + }, + "visible": true + }, + { + "name": "infoMessageLogAnalytics", + "type": "Microsoft.Common.InfoBox", + "visible": true, + "options": { + "text": "The Log Analytics Workspace in which AVD Insights and diagnostics data resides in.", + "style": "Info" + } + } + ] }, { "name": "optionVMMetrics", @@ -283,9 +326,9 @@ "defaultDescription": "A value for selection", "constraints": { "allowedValues": "[map(steps('basics').ResGroupsApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]", - "required": "[if(steps('AlertsConfig').optionVMMetrics, false, true)]" + "required": true }, - "visible": "[if(steps('AlertsConfig').optionVMMetrics, false, true)]" + "visible": true }, { "name": "HostPools", @@ -371,18 +414,40 @@ "type": "Microsoft.Common.Section", "label": "Azure Files Storage", "elements": [ + { + "type": "Microsoft.Common.SubscriptionSelector", + "name": "StorageSubscription", + "label": "Azure Files Subscription", + "resourceProviders": [ + "Microsoft.Storage" + ] + }, + { + "name": "StorageApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat('/subscriptions/', steps('AlertsConfig').AzFilesStorageSection.StorageSubscription.subscriptionId, '/providers/Microsoft.Storage/storageAccounts?api-version=2023-05-01')]", + "transforms": { + "list": "value[*].{label: name, value: id, description: join(' | ', [sku.name, location])}" + } + } + }, { "name": "StorageAccountResourceIds", "type": "Microsoft.Common.DropDown", "label": "AVD Related Storage Accounts", - "multiselect": true, - "selectAll": true, - "defaultValue": "[]", "toolTip": "The Storage Accounts that are used for FSLogix or MSIX App attach.", - "filterPlaceholder": "Filter Storage Accounts...", + "placeholder": "", + "defaultValue": "[]", + "multiselect": true, + "selectAll": false, + "filter": true, + "filterPlaceholder": "Filter Storage Accounts ...", + "multiLine": true, "defaultDescription": "A value for selection", "constraints": { - "allowedValues": "[map(steps('basics').StorAcctsApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]", + "allowedValues": "[steps('AlertsConfig').AzFilesStorageSection.StorageApi.transformed.list]", "required": true }, "visible": true @@ -468,7 +533,7 @@ "Environment": "[steps('AlertsConfig').Environment]", "HostPoolInfo": "[steps('AlertsConfig').hostPoolInfo]", "HostPools": "[steps('AlertsConfig').HostPools]", - "LogAnalyticsWorkspaceResourceId": "[steps('AlertsConfig').LogAnalyticsWorkspaceResource.id]", + "LogAnalyticsWorkspaceResourceId": "[steps('AlertsConfig').LAWSection.LogAnalyticsWorkspaceResource]", "ResourceGroupName": "[if(equals(steps('AlertsConfig').ResourceGroupStatus, 'New'), steps('AlertsConfig').resourceGroupNameNew, last(split(steps('AlertsConfig').resourceGroupNameExisting, '/')))]", "ResourceGroupStatus": "[steps('AlertsConfig').ResourceGroupStatus]", "StorageAccountResourceIds": "[steps('AlertsConfig').AzFilesStorageSection.StorageAccountResourceIds]", diff --git a/patterns/avd/scripts/Get-StorAcctInfo.ps1 b/patterns/avd/scripts/Get-StorAcctInfo.ps1 index e702a4c2c..94e967815 100644 --- a/patterns/avd/scripts/Get-StorAcctInfo.ps1 +++ b/patterns/avd/scripts/Get-StorAcctInfo.ps1 @@ -23,14 +23,20 @@ Connect-AzAccount -Identity -Environment $CloudEnvironment | Out-Null Import-Module -Name 'Az.Accounts' Import-Module -Name 'Az.Storage' -$SubName = (Get-azSubscription -SubscriptionId ($StorageAccountResourceIDs -split '/')[2]).Name +# $SubName = (Get-azSubscription -SubscriptionId ($StorageAccountResourceIDs -split '/')[2]).Name # Foreach storage account Foreach ($storageAcct in $storageAccountResourceIDs) { + $Subscription = Get-azSubscription -SubscriptionId ($storageAcct -split '/')[2] + $SubName = $Subscription.Name + + #Get current context and switch if storage in different Subscription + $Context = Get-AzContext + if ($Context.Subscription.Id -ne $Subscription.Id) {Set-AzContext -SubscriptionId $Subscription.Id | Out-Null} $resourceGroup = ($storageAcct -split '/')[4] $storageAcctName = ($storageAcct -split '/')[8] - #Write-Host "Working on Storage:" $storageAcctName "in" $resourceGroup + #Write-Output "Working on Storage:" $storageAcctName "in" $SubName " \ " $resourceGroup # $shares = Get-AzStorageShare -ResourceGroupName $resourceGroup -StorageAccountName $storageAcctName -Name 'profiles' -GetShareUsage $shares = Get-AzRmStorageShare -ResourceGroupName $ResourceGroup -StorageAccountName $storageAcctName @@ -38,7 +44,7 @@ Foreach ($storageAcct in $storageAccountResourceIDs) { # Foreach Share Foreach ($share in $shares) { $shareName = $share.Name - $share = Get-AzRmStorageShare -ResourceGroupName $ResourceGroup -StorageAccountName $storageAcctName -Name $shareName -GetShareUsage + $share = Get-AzRmStorageShare -ResourceGroupName $ResourceGroup -StorageAccountName $storageAcctName -Name $shareName -GetShareUsage -SubscriptionId $Subscription.Id #Write-Host "Share: " $shareName $shareQuota = $share.QuotaGiB #GB $shareUsageInGB = $share.ShareUsageBytes / 1073741824 # Bytes to GB diff --git a/patterns/avd/templates/deploy.bicep b/patterns/avd/templates/deploy.bicep index 87a087a0b..cba231a41 100644 --- a/patterns/avd/templates/deploy.bicep +++ b/patterns/avd/templates/deploy.bicep @@ -72,8 +72,8 @@ param ANFVolumeResourceIds array = [] param Tags object = {} -var ActionGroupName = 'ag-avdmetrics-${Environment}-${Location}' -var AlertDescriptionHeader = 'Automated AVD Alert Deployment Solution (v2.1.7)\n' // DESCRIPTION HEADER AND VERSION <----------------------------- +var ActionGroupName = 'ag-avdmetrics-${Environment}-${Location}-${uniqueString(subscription().displayName, time)}' +var AlertDescriptionHeader = 'Automated AVD Alert Deployment Solution (v2.2.0)\n' // DESCRIPTION HEADER AND VERSION <----------------------------- var AutomationAccountName = 'aa-avdmetrics-${Environment}-${Location}-${AlertNamePrefix}' var CloudEnvironment = environment().name var ResourceGroupCreate = ResourceGroupStatus == 'New' ? true : false @@ -81,8 +81,8 @@ var RunbookNameGetStorage = 'AvdStorageLogData' var RunbookNameGetHostPool = 'AvdHostPoolLogData' var RunbookScriptGetStorage = 'Get-StorAcctInfo.ps1${_ArtifactsLocationSasToken}' var RunbookScriptGetHostPool = 'Get-HostPoolInfo.ps1${_ArtifactsLocationSasToken}' -var StorAcctRGsAll = [for item in StorageAccountResourceIds: split(item, '/')[4]] -var StorAcctRGs = union(StorAcctRGsAll, []) +var StorAcctsAll = [for item in StorageAccountResourceIds: '${split(item, '/')[2]},${split(item, '/')[4]}'] //format of SubscriptionId,ResourceGroup +var StorAcctsSubRGs = union(StorAcctsAll, []) // var UsrManagedIdentityName = 'id-ds-avdAlerts-Deployment' var RoleAssignments = { @@ -2281,15 +2281,15 @@ module roleAssignment_LogAnalytics 'carml/1.3.0/Microsoft.Authorization/roleAssi // Assign role to Automation Account for Storage Account Contributor to allow Automation Account to gather Storage Statistics // (Needed for Automation Account) module roleAssignment_Storage 'carml/1.3.0/Microsoft.Authorization/roleAssignments/resourceGroup/deploy.bicep' = [ - for StorAcctRG in StorAcctRGs: { - scope: resourceGroup(StorAcctRG) - name: 'c_StorAcctContrib_${StorAcctRG}' + for StorAcctInfo in StorAcctsSubRGs: { + scope: resourceGroup(split(StorAcctInfo, ',')[0],split(StorAcctInfo, ',')[1]) + name: 'c_StorAcctContrib_${split(StorAcctInfo, ',')[1]}' params: { enableDefaultTelemetry: false principalId: automationAccount.outputs.systemAssignedPrincipalId roleDefinitionIdOrName: '/providers/Microsoft.Authorization/roleDefinitions/${RoleAssignments.StoreAcctContrib.GUID}' principalType: 'ServicePrincipal' - resourceGroupName: StorAcctRG + resourceGroupName: split(StorAcctInfo, ',')[1] } dependsOn: [ automationAccount