Microsoft Graph API PowerShell

Microsoft Graph API PowerShell

In the last blog, We talked about how to create a registered app with Graph API permissions. This app’s main purpose is to become the base for an employee directory through Powershell. If you haven’t read it yet, you can here. Today’s blog is about how to interact with the app with PowerShell. Here are the pieces of information you will need from the Application.

  1. The Tenant’s ID
  2. The Application ID
  3. The Secret Key

If you have those three pieces of information, we can build a script to grab all the users. The app has “User.Read.All” permissions. What this means is that the app can pull all the information inside the azure ad that is directly linked to the user’s Azure AD account. This does not include things like SharePoint, mail, etc, directory, teams, etc. You have to grant permission for those items. But items like name, usernames, images, and much more is accessible as read-only.

The first thing we want to do is put the required information into variables to be used over again and again.

$AppID = "XXXXXXXX-XXXX-xxxx-xxxx-xxxxxxxxxxxx"
$AppKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$ClientID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Next, we want to make a variable that will host the clientID inside the Oauth URL string. We use this string to authenticate later we want to grab the access token.

$Token = "https://login.microsoftonline.com/$($ClientID)/oauth2/v2.0/token"

The body of the access token requires a redirect url. You can set this as “http://localhost” if you want. We really don’t care unless the app has a redirected URL assigned to it. That’s for another day. So, the next step is to build the body. We do this with a basic hash table. Inside this table, we need client_id, client_secret, redirect_url, grant_type, and the scope.

$Body = @{
        client_id     = "$AppID"
        client_secret = "$AppKey"
        redirect_url  = "$redirect_url"
        grant_type    = "client_credentials"
        scope         = "https://graph.microsoft.com/.default"
}

Now we have the Token URL, and the body. We need to get the access token. We do this with a post method to the Token URL using the body information. Then we place that token in its own variable to be used in the header.

$request = Invoke-RestMethod -Uri $token -Body $Body -Method Post
$Access_Token = $request.access_token

Now we need to create the header. We are using the Authorization inside the header. We bear the access token inside the header.

$Header = @{
        Authorization = "Bearer $($Access_Token)"
}

Up to this point, we have been building the header for the next command. Now we need to switch gears and create the query URL for the graph. You can build out the string inside the graph explorer (Link) and read up on the documentation. The permissions give us access to the users’ information. You can read up on the query statement at this link.

In our example, we want to get a list of all users and these values.

  • First Name
  • Last Name
  • Display Name
  • Email Address
  • What they use to sign in with
  • Job Title
  • Account Enabled
  • Assigned Licenses.
$userInfoLink = 'https://graph.microsoft.com/v1.0/users?$select=givenname,surname,displayName,mail,userPrincipalName,jobtitle,accountenabled,assignedlicenses'

We are using the /users api to gather this information. Hint to the User.Read.All permissions we gave in the last blog post. Now, we grab the first piece of information using Invoke-RestMethod.

$PageInfo = Invoke-RestMethod -Headers $Header -Uri $UserInfoLink -method get 
$PageInfo.Value
$Userinfo = $PageInfo.Value

BAM! we have information. $PageInfo.Value will give you the first hundred records. Wait, only the first 100? Yep, Graph only presents the first 100 items. So, how do you handle that? You create a loop. how do we determine if we need a loop, we look to see if the value ‘@odata.nextlink’ exists. We do this with a Do While loop and the ‘@odata.nextlink’ information. The ‘@odata.nextlink’ is the next link request that will need to be executed. AKA the next page of the outcome. So each time the loop has a ‘@odata.nextlink’ the final loop will stop because it doesn’t have the link.

Do {
        $PageInfo = Invoke-RestMethod -Headers $Header -Uri $PageInfo.'@odata.nextlink' -Method Get
        $Userinfo += $PageInfo.Value 
} while (($PageInfo | Get-Member).name -contains '@odata.nextlink')

Notice we keep adding $PageInfo.Value to userinfo. This is building the userinfo array. This way we have the data we need. One of the requirements for this script is to have the Assigned user Information. So we need to add the sku part number of each license to the user. The problem is the assigned licenses gives back only the skuID. So what we can do is loop through the $UserInfo and add them accordingly. So we do this by starting a For Loop. Why a for loop? Because we will be adding a member property of the sku part numbers.

