Category Archives: Azure Virtual Desktop

Correct SessionDesktop friendlyname using AVD Rest API

When you deploy an Azure Virtual Desktop application group with the default desktop through ARM, the FriendlyName attribute is not respected, and remains at the default value of SessionDesktop.

This is easy to correct manually in the portal, but as I don’t want my admins having modify rights there, I introduced an extra pipeline step (YAML/Azure DevOps) to uses the Az module’s REST command to correctly set the FriendlyName of the SessionDesktop:

    - task: AzureCLI@2
      displayName: Correct app name
      inputs:
        azureSubscription: ${{ parameters.serviceConnection }}
        scriptType: ps
        scriptLocation: inlineScript
        inlineScript: |
          az rest --method PATCH --uri 'https://management.azure.com/subscriptions/${{ parameters.subscriptionId }}/resourceGroups/${{ parameters.resourceGroupName }}/providers/Microsoft.DesktopVirtualization/applicationGroups/ag-myappgroupname-01/desktops/SessionDesktop?api-version=2021-01-14-preview' --body '{""properties"":{""description"": ""Descriptive Tekst"",""friendlyName"": ""DevOps desktop""}}'

The API used is documented here: https://docs.microsoft.com/en-us/rest/api/desktopvirtualization/desktops/update

Lightweight LAPS solution for INtune (MEM)

Managing local admin accounts using Intune has a lot of quirks, my tele-colleague Rudy Ooms has already written extensively about this. He also wrote a PowerShell solution to rotate a specific local admin’s password and had the genius idea of using Proactive Remediations (a MEM feature) to display passwords to admins, integrated / free in the Intune Console.

However, I felt I needed a more lightweight solution that;

  • does not require/modify registry keys
  • does not store the password locally
  • does not need separate detection and remediation scripts
  • automatically provisions a local admin account
  • can remove any other local admin accounts if desired
  • is language/locale-agnostic (e.g. ‘Administrators’ vs ‘Administradores’….)

Thus LeanLAPS was born!

To install/use:

1. head into the Proactive Remediations section of MDE and click Create script package:

2. Fill out some details:

3. Download and doublecheck the config of LeanLAPS.ps1 (e.g. configure if other local admins should be removed, what the local admin name should be and the password length). Make sure to use NotePad++ / that the file stays UTF-8 Encoded without a BOM.

4. Set both the detection and remediation script to LeanLAPS.ps1 and run it in 64 bit:

5. Assign to a device group (user groups won’t work) and deploy. By default it will run every day, but you can also let it run more or less frequently, which determines how often the password is reset (hourly in below example):

6. Deploy, and then click on the script package:

7. Go to Device status and add both output columns:

Congratulations, you can now see the current local admin passwords for all managed Windows 10 devices!

Note: if you wish to trigger a quick remediation, delete the correct keys under Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\IntuneManagementExtension\SideCarPolicies\Scripts\Execution and Reports in the client’s registry, then restart the IntuneManagementExtension service and the remediation will re-run within 5 minutes.

RBAC

If you provide e.g. your helpdesk with the correct Intune roles, they will be able to see local admin passwords as reported by above solution:

Previewing image.png

WVD Hostpool ExpirationTime

Spent some time troubleshooting an issue deploying new VM’s to our Windows Virtual Desktop hostpool today, and since the DSC extension that adds the WVD host to the hostpool completed successfully but the VM’s didn’t join the hostpool, here’s the error for those googling it in the future:

No match for RegistrationKey found in the meta mof file

The reason the RegistrationKey never made it to the extension ended up being an expirationTime set “only” 6 hours in the future. Apparently, one of the steps isn’t actually executed in our target zone (West Europe). Setting the host pool token expiration time to 20 hours resolved above error.

The full DSC extension log in Azure:

