Automating with Graph API

Automating with Graph API

Last week we discussed sending emails with Graph API. You can read about it here. Today we will be taking that script and making it so it can be automated. On the backend, you will need to create an Azure App. You can read about how to do that here. The following code only works in Powershell 7 and above. Automating with Graph API works best in PowerShell 7. You will need to set up your App with Users.Read.All and Mail.Send as the minimal. levels.

The Script

import-module Microsoft.Graph.Users
Import-module Microsoft.Graph.Users.Actions  

$EmailToSend = "A Cloud Email @ your domain"
$EmailToReceive = "Any Email"
$AppID = "This is your App ID"
$SecuredPassword = "This is your Password"
$tenantID = "This is your tenant ID"

$SecuredPasswordPassword = ConvertTo-SecureString -String $SecuredPassword -AsPlainText -Force
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppID, $SecuredPasswordPassword

Connect-MgGraph -TenantId $tenantID -ClientSecretCredential $ClientSecretCredential

#Connect-MgGraph -Scopes "User.Read.All, Mail.Send"
$users = Get-MgUser -filter "accountenabled eq false"
$ReturnString = ""
foreach ($user in $users) {
    if ($null -ne (Get-MgUserLicenseDetail -UserId $user.Id)) {
        [pscustomobject][ordered]@{
            UPN      = $user.UserPrincipalName
            Licenses = (Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", "
        }
        $ReturnString = $ReturnString + "$($user.UserPrincipalName): $((Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", ")`n"
    }
}
 
$body = @"
<html>
<header>Licenses</header>
<body>
<center>
<h1>Disabled Users</h1>
<h2>With Licenses</h2>
</center>
$ReturnString
</body>
</html>
"@
$params = @{
    message = @{
        subject = "Disabled Users with Licenses"
        body = @{
            contentType = "HTML"
            content = $body
        }
        toRecipients = @(
            @{
                emailAddress = @{
                    address = $EmailToReceive 
                }
            }
        )
    }
    saveToSentItems = "false"
}
 
# A UPN can also be used as -UserId.
Send-MgUserMail -UserId $EmailToSend -BodyParameter $params

The Breakdown

This script is the same as last week’s except for how it connects and how you feed the email addresses. We are using the Client Secret Credential flag, which is only available in Powershell 7, to trigger the connect command. You need some basic information first. This information will allow Automating with Graph API to work.

$AppID = "This is your App ID"
$SecuredPassword = "This is your Password"
$tenantID = "This is your tenant ID"

The App is the application ID from the azure app you created. the tenant ID is also the tenant ID of the azure app you created. Remember, I stated to keep the secret key value. This is where you will use it. Place it in the Secure Password area. Next, we need to convert this information into a secure object.

$SecuredPasswordPassword = ConvertTo-SecureString -String $SecuredPassword -AsPlainText -Force
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppID, $SecuredPasswordPassword

Now, we need to convert the plain text to a secure string. We do this with the convertto-securestring command. We enter the string and force it with the force tag. Once we have done that, we want to create a credential object. We use the new-object command to create an automation pscredential object. We feed it the appID and the password we created above. This gives us the ps object that we will use for the next part.

Connect-MgGraph -TenantId $tenantID -ClientSecretCredential $ClientSecretCredential

Using the connect-mggraph command we connect to our tenant and pass the app id and password as a single object. This will connect us directly to Graph API. Later we will run this script through the task scheduler. The remainder of the script will stay the same. Finally, we supply the email addresses. Automating with Graph API couldn’t be easier. So Enjoy!

Additional Links

Microsoft Graph API – Powershell, Download user Images

Microsoft Graph API – Powershell, Download user Images

In my previous post, we went over how to Grab user information from a client. Today we will be going over how to Download User Images with Graph API. This piece is very straightforward until you get to the graph link. There is a unique limitation to PowerShell quotes that I found a good workaround.

Ok, we start off with the loop like before. We are using the /Users API. Since this is a user-level item, you have a top loop through each user with the User Principal Name. This means your string will be inside double quotes “” instead of single quotes because you want PowerShell to read the value of the $($UPN). so far simple. The next part is the word photo. Once again, simple. Then the impact. the word $value has to be at the end. This means it’s going to drop whatever is instead value into the string. There are a few ways around this.

Option 1

Declare the variable beforehand. Simple and easy way to fix this problem.

$Value = '$value'
$UserPhotoLink = "https://graph.microsoft.com/v1.0/users/$($UPN)/photo/$value"

Options 2

Use the + symbol to add the string.

$UserPhotoLink = "https://graph.microsoft.com/v1.0/users/$($UPN)/photo/" + '$value'

Download User Images with Graph API

Now we have the link we need to download the image. Once again we are going to use the invoke-restmethod with our custom header like before. This time we are going to give the -outfile. Since not everyone has an image, I am also going to set the error action to silently continue.

Invoke-RestMethod -Method get -Headers $Header -Uri $UserPhotoLink -ErrorAction SilentlyContinue -OutFile "$DropPath\$($UPN).jpg" 

That’s it. The for loop will allow you to download all of your user’s images with the UPN as the file name. I hope this has been helpful.

More Information:

Microsoft Graph API – Powershell, Download user Images

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.