for ($I = 0; $I -lt $Userinfo.count; $I++) {
}

Inside this loop, we need to get the users’ licenses. We can do that with graphs as well. It’s still part of the user.read.all permissions as it still uses the /users api. We select the user by giving the UPN name. Then we ask for the license details with a /licenseDetails. I know super complex. Here is what the link will look like

for ($I = 0; $I -lt $Userinfo.count; $I++) {
        $LiceLink = "https://graph.microsoft.com/v1.0/users/$($UserInfo[-1].Userprincipalname)/licenseDetails"
}

Next we invoke the rest method again and using the link we generated pull in the information. I have never seen a user have more than 20 licenses. So, no need for a loop here.

for ($I = 0; $I -lt $Userinfo.count; $I++) {
        $LiceLink = "https://graph.microsoft.com/v1.0/users/$($UserInfo[-1].Userprincipalname)/licenseDetails"
        $UserLice = (Invoke-restmethod -Headers $Header -Uri $LiceLink -Method Get).value
}

Finally, we use the Add-Member feature to add a licensing information variable with the sku part number of each licenses inside that user’s account.

for ($I = 0; $I -lt $Userinfo.count; $I++) {
        $LiceLink = "https://graph.microsoft.com/v1.0/users/$($UserInfo[-1].Userprincipalname)/licenseDetails"
        $UserLice = (Invoke-restmethod -Headers $Header -Uri $LiceLink -Method Get).value
        $UserInfo[$i] | Add-Member -Name "LicenseInfo" -MemberType NoteProperty -Value $UserLice.skupartnumber
}

Each time the loop runs, it pulls that User’s UPN from the UserInfo array. Grabs the details of the license from graph and pulls only the sku part number. Then adds that part number to the license info inside the UserInfo array at that location. Let’s put it all together now.

The Script

Function Get-GraphEmployeeReport {
    [cmdletbinding()]
    param (
        [string]$org,
        [string]$AppID = (Read-Host -Prompt "AppId"),
        [string]$AppKey = (Read-Host -Prompt "AppKey"),
        [string]$ClientID = (Read-Host -Prompt "ClientID"),
        [string]$redirect_url = "https://localhost",
        [string]$OutfilePath = "C:\FMIT\Reports\MFA",
        [switch]$Output
    )
    $Token = "https://login.microsoftonline.com/$($ClientID)/oauth2/v2.0/token"
    $Body = @{
        client_id     = "$AppID"
        client_secret = "$AppKey"
        redirect_url  = "$redirect_url"
        grant_type    = "client_credentials"
        scope         = "https://graph.microsoft.com/.default"
    }
    $request = Invoke-RestMethod -Uri $token -Body $Body -Method Post
    $Access_Token = $request.access_token
    
    $Header = @{
        Authorization = "Bearer $($Access_Token)"
    }
    $userInfoLink = 'https://graph.microsoft.com/v1.0/users?$select=givenname,surname,displayName,mail,userPrincipalName,jobtitle,accountenabled,assignedLicenses'
    $PageInfo = Invoke-RestMethod -Headers $Header -Uri $UserInfoLink -method get #| where-object { ($_.assignedLicenses.count -gt 0) -and ($_.accountEnabled -eq $true) }
    $Userinfo = $PageInfo.Value
    Do {
        $PageInfo = Invoke-RestMethod -Headers $Header -Uri $PageInfo.'@odata.nextlink' -Method Get
        $Userinfo += $PageInfo.Value 
    } while (($PageInfo | Get-Member).name -contains '@odata.nextlink')
    for ($I = 0; $I -lt $Userinfo.count; $I++) {
        $LiceLink = "https://graph.microsoft.com/v1.0/users/$($UserInfo[-1].Userprincipalname)/licenseDetails"
        $UserLice = (Invoke-restmethod -Headers $Header -Uri $LiceLink -Method Get).value
        $UserInfo[$i] | Add-Member -Name "LicenseInfo" -MemberType NoteProperty -Value $UserLice.skupartnumber
    }
    $Userinfo
}

That’s all it takes to get the information from the azure ad. You can export this data into an XML or JSON and integrate it with any other system that you like. This does work with Powershell universal as well. You can create a table that has the next button or just gather all the information at once and present it. The more times it looks the longer the information will take to populate. Using the User.Read.All, you are also able to pull photos with graph API.

If you have any more questions, feel free to ask.