[
    {
        "code": "ComponentStatus/DscConfigurationLog/succeeded",
        "level": "Info",
        "displayStatus": "Provisioning succeeded",
        "message": "[2021-06-03 10:37:48Z] [VERBOSE] [REDACTED]: LCM:  [ Start  Set      ]  [[Script]ExecuteRdAgentInstallClient]\r\n[2021-06-03 10:37:48Z] [VERBOSE] [REDACTED]:                            [[Script]ExecuteRdAgentInstallClient] Performing the operation \"Set-TargetResource\" on target \"Executing the SetScript with the user supplied credential\".\r\n[2021-06-03 10:38:05Z] [VERBOSE] [REDACTED]: LCM:  [ End    Set      ]  [[Script]ExecuteRdAgentInstallClient]  in 16.5940 seconds.\r\n[2021-06-03 10:38:05Z] [VERBOSE] [REDACTED]: LCM:  [ End    Resource ]  [[Script]ExecuteRdAgentInstallClient]\r\n[2021-06-03 10:38:05Z] [VERBOSE] [REDACTED]: LCM:  [ End    Set      ]\r\n[2021-06-03 10:38:05Z] [VERBOSE] [REDACTED]: LCM:  [ End    Set      ]    in  17.3440 seconds.\r\n[2021-06-03 10:38:05Z] [VERBOSE] Operation 'Invoke CimMethod' complete.\r\n[2021-06-03 10:38:05Z] [VERBOSE] Time taken for configuration job to complete is 17.478 seconds\r\n[2021-06-03 10:38:08Z] [VERBOSE] Performing the operation \"Start-DscConfiguration: SendMetaConfigurationApply\" on target \"MSFT_DSCLocalConfigurationManager\".\r\n[2021-06-03 10:38:08Z] [VERBOSE] Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendMetaConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguration'.\r\n[2021-06-03 10:38:08Z] [VERBOSE] An LCM method call arrived from computer REDACTED with user sid S-1-5-18.\r\n[2021-06-03 10:38:08Z] [VERBOSE] [REDACTED]: LCM:  [ Start  Set      ]\r\n[2021-06-03 10:38:08Z] [VERBOSE] [REDACTED]: LCM:  [ Start  Resource ]  [MSFT_DSCMetaConfiguration]\r\n[2021-06-03 10:38:08Z] [VERBOSE] [REDACTED]: LCM:  [ Start  Set      ]  [MSFT_DSCMetaConfiguration]\r\n[2021-06-03 10:38:08Z] [VERBOSE] [REDACTED]: LCM:  [ End    Set      ]  [MSFT_DSCMetaConfiguration]  in 0.0160 seconds.\r\n[2021-06-03 10:38:08Z] [VERBOSE] [REDACTED]: LCM:  [ End    Resource ]  [MSFT_DSCMetaConfiguration]\r\n[2021-06-03 10:38:08Z] [VERBOSE] [REDACTED]: LCM:  [ End    Set      ]\r\n[2021-06-03 10:38:08Z] [VERBOSE] [REDACTED]: LCM:  [ End    Set      ]    in  0.0790 seconds.\r\n[2021-06-03 10:38:08Z] [VERBOSE] Operation 'Invoke CimMethod' complete.\r\n[2021-06-03 10:38:08Z] [VERBOSE] Set-DscLocalConfigurationManager finished in 0.194 seconds."
    },
    {
        "code": "ComponentStatus/DscExtensionLog/succeeded",
        "level": "Info",
        "displayStatus": "Provisioning succeeded",
        "message": "[2021-06-03 10:37:23Z] Creating Working directory: C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\bin\\..\\DSCWork\\Configuration.0\r\n[2021-06-03 10:37:23Z] Downloading configuration package\r\n[2021-06-03 10:37:24Z] Downloading https://raw.githubusercontent.com/Azure/RDS-Templates/master/ARM-wvd-templates/DSC/Configuration.zip to C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\bin\\..\\DSCWork\\Configuration.0\\Configuration.zip\r\n[2021-06-03 10:37:41Z] Extracting Configuration.zip\r\n[2021-06-03 10:37:41Z] Looking for the definition of the configuration function.\r\n[2021-06-03 10:37:41Z] Executing C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\bin\\..\\DSCWork\\Configuration.0\\Configuration.ps1\r\n[2021-06-03 10:37:41Z] Preparing configuration arguments and configuration data.\r\n[2021-06-03 10:37:41Z] Creating MOF files.\r\n[2021-06-03 10:37:41Z] Executing the configuration function to generate the MOF files.\r\n[2021-06-03 10:37:42Z] Verifying metaconfiguration for reboot information...\r\n[2021-06-03 10:37:42Z] Backing up C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\DSCWork\\Configuration.0\\AddSessionHost\\localhost.meta.mof\r\n[2021-06-03 10:37:42Z] No match for RegistrationKey found in the meta mof file\r\n[2021-06-03 10:37:42Z] WMF 5 or newer, Injecting RebootNodeIfNeeded = False and ActionAfterReboot = \"StopConfiguration\"\r\n[2021-06-03 10:37:42Z] Executing Set-DscLocalConfigurationManager...\r\n[2021-06-03 10:37:44Z] Settings handler status to 'transitioning' (C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\Status\\0.status)\r\n[2021-06-03 10:37:47Z] Settings handler status to 'transitioning' (C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\Status\\0.status)\r\n[2021-06-03 10:37:47Z] Get-DscLocalConfigurationManager: \r\n\r\nActionAfterReboot              : StopConfiguration\r\nAgentId                        : ADF6E52A-C457-11EB-9BDA-000D3A2633E7\r\nAllowModuleOverWrite           : False\r\nCertificateID                  : \r\nConfigurationDownloadManagers  : {}\r\nConfigurationID                : \r\nConfigurationMode              : ApplyOnly\r\nConfigurationModeFrequencyMins : 15\r\nCredential                     : \r\nDebugMode                      : {NONE}\r\nDownloadManagerCustomData      : \r\nDownloadManagerName            : \r\nLCMCompatibleVersions          : {1.0, 2.0}\r\nLCMState                       : Idle\r\nLCMStateDetail                 : \r\nLCMVersion                     : 2.0\r\nStatusRetentionTimeInDays      : 10\r\nSignatureValidationPolicy      : NONE\r\nSignatureValidations           : {}\r\nMaximumDownloadSizeMB          : 500\r\nPartialConfigurations          : \r\nRebootNodeIfNeeded             : False\r\nRefreshFrequencyMins           : 30\r\nRefreshMode                    : PUSH\r\nReportManagers                 : {}\r\nResourceModuleManagers         : {}\r\nPSComputerName                 : \r\n\r\n\r\n\r\n\r\n[2021-06-03 10:37:47Z] Executing Start-DscConfiguration...\r\n[2021-06-03 10:37:47Z] Settings handler status to 'transitioning' (C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\Status\\0.status)\r\n[2021-06-03 10:38:07Z] Settings handler status to 'transitioning' (C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\Status\\0.status)\r\n[2021-06-03 10:38:07Z] Updating execution status (HKLM:\\SOFTWARE\\Microsoft\\Azure\\DSC\\2.83.1.0\\Status)\r\n[2021-06-03 10:38:07Z] LCM state is Idle\r\n[2021-06-03 10:38:07Z] DSC configuration completed.\r\n[2021-06-03 10:38:07Z] Resetting metaconfiguration...\r\n[2021-06-03 10:38:07Z] Restoring C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\DSCWork\\Configuration.0\\AddSessionHost\\localhost.meta.mof.bk...\r\n[2021-06-03 10:38:07Z] Executing Set-DscLocalConfigurationManager...\r\n[2021-06-03 10:38:07Z] Settings handler status to 'transitioning' (C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\Status\\0.status)\r\n[2021-06-03 10:38:10Z] Settings handler status to 'transitioning' (C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\Status\\0.status)\r\n[2021-06-03 10:38:10Z] Get-DscLocalConfigurationManager: \r\n\r\nActionAfterReboot              : ContinueConfiguration\r\nAgentId                        : ADF6E52A-C457-11EB-9BDA-000D3A2633E7\r\nAllowModuleOverWrite           : False\r\nCertificateID                  : \r\nConfigurationDownloadManagers  : {}\r\nConfigurationID                : \r\nConfigurationMode              : ApplyOnly\r\nConfigurationModeFrequencyMins : 15\r\nCredential                     : \r\nDebugMode                      : {NONE}\r\nDownloadManagerCustomData      : \r\nDownloadManagerName            : \r\nLCMCompatibleVersions          : {1.0, 2.0}\r\nLCMState                       : Idle\r\nLCMStateDetail                 : \r\nLCMVersion                     : 2.0\r\nStatusRetentionTimeInDays      : 10\r\nSignatureValidationPolicy      : NONE\r\nSignatureValidations           : {}\r\nMaximumDownloadSizeMB          : 500\r\nPartialConfigurations          : \r\nRebootNodeIfNeeded             : True\r\nRefreshFrequencyMins           : 30\r\nRefreshMode                    : PUSH\r\nReportManagers                 : {}\r\nResourceModuleManagers         : {}\r\nPSComputerName                 : \r\n\r\n\r\n\r\n\r\n[2021-06-03 10:38:10Z] Settings handler status to 'success' (C:\\Packages\\Plugins\\Microsoft.Powershell.DSC\\2.83.1.0\\Status\\0.status)"
    },
    {
        "code": "ComponentStatus/Metadata/succeeded",
        "level": "Info",
        "displayStatus": "Provisioning succeeded",
        "message": "VMUUId=6DFC142E-0A63-41D1-BE3E-31F5D0D9D8A3;AgentId=BDF6E52A-C427-12EB-9BDA-001D3A2633E7;"
    }
]

