Category Archives: Azure

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

Enable WVD diagnostics for Azure Monitor using ARM Templates

Now that Azure Monitor has support for Windows Virtual Desktop, I wanted to enable this for all WVD workspaces, host pools and application groups.

Christiaan beautifully described how to do this through the Azure Portal, but of course, in an enterprise environment we don’t click around, we define everything as Code!

This example ARM template will enable the Diagnostics setting for the hostpool, workspace and application group vectors. To get data from VM’s (hosts), use your preferred method (plenty of examples exist already and depend on your setup).

After applying / including / redeploying this ARM template in your WVD environment, you’ll be able to see metrics coming in to the Azure Monitor Insights dashboard for WVD 🙂

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

Conditional nested ARM template to add WVD application group to Workspace

In Windows Virtual Desktop (ARM version), applications are part of application groups, which in turn get nested under Workspaces.

In an ‘Infra As Code’ world these should be deployed through ARM templates (or Az Cli/Ps scripts). I had a long wrestle with ARM today getting applications assigned to workspaces ONLY if they weren’t already assigned.

Azure throws a friendly 400 error if you try to add an app that already exists, and interestingly, the ARM ‘contains’ function fails to properly evaluate WVD Workspace members when in a nested template.

So, I had to resort to some trickery by converting it to a string. For anyone else wanting to incrementally attach application groups to workspaces, feel free to copy/clone my template 🙂

Git source

{
    "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "workspaceName": {
            "type": "string",
            "metadata": {
                "description": "The name of the Workspace."
            },
            "defaultValue": "NLD-WVD-WS01"
        },
        "workspaceResourceGroup": {
            "type": "string",
            "metadata": {
                "description": "The workspace resource group Name."
            },
            "defaultValue": "WE-WVD-RG"
        },
        "appGroupName": {
            "type": "string",
            "metadata": {
                "description": "The name of the Application Group to be linked."
            },
            "defaultValue": "testag2"
        }
    },
    "variables": {
        "appGroupResourceId": "[resourceId('Microsoft.DesktopVirtualization/applicationgroups/', parameters('appGroupName'))]"
    },
    "resources": [
        {
            "apiVersion": "2018-05-01",
            "name": "AddAppGroupToWorkspaceIncrementally",
            "type": "Microsoft.Resources/deployments",
            "resourceGroup": "[parameters('workspaceResourceGroup')]",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "resources": [
                        {
                            "name": "[parameters('workspaceName')]",
                            "apiVersion": "2019-12-10-preview",
                            "condition": "[not(greater(indexOf(string(reference(concat('/subscriptions/',subscription().subscriptionId,'/resourceGroups/',parameters('workspaceResourceGroup'),'/providers/Microsoft.DesktopVirtualization/workspaces/',parameters('workspaceName')),'2019-12-10-preview','Full').properties.applicationGroupReferences),variables('appGroupResourceId')),0))]",
                            "type": "Microsoft.DesktopVirtualization/workspaces",
                            "location": "eastus",
                            "properties": {
                                "applicationGroupReferences": "[union(reference(concat('/subscriptions/',subscription().subscriptionId,'/resourceGroups/',parameters('workspaceResourceGroup'),'/providers/Microsoft.DesktopVirtualization/workspaces/',parameters('workspaceName')),'2019-12-10-preview','Full').properties.applicationGroupReferences,array(variables('appGroupResourceId')))]"
                            }
                        }
                    ]
                }
            }
        }        
    ]
}

Get meta data such as the default domain of foreign Azure tenants

For an Azure Managed Application Marketplace scenario, I needed to get information about the user logon domains and the company name of any tenants that purchased our Managed Application through the Azure Marketplace.

Azure Marketplace only has a CRM and a CRUD connection, these don’t send more than some basic name/email/company name and subscription ID’s, and thus the CRM and CRUD operations are hard to match up against one another and to really know who you’re dealing with.

With the following PS snippets, you can get all information from all tenants that have a link with you through Azure Lighthouse / Azure Marketplace:

