Home Disabling self-service licenses (in bulk!)
Post
Cancel

Disabling self-service licenses (in bulk!)

As you may be aware users can self-service purchase licenses. This is a feature that is turned on by default, and very annoyingly, one that has to be turned off per eligible product (and Micrpsoft likes to add new ones). Once upon a time you could make creative use of the Msol module to turn self-service off over all your tenants but with the demise of that module and DAP we have been without a solution for this. I recently ran into the newer MSCommerce module which allows you to turn self-service off on a per tenant basis and noticed that it uses a scope that is available to use in other contexts. After some poking I’ve got great news for everyone, it works, and it works through GDAP! That means we can turn off self-service in bulk over ALL our tenants.

Desktop View Pictured: Users adding licenses to the tenant by themselves through the self service functionality.

Prerequisites

You will need a multi-tenant app (Secure Application Model) that is consented in your customer tenants. This blog will not go into setting this up. If you don’t have one I can recommend this post from Nick from T-Minus 365. It contains all the building blocks and information needed to get started with the Secure Application Model and contains updated information for the GDAP era.

You will have to add an API/permission to your app.

Go to your app in EntraId, open the “API Permissions page”. Click “Add a permission”. Click “APIs my organization uses”. VERY SPECIFICALLY search for M365 License Manager. Click “Delegated permissions”. Select the LicenseManager.AccessAsUser permission to add it your app. This permission will also have to be consented in your customer tenants.

The code

Our next job is requesting a token for the customer tenant using the “M365 License Manager/aeb86249-8ea3-49e2-900b-54cc8e308f85” scope. As usual I’m using my own token retrieval function here. All you have to do is fill the values of $commonTokenSplat and of course $customerTenantId.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
$customerTenantId = ""

$commonTokenSplat = @{
    ApplicationID = ""
    ApplicationSecret = ""
    RefreshToken = ""
}

function Get-MicrosoftToken {
    Param(
        # tenant Id
        [Parameter(Mandatory=$false)]
        [guid]$tenantId,

        # Scope
        [Parameter(Mandatory=$false)]
        [string]$Scope = 'https://graph.microsoft.com/.default',

        # ApplicationID
        [Parameter(Mandatory=$true)]
        [guid]$ApplicationID,

        # ApplicationSecret
        [Parameter(Mandatory=$true)]
        [string]$ApplicationSecret,

        # RefreshToken
        [Parameter(Mandatory=$true)]
        [string]$RefreshToken
    )

    if ($tenantId) {
        $Uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
    }
    else {
        $Uri = "https://login.microsoftonline.com/common/oauth2/v2.0/token"  
    }

    # Define the parameters for the token request
    $Body = @{
        client_id       = $ApplicationID
        client_secret   = $ApplicationSecret
        scope           = $Scope
        refresh_token   = $RefreshToken
        grant_type      = 'refresh_token'    
    }

    $Params = @{
        Uri = $Uri
        Method = 'POST'
        Body = $Body
        ContentType = 'application/x-www-form-urlencoded'
        UseBasicParsing = $true
    }

    try {
        $AuthResponse = (Invoke-WebRequest @Params).Content | ConvertFrom-Json
    } catch {
        throw "Authentication to $($tenantId) failed: $($_.Exception.Message)"
    }

    return $AuthResponse
}

# Get token for the customer tenant
if ($LicenseToken = (Get-MicrosoftToken @commonTokenSplat -TenantID $customerTenantId -Scope "aeb86249-8ea3-49e2-900b-54cc8e308f85/.default").Access_Token) {
    $header = @{
        Authorization = 'bearer {0}' -f $LicenseToken
        Accept        = "application/json"
    }
}

You should now have a valid token and header for your customer tenant and the aeb86249-8ea3-49e2-900b-54cc8e308f85 scope. Now we will use that header to retrieve the self-service product data from the customer tenant.

1
2
3
4
5
try {
    $selfServiceItems = (Invoke-RestMethod -Method GET -Uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products" -Headers $header).items
} catch {
    throw "Failed to retrieve self service products: $($_.Exception.Message)"
}

Now that we have the self-service product data we can foreach through it and disable them.

1
2
3
4
5
6
7
8
$selfServiceItems | Where-Object { $_.policyValue -eq "Enabled" } | ForEach-Object {
    try {
        $product = $_
        (Invoke-RestMethod -Method PUT -Uri "https://licensing.m365.microsoft.com/v1.0/policies/AllowSelfServicePurchase/products/$($product.productId)" -Headers $header -ContentType 'application/json' -body '{ "policyValue": "Disabled" }')
    } catch {
        Write-Error "Failed to disabled product $($product.productName):$($_.Exception.Message)"
    }
}

And that’s it! All you have to do is loop through your customer tenants using this solution and self-service purchasing will be turned off. The funny thing is that the MSCommerce module is technically perfectly capable of doing this, except the Connect cmdlet wasn’t made with MSPs/multi-tenant in mind.

This post is licensed under CC BY 4.0 by the author.