EvotecIT / O365Essentials

A module that helps to manage some tasks on Office 365/Azure via undocumented API

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fetching users licensed by a specific product

f0oster opened this issue · comments

Hi,

I found this module when poking around reversing the internal AAD APIs and hacked together a quick function to dump user license assignments (direct & group) using the https://main.iam.ad.ext.azure.com/api/AccountSkus/UserAssignments endpoint.

Unfortunately I can't use the Graph API for this purpose at the moment (no access, and I'm not a GA to grant consent in our tenant), and was trying to avoid the old approach of dumping all tenant users via MSOL, since it's obscenely slow (we have 400k+ users) and also being retired.

Unfortunately, it seems like this endpoint is rather buggy and the data received is sometimes null. This is especially common for licenses with a large quantity of users assigned, which is a shame, as it's what my main use case was. I can replicate the same issues in the browser through the AAD interface (with API responses having null data). Was wondering if you had any experience experimenting with this endpoint?

Anyway, if you wanted to implement this feature yourself or wanted me to submit a PR, please let me know. The function is below.

function Get-O365LicenseAssignmentsByFeatureName {

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [string] $FeatureName,
        [string] $TenantName
    )
        
    $NextLink = ""
    $LastPage = $false
    
    $Result = while (-not($LastPage)) {
        
        $Uri = "https://main.iam.ad.ext.azure.com/api/AccountSkus/UserAssignments?accountSkuID=$TenantName%3A$FeatureName&nextLink=$nextLink&searchText=&columnName=&sortOrder=undefined"
        
        Write-Verbose "Querying $Uri..."
        Invoke-O365Admin -Uri $Uri -Headers $Headers

        $NextLink = $Res.nextLink
        $LastPage = $Res.lastPage

        if ($lastPage) {
           
            Write-Verbose "Reached last page."
            break

        }

        if (-not($nextLink)) {

            Write-Verbose "No nextLink fed by response."
            break

        } 

    }

    return $Result

}

Can't say I had any issue with this API, but I trust you on that. I would be happy to accept PR, but keep in mind that Invoke-O365Admin should process NextLinks automatically and there's no need for LastPage,NextLink and so on - at least I believe so. If it's not supported now (and it should be) I would prefer to fix Invoke-o365admin rather than do it like you did for specific query.

but keep in mind that Invoke-O365Admin should process NextLinks automatically and there's no need for LastPage,NextLink and so on

Thanks for pointing this out, I neglected to read the implementation / how this function works :).

Let me confirm and get back to you on this.

I've read Invoke-O365Admin and I see what you're referring to. The implementation doesn't work with this endpoint.

I guess the endpoint may not follow the same conventions as others you've consumed - @odata. prefix is being omitted from the API responses, and is also not being included in the query parameters by the client.

Browser query params:

accountSkuID:
nextLink: (this is a base64 encoded string)
searchText: 
columnName: 
sortOrder:

API Response:

{
	"sortedList": true,
	"items": [
		// list of licenses/users
	],
	"nextLink": "base64 encoded string",
	"firstPage": true,
	"lastPage": false
}

Ok, so if it's a bit different output I would suggest to add logic to Invoke-O365Admin to detect that and apply i, so one can use it easily and we keep mess outside.