Add remoteapp to WVD hostpool using an ARM template

As I couldn’t find anyone who had written about adding a RemoteApp to a Windows Virtual Desktop hostpool (existing) in an existing Workspace, I figured I’d share my template.

  • It automatically creates an application group if it doesn’t exist yet
  • It takes into account existing applications if the application group already exists
  • The group has to be a remoteapp group, not a desktop group
  • This template is idempotent
  • This template can authorize any existing principal you specify
  • It creates 1 or more remoteapps in the group specified
  • It automatically manages the link to an existing hostpool/workspace

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/ARM%20templates/add-remoteAppToWVDHostPool.json

Incremental WVD deployments or redeploying persistent WVD hosts

When you add hosts to a WVD hostpool, and want to do so programmatically, this can be done by seperately deploying new hosts.

Redeploying an existing host manually is easy if it isn’t persistent, but if the disk should be retained, you’ll want to disconnect and reconnect it. This would require a lot of scripting (or third party tools).

As I wanted to keep our WVD solution contained to a single ARM template, some creativity was needed. For example, when deploying a new host that has a newer version of a gallery image, ARM would normally complain that the existing hosts don’t have the correct image version and it can’t change that for you since it is a read-only property (imageReference). Another example would be to move the VM between availability zones.

Linked WVD ARM template solves all these issues, but has a couple of important parameters to be aware of if you’re going to use it;

  1. redeploy: set to true in a scenario where you want to upgrade existing hosts, e.g. from a different VM type, or want to redeploy to another availability Zone. It will use the provided list of disk names to create new VM’s based on these existing disks
  2. existingDiskNames: if redeploy = true, supply the list of disk names of the persist VM’s you just deleted.
  3. vmNumberOfInstances: the number of VM’s you want to have (including existing VM’s)
  4. existingNumberOfInstances: the number of VM’s already present in the hostpool when running this arm template.
    #3 and #4 should be the same if redeploy = true, the template does not support redeploying and expanding the hostpool in the same deployment, this should be done in order.

After redeploying hosts, make sure to reconnect the users as they were assigned before you deleted them.

Download the ARM template here from GIT:

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/ARM%20templates/wvdDynamicHostpool.json