Category Archives: Azure

Inviting an external user to a PowerApp programmatically

Another week, another use case for Managed Identities in Automation Accounts!

The scenario today concerns a PowerApp and connected resources that should be shared with external identities, automatically of course. For each user this requires a guest account in the host / resource tenant, and a license. The license can be applied in the home tenant of the guest, or in your tenant.

Key points:

  1. Runbook that invites a user and adds the resulting guest account to a security group
  2. Security group gives access to the PowerApp and underlying (SpO) resources, and uses Group Based Licensing to license the guest for PowerApps and Sharepoint Online
  3. Logic App that is triggered by the PowerApp (trigger on create item in a sharepoint list), and starts the runbook
  4. When the invited user (guest) redeems the invitation, they are directed to a Sharepoint page first so Sharepoint syncs their profile. Otherwise, the PowerApp will not have access to any lists in Sharepoint Online as Guests are not synced to SpO until they access SpO directly.

I may demo the PowerApp, Logic App and Sharepoint lists at some point, but the main thing I wanted to share today is the Azure Runbook that creates the Guest invitation and adds the Guest to a security group using the Managed Identity of the Automation account, instead of service accounts or other pre-2021 solutions:

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/invite-guestUser.ps1

License reports by a Managed Identity

Capitalizing on the huge advantages that managed identities in Azure offer, here’s another use case similar to the scheduled migration script that also uses it’s managed identity (and graph permissions) to autonomously run as an Azure Runbook without any credentials stored.

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/get-licenseReportByDomain.ps1

The script will log in to Graph, retrieve all unique license types and how they are assigned to users, and will then email an HTML report (table) the the specified recipient, order by email domain.

Scheduled migration / group add script

Usually, I want to roll out new features gradually to my users.

I used to do this by creating a security group, assigning new policies/software/patches etc to it, and then telling the IT staff to add users to it in groups and check with them if things went well.

But why? Why is that manual step needed?

Exactly, it is not!

So attached script can be scheduled in an Azure Automation Account (with Managed Identity enabled!) to add an X amount of users from Group A to Group B. This script can be scheduled to e.g. run daily until all users have been moved. Multiple scheduled would also work in case you’re deploying multiple features.

Handling exceptions

When certain users need to be excluded, simply create an exclusion security group and exclude that from your policies. Exclusions take precedence in most systems.

Grouping users

You can group deployment by supplying the groupByProperty, e.g. by Country

The code

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/add-batchToGroup.ps1

Required modules

  • Az.Accounts

Graph Permissions

You’ll have to give the Managed Identity of your automation account sufficient graph permissions. This cannot be done through the GUI, so I’ve added a snippet you can use here:

https://gitlab.com/Lieben/assortedFunctions/-/blob/master/add-roleToManagedIdentity.ps1

Additionals

You’ll probably want to communicate to your users in advance, it’d be fairly easy to generate a report in advance or to add email notifications to the script but that will require additional graph permissions.

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

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;"
    }
]