Michael Aitken

IT Engineer, Tinkerer, Educator


PowerShell: Graph Queries

Posted on: June 22, 2025


While I rely largely on the Microsoft.Graph PowerShell modules, not all Graph API endpoints can be called by the current release and beta versions of Microsoft.Graph cmdlets. There have been scenarios where Get-MgUser has not been able to pull everything, like custom security attributes populated by an app registration, while Graph Explorer is able to. The problem: Graph Explorer won't benefit my scripts. This is where Invoke-MgGraphRequest comes in handy. This cmdlet allows you interact directly with the Graph API endpoint similarly to how you would in Graph Explorer. Microsoft.Graph and Microsoft.Graph.Beta modules are not always up to date with the newest general availability and preview functionalities, though Graph may allow you to work with them. If you have a working Graph query, you can use it with Invoke-MgGraphRequest.

Preparation

The Invoke-MgGraphRequest cmdlet is included in the same module as Connect-MgGraph, which we will need for our connection. Install the Microsoft.Graph.Authentication module before moving forward. If you want to put together a handy way to install and update your modules, this article may help. If you want a quick run-through on connecting with Connect-MgGraph and some fun things you can do with it, this article may interest you.

PowerShell

### Install Microsoft.Graph.Authentication
Install-Module -Repository PSGallery -Name Microsoft.Graph.Authentication
PowerShell

### Connect to Graph
$tenantId = 'xxxxx'
$clientId = 'yyyyy'
Connect-MgGraph -TenantId $tenantId -ClientId $clientId -NoWelcome

As a last note on preparation, I would highly recommend building and testing your query first in Graph Explorer. You’ll get the benefit of Graph Explorer’s auto-complete and drop-down menu for creating your query, modifiable (but still delegated) permissions that may make determining assignment easier if this query is going to go into a runbook, as well as the response previews showing your output, viewable response headers, and code snippets showing code that would achieve the same output in several languages. The code snippets that Graph Explorer will show you a PowerShell equivalent, however it will show code using the Microsoft.Graph modules. That’s incredibly useful and may even partly invalidate this article. As mentioned before, not everything that can be achieved with Graph API can be achieved with the cmdlets in the Microsoft.Graph modules. Thus, we press forward. Once you’ve tested and confirmed your URI, you can copy and paste it.

Invoke-MgGraphRequest

For this article, we will keep our URIs simple. We will look at pulling all user objects (GET), creating a new user, modifying a user (PATCH), and deleting a user (DELETE). Simple, not incredibly in-depth, but will showcase the cmdlet well. Each example will use the Invoke-MgGraphRequest cmdlet, a method (GET/POST/PATCH/DELETE), and a URI (where you can copy and paste your tested URI, encapsulated in quotation marks). Methods that update information, like PATCH and POST, will require a body parameter in JSON format. Like other multi-line strings, this JSON will need to be encapsulated in here-string markers and will need to be left-aligned in your editor. See the code blocks for examples. But enough prep, let's get to the fun part.

GET

First, we'll use the GET method to pull user information. If you're in Graph Explorer, you will get a sample that pulls the authenticated user's attributes. You can change the "me" at the end to "users" to get all your user objects or change it to "users/user-id" to get a specific user. Add "?$select=attribute" to the end to limit the attributes in your output. These can be stored in a variable as a single object or array and exported/processed the same as any other array.

PowerShell

### Graph GET request

### Pull the authenticated user's attributes
Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/me'

### Pull a specific user's attributes
Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/users/user-id'

