Table of Contents
We already know that when we make a call to Microsoft Graph, we need to be able to make an API call that includes the endpoint, method, and any information needed to complete our request.
The next step is to take that information and be able to use it in a PowerShell script. This post will cover some important tips and tricks to get the most out of Microsoft Graph when working with PowerShell.
There are a lot of different things we could cover, but as we have been working through this blog post, we wanted to break it down to three main areas:
- Using the right version of PowerShell
- Splatting
- And a primer understanding return codes.
Using the right version of PowerShell
The Graph PowerShell SDK is powerful but doesn’t support custom API calls. Invoke-RestMethod is a much more flexible option. We can make any REST API call we want to, it is native to PowerShell, and it automatically converts JSON or XML responses to PowerShell objects.
We want to help you build custom tools that will work in any environment, and that makes Invoke-RestMethod the best option.
Invoke-RestMethod is part of the PowerShell.Utility module. If you’re following this post, you probably already imported the MSAL module, so we don’t need to import anything else to access Microsoft Graph
PS C:\> Get-InstalledModule MSAL.PS
Version Name Repository
------- ---- ----------
4.37.0.0 MSAL.PS PSGallery
Invoke-RestMethod was updated in PowerShell version 7. The StatusCodeVariable parameter was added. This parameter allows us to easily pass the return code from our REST call back to our script.
It’s not a requirement, but it will make troubleshooting and more robust scripts possible. If you aren’t already using PowerShell version 7, we highly recommend installing PowerShell 7 now.
Invoke-RestMethod is used to send requests to Microsoft Graph (or any other REST API). There are several different parameters available, but there are only a handful we will need to use regularly. We commonly use Method, Uri, Headers, Body, and StatusCodeVariable.
- Method – this is the verb that describes what we are trying to do with our API call.
- Uri – this is the endpoint (or URL) we are accessing.
- Headers – the Header include information requiring in the request, including our authorization token and accepted response type.
- Body – this is a JSON payload that is delivered as part of a PUT, PATCH, or POST call.
- StatusCodeVariable – the status code variable tells PowerShell where to store the status code returned by the service
We can pass each one of these parameters to Invoke-RestMethod in a single line of code. That’s fine for calling Invoke-RestMethod from a terminal, but it’s going to be difficult to read in a script. It also means that we may have to make multiple API calls with explicitly defined parameters. This is cumbersome and doesn’t follow best practices for scripting.
Splatting
Splatting in PowerShell is used to pass parameters into a command using either a hashtable or an array. Arrays can be used to pass positional parameters, while hashtables can be used to pass named parameters.
Splatting allows you to reuse code, which will help to keep your scripts cleaner and easier to read. If you reuse splats with common parameters, you will be able to consistently pass common parameters and minimize errors. Parameters can be updated or added to a splat at any time prior to making an API call.
A simple splat for a GET request could look like this:
$graphParams = @{
Headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $token"
}
Method = "GET"
URI = "https://graph.microsoft.com/v1.0/users"
StatusCodeVariable = "SCV"
ErrorAction = "SilentlyContinue"
}
When we create a splat, we are creating a hashtable using the $variable = @{ key = “value” } format. Each parameter we want to add to the splat is used as a key in our hashtable, and the values are set accordingly. We can call Microsoft Graph using the following format:
$result = Invoke-RestMethod @graphParams
The result is returned as a PSCustomObject.
PS C:\> $result | Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
@odata.context NoteProperty string @odata.context=https://graph.microsoft.com/v1.0/$metadata#users
@odata.nextLink NoteProperty string @odata.nextLink=https://graph.microsoft.com/v1.0/users?$skiptoken=RFNwdAIAAQAAABI6VXNlcjEwMjVAbXNlZHUudm4pVXNlcl8yMm…
value NoteProperty Object[] value=System.Object[]
We can view the results returned from the API call by calling the $_.value property of the object. Since it is a PowerShell object, we can even pipe the value to another command. In this case, we wanted to view all the users returned that had a DisplayName that starts with “Allan“.
$result.value | Where-Object DisplayName -like 'Allan*'
businessPhones : {}
displayName : Allan Deyoung
givenName :
jobTitle :
mail : [email protected]
mobilePhone :
officeLocation :
preferredLanguage :
surname :
userPrincipalName : [email protected]
id : 19d877b4-b2f8-456d-ad26-766dec8f5d74
We also added the StatusCodeVariable parameter to our splat. By calling the variable we set, in this case $SCV, we can view the status code that was returned.
PS C:\> $scv
200
Add parameters or update existing parameters in a splat
We can add additional parameters or update existing parameters in a splat by using the format $SplatVariable[“Key”] = “New Value”. We can see that in the following example:
We will start by creating a hashtable without the URI key.
$graphParams = @{
Headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $token"
}
Method = "GET"
StatusCodeVariable = "SCV"
ErrorAction = "SilentlyContinue"
}
The hashtable was created without the Uri key:
PS C:\> $graphParams
Name Value
---- -----
Method GET
Headers {[Content-Type, application/json], ...
ErrorAction SilentlyContinue
StatusCodeVariable SCV
We can add a URI to the hashtable with the following command:
$graphParams["URI"]= "https://graph.microsoft.com/beta/users"
When we return the variable, we can see the URI has been added to our hashtable.
PS C:\> $graphParams
Name Value
---- -----
Method GET
URI https://graph.microsoft.com/beta/users
Headers {[Content-Type, application/json], ...
ErrorAction SilentlyContinue
StatusCodeVariable SCV
We can update a value using the exact same command:
$graphParams["URI"]= "https://graph.microsoft.com/v1.0/devices"
PS C:\> $graphParams
Name Value
---- -----
URI https://graph.microsoft.com/v1.0/devices
Method GET
Headers {[Content-Type, application/json], ...
StatusCodeVariable SCV
ErrorAction SilentlyContinue
Splatting in PowerShell script
There are different ways we can approach using splatting in PowerShell scripts. One approach is to create a splat for each type of API call we will make at the start of our script, and then call the specific splat when we need to call Invoke-RestMethod.
If we take this approach, we will need to have multiple splats declared early in our script. We will also need to add or update the URI and Body each time we use a splat. For example, if we have a script that will be making GET, POST, and PATCH calls, we could include the following section in our script:
#Create MS Graph Splats
#Create GET splat
$graphParams = @{
Headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $token"
}
Method = "GET"
ErrorAction = "SilentlyContinue"
StatusCodeVariable = "SCV"
}
#Create POST splat
$graphPostParams = @{
Headers = @{
"Authorization" = "Bearer $token"
"Accept" = "application/json"
"Content-Type" = "application/json"
}
Method = "POST"
ErrorAction = "SilentlyContinue"
StatusCodeVariable = "SCV"
}
#Create PATCH splat
$graphPatchParams = @{
Headers = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
Method = "PATCH"
ErrorAction = "SilentlyContinue"
StatusCodeVariable = "SCV"
}
If we take this approach, we can add the additional parameters to the splat immediately before running Invoke-RestMethod. For example, if we want to return all Entra ID Users, we can use the following script to connect to Microsoft Graph, create a splat hashtable, and finally call Invoke-RestMethod:
#Use a client secret to authenticate to Microsoft Graph
$authparams = @{
ClientId = "xxxxxxxxxxxxxxxxxxxxxxx"
TenantId = "xxxxxxxxxxxxxxxxxxxxxxx"
ClientSecret = ('xxxxxxxxxxxxxxxxxxxxxxx' | ConvertTo-SecureString -AsPlainText -Force )
}
$auth = Get-MsalToken @authParams
$AccessToken = $Auth.AccessToken
#Create MS Graph Splats
#Create GET splat
$graphParams = @{
Headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $AccessToken"
}
Method = "GET"
ErrorAction = "SilentlyContinue"
StatusCodeVariable = "SCV"
}
$graphParams["URI"]= “https://graph.microsoft.com/beta/users”
$Result = Invoke-RestMethod @graphParams
$Result
This will return our users to a PSCustomObject as we demonstrated in the first example.
PS C:\> $Result
@odata.context value
-------------- -----
https://graph.microsoft.com/beta/$metadata#users {@{id=cd90a87a-7156-4f6a-88b5-5ee908354b3c; deletedDateTime=; accountEnabled=True; ageGroup=; businessP…
Perhaps the better option would be to include a splat as part of a function. PowerShell functions allow us to reuse code, so we could simply create a different function for each method, but we can take it a step further.
By passing in parameters for the URI, method, and body, we can use a single function to handle every method available to us in Microsoft Graph.
[cmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[string]$token,
[Parameter(Mandatory=$True)]
[string]$URI,
[Parameter(Mandatory=$True)]
[string]$Method,
[Parameter(Mandatory=$False)]
[string]$Body
)
We can adjust the Splat in the function to account for different methods. In this case, both the method and URI values are variables. Their values are being passed in from the function’s parameters.
#Create Splat hashtable
$graphParams = @{
Headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $token"
}
Method = $Method
URI = $URI
ErrorAction = "SilentlyContinue"
StatusCodeVariable = "scv"
}
Some methods require a body, but not all of them do. We want to be able to add the body to our splat, but only if it is needed. We can accomplish that with a conditional statement that looks for methods that require a body.
#If method requires body, add body to splat
If($Method -in ('PUT','PATCH','POST')){
$graphParams["Body"] = $Body
}
Finally, we will make our API call using Invoke-RestMethod. Any value returned will be returned to the $MSGraphResult variable. That value and our status code variable will be returned to the main script.
#Return API call result to script
$MSGraphResult = Invoke-RestMethod @graphParams
#Return status code variable to script
Return $SCV, $MSGraphResul
Create a function with spatting
Now that we have created our function. We know that we can update the job title of a user with a PATCH request to https://graph.microsoft.com/v1.0/users/{userid}
We need to include the job title in the body of the request, so we will have to pass the body to the function through a parameter. In our example, in the below screenshot, the job title is empty for this user.
We have created a script that includes the function outlined above. This script authenticates to MS Graph using an app registration and a client secret. Each value we need to pass into our function is saved to a variable, and each variable is being passed to the function.
#Function to make Microsoft Graph API calls
Function Invoke-MsGraphCall {
[cmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[string]$Token,
[Parameter(Mandatory=$True)]
[string]$URI,
[Parameter(Mandatory=$True)]
[string]$Method,
[Parameter(Mandatory=$False)]
[string]$Body
)
#Create Splat hashtable
$graphParams = @{
Headers = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $Token"
}
Method = $Method
URI = $URI
ErrorAction = "SilentlyContinue"
StatusCodeVariable = "scv"
}
#If method requires body, add body to splat
If($Method -in ('PUT','PATCH','POST')){
$graphParams["Body"] = $Body
}
#Return API call result to script
$MSGraphResult = Invoke-RestMethod @graphParams
#Return status code variable to script
Return $SCV, $MSGraphResult
}
#Create required variables
$URI = "https://graph.microsoft.com/v1.0/users/cd90a87a-7156-4f6a-88b5-5ee908354b3c"
$Body = @{ "jobTitle" = "Sales Manager" } | ConvertTo-Json
$Method = "PATCH"
#Call Invoke-MsGraphCall
$MSGraphCall = Invoke-MsGraphCall -AccessToken $Token -URI $URI -Method $Method -Body $Body
$LastStatusCode = $MSGraphCall[0]
$ReturnedValue = $MSGraphCall[1].value
$ReturnedValue
Write-Host "SCV is $LastStatusCode"
The PATCH method doesn’t return a value, so there is no value for $ReturnedValue, but a status code value was returned. We can see that in our terminal after running the script. 204 is the exit code that we would expect for a successful PATCH request.
PS E:\> E:\scripts\graph_splat_function.ps1
SCV is 204
We can also see that the job title of the user was updated as we expected:
Microsoft Graph Response Codes
When we make calls to Microsoft Graph, we always expect a response code, whether the API call was successful or not. These response codes are essential for understanding whether our call was accepted and processed successfully. If the API call failed, the response code can be essential to understand what went wrong and how to correct it. The following table lists some of the most common error codes you may encounter.