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.
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.