### Pull all user's attributes
(Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/users').value

### Pull all user's attributes, limit output to DisplayName and Id. Store as a variable
$AllUsers = (Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/users?$select=DisplayName,Id').value

Hold up, you probably noticed the "().value" on the queries pulling all users. If your query is on a single object, you will see the results as you'd expect to see them. If you're querying a "collection" of objects, like the users, groups, etc. endpoints, then you will receive multiple attributes pertaining to the query itself, not the results of the query. You will likely see "@odata.context", which is a metadata value telling your client how to interpret the data based on the Open Data Protocol (OData), as well as a "value" attribute, which is an array containing your results. Unless you know you need the @odata.context value, you can ignore it. It may come in handy for creating a tool that dynamically interprets and validates schemas. As cool as that sounds, that's not what we're doing here. Let's move on to POST.

POST

In our POST example, we'll create a new user. The URI will be simple. Use the endpoint you want this new object to be, in this case, the users endpoint. The complex piece here is the request body. You will need to create a JSON with the attributes and parameters you want to set on this user. This will mirror what you would put in Graph Explorer. As mentioned before, encapsulate your JSON in here-string. Start with a @" and end with a "@. Note that the opening @" should be the last thing on its line and the closing "@ should be left-aligned for the formatting to work. This is required for PowerShell to recognize the string as being a multi-lined value. You do not need to create a variable out of the new user being created as I did below. You can, and you may find it useful if you need to reference that new user object in your script. The variable will make the process more efficient.

PowerShell

### Graph POST request
$requestBody = @"
{
  "accountEnabled": true,
  "displayName": "Appleseed, Johnny",
  "mailNickname": "JAppleseed",
  "userPrincipalName": "[email protected]",
  "passwordProfile": {
    "forceChangePasswordNextSignIn": true,
    "password": "1ncr3dibly$ecureP@$sw0rd"
  }
}
"@
$NewUser = Invoke-MgGraphRequest -Method POST -Body $requestBody -Uri "https://graph.microsoft.com/v1.0/users"

PATCH

Next, we'll look at PATCH and use it to update a user's department and job title. This will be very similar to the above POST example, though our JSON can be simpler. You will need to use attribute names as they exist in Graph. You can dig into the Graph REST API v1.0 and Graph Rest API beta documentation for more.

PowerShell

### Graph PATCH request
$requestBody = @"
{
  "Department": "Accounting",
  "jobTitle": "Accountant 2"

}
"@
Invoke-MgGraphRequest -Method PATCH -Body $requestBody -Uri 'https://graph.microsoft.com/v1.0/users/user-id'

DELETE

For our last example, we'll use DELETE to soft-delete the user object we created earlier, using our $newUser variable as a reference. No JSON will be needed here, just a method and URI. You will need to provide the Id value for the user. You can either break it out into its own variable first and then feed it into your URI, or you could declare it using the $() subexpression. Either will work the same.

PowerShell

### Graph DELETE request

### Declare variable for UserId, then feed to URI
$idNewUser = $NewUser.id
Invoke-MgGraphRequest -Method DELETE -Uri 'https://graph.microsoft.com/v1.0/users/$idNewUser'

### Use subexpression
Invoke-MgGraphRequest -Method DELETE -Uri "https://graph.microsoft.com/v1.0/users/$($NewUser.id)"

Use Cases

I personally prefer Microsoft.Graph cmdlets, but not all Graph endpoints can be called with Microsoft.Graph cmdlets. One that immediately comes to mind (at the time of writing) is eDiscovery in Microsoft Purview. There is a set of beta cmdlets, but they do not appear to function yet. I have also only been able to pull custom security attributes that are managed by application objects. It's useful to pull those for troubleshooting and verification but Get-MgUser does not support those properties. Invoke-MgGraphRequest, however, does.

PowerShell

### Pull all eDiscovery cases (that you have access to)
Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/security/cases/eDiscoveryCases'
PowerShell

### Pull a specific user's custom extension attribute
Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/users/user-id?$select=Id,DisplayName,extension_ClientId_AttributeName'

Conclusion

It may be more convenient to use Microsoft.Graph modules in your PowerShell scripts. Those modules and cmdlets are still evolving and expanding as the Microsoft cloud ecosystem also continues to evolve and expand. Graph will let you work with all of it and sometimes you'll be required to work with it directly. Invoke-MgGraphRequest will let you get around the Microsoft.Graph modules missing some functionality and let you continue to script for certain scenarios despite that missing functionality. Specifically in terms of working directly with Graph, as much as I hate to don my "you need to understand the inner workings" hat, it will benefit you to dive deeper into making Graph API calls. Like how base PowerShell cmdlets are wrappers for .NET code, the cmdlets in the Microsoft.Graph modules are wrappers for Graph calls. You may see Get-MgUser, but behind the scenes, what's being processed is actually "GET https://graph.microsoft.com/v1.0/users". Deeper understanding is always a positive.

How do you use Invoke-MgGraphRequest. Do you use Graph Explorer a lot and see a benefit from this? Let me know your thoughts! You can reach out to me at any of my linked socials, like my LinkedIn, or email me at [email protected].