(Invoke-RestMethod -Method GET -Uri "https://management.azure.com/tenants?api-version=2020-01-01&`$includeAllTenantCategories=true" -UseBasicParsing -Headers @{"Authorization" = "Bearer $azureToken"}).Value

You’ll of course need a bearer token for management.azure.com and I recommend using this snippet to get tenant ID’s from the subscriptions that use your marketplace offers. The output is in JSON and looks like this for Microsoft’s home tenant:

{
    "id":  "/tenants/72f988bf-86f1-41af-91ab-2d7cd011db47",
    "tenantId":  "72f988bf-86f1-41af-91ab-2d7cd011db47",
    "countryCode":  "US",
    "displayName":  "Microsoft",
    "domains":  [
                    "drawbridge.com",
                    "expresslogic.com",
                    "euevents.microsoft.com",
                    "nonprofits.microsoft.com",
                    "benefits.microsoft.com",
                    "forzaesports.com",
                    "bons.ai",
                    "bonsaiai.com",
                    "bonsai.ai",
                    "mileiq.com",
                    "mobiledatalabs.com",
                    "azmosa.io",
                    "fslogix.com",
                    "Howdy.ai",
                    "Xoxco.com",
                    "Botkit.ai",
                    "glintinc.com",
                    "maquette.ms",
                    "tibazdev.microsoft.com",
                    "mail.appcenter.ms",
                    "Hexadite.com",
                    "lobe.ai",
                    "appcenter.ms",
                    "github.com",
                    "gearspop.com",
                    "messages.microsoft.com",
                    "flipgrid.com",
                    "semanticmachines.com",
                    "video2brain.com",
                    "averesystems.com",
                    "initiativegaming.com",
                    "mail1.averesystems.com",
                    "seaofthieves.com",
                    "Intentional.com",
                    "m12.vc",
                    "email.bing.com",
                    "playfab.com",
                    "itsm.microsoft.com",
                    "Windows.mail.microsoft.com",
                    "smtphost.microsoft.com",
                    "exmail.microsoft.com",
                    "altvr.com",
                    "altspacevr.com",
                    "corp.microsoft.com",
                    "cyclecomputing.com",
                    "cloudyn.com",
                    "nuget.org",
                    "microsoftsmarthq.com.au",
                    "lockbox.microsoft.com",
                    "acompli.com",
                    "domains.microsoft",
                    "service.linkedin.com",
                    "microsoft.com",
                    "eventscommunication.microsoft.com",
                    "deis.com",
                    "Lynda.com",
                    "Slideshare.com",
                    "Newsle.com",
                    "linkedin.com",
                    "myemailing.microsoft.com",
                    "maluuba.com",
                    "internal.linkedin.cn",
                    "linkedin.biz",
                    "microsoftcan.onmicrosoft.com",
                    "educatorcommunity.microsoft.com",
                    "simplygon.com",
                    "MicrosoftAPC.onmicrosoft.com",
                    "messages2.microsoft.com",
                    "shadmorris.com",
                    "MicrosoftEur.onmicrosoft.com",
                    "security.microsoft.com",
                    "robovm.com",
                    "solaircorporate.com",
                    "wandlabs.com",
                    "azureemail.microsoft.com",
                    "genee.me",
                    "microsoftstudios.com",
                    "MICROSOFTCSR.COM",
                    "bigpark.com",
                    "bing.com",
                    "corp.webtv.net",
                    "HaloWaypoint.com",
                    "musiwave.com",
                    "navic.tv",
                    "ntdev.corp.microsoft.com",
                    "redmond.corp.microsoft.com",
                    "europe.corp.microsoft.com",
                    "middleeast.corp.microsoft.com",
                    "exchange.corp.microsoft.com",
                    "southamerica.corp.microsoft.com",
                    "fareast.corp.microsoft.com",
                    "winse.corp.microsoft.com",
                    "mslpa.corp.microsoft.com",
                    "windows.microsoft.com",
                    "africa.corp.microsoft.com",
                    "ntdev.microsoft.com",
                    "wingroup.windeploy.ntdev.microsoft.com",
                    "southpacific.corp.microsoft.com",
                    "segroup.winse.corp.microsoft.com",
                    "northamerica.corp.microsoft.com",
                    "service.microsoft.com",
                    "exchange.microsoft.com",
                    "xbox.com",
                    "zune.net",
                    "msg.microsoft.com",
                    "titanium.microsoft.com",
                    "microsoft.mail.onmicrosoft.com",
                    "filtering.exchange.microsoft.com",
                    "skype.net",
                    "hybrid.microsoft.com",
                    "fbt.microsoft.com",
                    "ageofempiresonline.com",
                    "yammer-inc.com",
                    "service.fbt.microsoft.com",
                    "service.exchange.microsoft.com",
                    "mslicense.com",
                    "office365.microsoft.com",
                    "crm.microsoft.com",
                    "mssales.microsoft.com",
                    "mssupport.microsoft.com",
                    "smc.microsoft.com",
                    "sharepointjournaling.exchange.microsoft.com",
                    "wingroup.microsoft.com",
                    "managed.microsoft.com",
                    "serivce.exchange.microsoft.com",
                    "primary.exchange.microsoft.com",
                    "filtering.service.exchange.microsoft.com",
                    "pioneer.exchange.microsoft.com",
                    "wmislabcon01.redmond.corp.microsoft.com",
                    "winfarmmail.ntdev.corp.microsoft.com",
                    "WOSTIX-TEST.NTDEV.corp.microsoft.com",
                    "SPSDOG4-27.redmond.corp.microsoft.com",
                    "SPSDOG4-34.redmond.corp.microsoft.com",
                    "spsdog4-16.redmond.corp.microsoft.com",
                    "cyrusb-z400.redmond.corp.microsoft.com",
                    "MOSSDOG2982.redmond.corp.microsoft.com",
                    "osgwebindex.redmond.corp.microsoft.com",
                    "wostcktiis01.redmond.corp.microsoft.com",
                    "osgemail.redmond.corp.microsoft.com",
                    "extranettest.microsoft.com",
                    "pssupport.microsoft.com",
                    "extranet.microsoft.com",
                    "munich.microsoft.com",
                    "news.microsoft.com",
                    "mpsd.microsoft.com",
                    "gmo.microsoft.com",
                    "ims.microsoft.com",
                    "partners.extranet.microsoft.com",
                    "parttest.extranettest.microsoft.com",
                    "mscourseware.com",
                    "placeware.com",
                    "nokia.microsoft.com",
                    "www.surfaceclub.sg",
                    "winse.microsoft.com",
                    "surface.com",
                    "rare.co.uk",
                    "screentonic.com",
                    "mds.microsoft.com",
                    "mail.microsoft.com",
                    "mailflowtest.mail.microsoft.com",
                    "t-dynmktge.com",
                    "aspproject.nl",
                    "metricshub.com",
                    "ageofempires.com",
                    "azure.com",
                    "fast.no",
                    "microsoft.co.nz",
                    "live.co.hu",
                    "groupme.com",
                    "aquantive.com",
                    "fastsearch.com",
                    "microsoft.tm.hu",
                    "microsoft.ccsctp.com",
                    "healthvault.com",
                    "perceptivepixel.com",
                    "marketingpilot.com",
                    "phonefactor.com",
                    "lucernepublishing.com",
                    "vexcel.co.at",
                    "vexscan.com",
                    "qik.com",
                    "parlano.com",
                    "musiwave.net",
                    "skype.com",
                    "slimbezig.nl",
                    "Softricity.com",
                    "windows-live.hu",
                    "xboxtest.com",
                    "groove.net",
                    "008.mgd.microsoft.com",
                    "vexcel.at",
                    "officelive.co.hu",
                    "windowslive.co.hu",
                    "xbox360.co.hu",
                    "xbox.co.hu",
                    "winlive.co.hu",
                    "windows-live.co.hu",
                    "microsoft.eu",
                    "datallegro.com",
                    "projectspark.com",
                    "Storesimple.com",
                    "Phonefactor.net",
                    "yadata.com",
                    "surfaceclub.sg",
                    "microsoft.onmicrosoft.com",
                    "zone.com",
                    "sentillion.com",
                    "view012.de",
                    "windowsmedia.hu",
                    "greenbutton.com",
                    "css.one.microsoft.com",
                    "proclarity.com",
                    "rareware.com",
                    "capptain.com",
                    "mgd.microsoft.com",
                    "064d.mgd.microsoft.com",
                    "inmage.net",
                    "inmage.com",
                    "bingnews.microsoft.com",
                    "aorato.com",
                    "api.yammer.com",
                    "email.microsoft.com",
                    "officelabs.microsoft.com",
                    "Codenauts.com",
                    "codenauts.de",
                    "Hockeyapp.com",
                    "qa2.parature.net",
                    "componentart.com",
                    "datazen.com",
                    "nuvolarosa.eu",
                    "bayiportali.mmdservice.com",
                    "inside-r.org",
                    "Getliveloop.com",
                    "Sunrise.am",
                    "incentgames.com",
                    "doublelabs.com",
                    "Fantasysalesteam.com",
                    "clickdimensions.Microsoft.com",
                    "volometrix.com",
                    "bluestripe.com",
                    "time.microsoft.com",
                    "revolutionanalytics.com",
                    "inside-r.com",
                    "revolution-computing.com",
                    "fieldone.com",
                    "Pioneerinteractive.com",
                    "msitsupp.microsoft.com",
                    "metanautix.com",
                    "dwh.io",
                    "pressplay.dk",
                    "adxstudio.com",
                    "Havok.com",
                    "Trinigy.net",
                    "Projectanarchy.com",
                    "Rocketbox.de",
                    "cloudappsecurity.com",
                    "email-2.microsoft.com",
                    "Swiftkey.com",
                    "Swiftkey.net",
                    "Swiftmoji.com",
                    "Touchtype-online.com",
                    "msfts2.onmicrosoft.com",
                    "msfts2.mail.onmicrosoft.com",
                    "Xamarin.com",
                    "secureislands.com",
                    "gears.gg",
                    "promoteiq.com",
                    "sangamemail.microsoft.com",
                    "preonboarding.microsoft.com",
                    "microsoftprd.onmicrosoft.com",
                    "bluetalon.com",
                    "citusdata.com",
                    "spotfront.com",
                    "dcat.microsoft.com",
                    "jclarity.com",
                    "msftdomains.microsoft.com",
                    "msra.microsoft.com",
                    "sales.microsoft.com",
                    "askhr.microsoft.com",
                    "idwebmail.microsoft.com",
                    "movere.io",
                    "experience.microsoft.com",
                    "thefightisinus.org",
                    "Unifiedlogic.com",
                    "mover.io",
                    "msads.microsoft.com",
                    "winautomation.com",
                    "softomotive.com"
                ],
    "tenantCategory":  "Home",
    "defaultDomain":  "microsoft.onmicrosoft.com",
    "tenantType":  "AAD"
}

The ‘tenantCategory’ property will have a value of Home if you have an active account in that tenant (as a guest user for example). If lighthouse / CSP is the only authorization, it will say “ProjectedBy”. Both is also possible (tenantCategory is not a json array though, it is ‘seperated’ by a comma).

Get tenant ID using Azure Subscription ID

For a customer use case in an Azure Marketplace Managed Application scenario, I needed the ability to translate the (customers’) Azure Subscription ID (which is known to the publishing tenant) to a tenant ID. Using Get-AzSubscription, Lighthouse subscriptions don’t show the true tenant ID of the other tenant, but only show your own tenant ID.

The following PS function can retrieve the tenant ID for you (without authentication):

function get-tenantIdFromSubscriptionID($subId){
    $response = try {(Invoke-WebRequest -UseBasicParsing -Uri "https://management.azure.com/subscriptions/$($subId)?api-version=2015-01-01" -ErrorAction Stop).BaseResponse} catch { $_.Exception.Response } 
    $stringHeader = $response.Headers.ToString()
    return($stringHeader.SubString($stringHeader.IndexOf("login.windows.net")+18,36))
}