Unlocking Microsoft Graph API with PowerShell: My Journey from Confusion to Clarity
Introduction
As an IT administrator, PowerShell has always been my go-to tool for managing Microsoft environments. When I first started working with the Microsoft Graph PowerShell module, I could execute commands successfully, but I soon realized that while I could follow a script, I didn’t fully grasp the fundamentals of authentication and interacting with APIs. That’s when I decided to take a step back and truly learn how APIs work, how authentication is handled, and how to apply this knowledge to Microsoft Graph API using PowerShell.
Why I Decided to Learn About APIs
I initially assumed that understanding APIs was only necessary for developers, but as I delved deeper into Microsoft Graph, I realized that mastering APIs unlocks endless possibilities for automation and control. The Microsoft Graph PowerShell module simplifies API interactions, but to troubleshoot issues, customize requests, or use features not covered by cmdlets, a deeper understanding of REST APIs and authentication is essential.
How I used ChatGPT to learn the Basics of APIs and Authentication
I started my chat conversation by giving ChatGPT some context and a bit about how I like to learn by doing, here’s my original prompt:
I am an IT administrator with years of experience in Active Directory, Office 365, Microsoft products as well as PowerShell automation for admin tasks, however I have no developer experience. I need to learn about APIs and ultimately interact with the Microsoft Graph API through PowerShell, but first I need to understand the concepts of APIs. I learn best by doing, so provide me workable examples to test out on my Windows 11 machine. I would prefer not to install something like postman until I have a good understanding of the concepts. Teach me about APIs
Step 1: Playing with Open APIs
The first thing it came back with was some API basic concepts:
Endpoints – The URL you send requests to (e.g., https://api.example.com/users
).
HTTP Methods – Define what action to perform:
GET
– Retrieve data.POST
– Send data to the server.PUT
– Replaces an entire resource.PATCH
– Update existing data, but only specified fields.DELETE
– Remove data.
Headers – Include additional information, such as authentication tokens or content type.
Query Parameters – Add extra filters to a request (e.g., ?id=123
).
Responses – Data returned by the API, usually in JSON format.
I then explored interacting with public APIs that don’t require authentication using PowerShell. One great example was testing the JokeAPI:
Invoke-RestMethod -Uri "https://v2.jokeapi.dev/joke/Any?type=single"
This simple command fetched a random joke in JSON format. It helped me understand how APIs return structured data and how I could extract useful information.
Breaking it Down
Invoke-RestMethod
– PowerShell cmdlet to interact with APIs.
-Uri
– Specifies the API endpoint.
?type=single
– Query parameter to filter results.
It was at this point that I went off on a tangent discovering how to filter objects and realised that each specific API should have documentation about the available filters or parameters.
TL;DR – Filtering
- Check the API documentation – It lists all query parameters.
- Look for valid values – Some APIs provide a list of allowed values.
- Combine filters with
&
– Example:?category=Programming&type=single
. - Use commas (
,
) for multiple values – Some APIs allow?category=Programming,Pun
. - Some APIs support
$filter
syntax – Like Microsoft Graph API.
Step 2: Making more complex requests with headers
I then started using custom headers:
Example: Get a Random Quote with Headers
$headers = @{
"Accept" = "application/json"
}
Invoke-RestMethod -Uri "https://api.quotable.io/random" -Headers $headers
Breaking it Down
- The
Accept
header tells the API that we want JSON data. - The response includes fields like
"content"
(the quote) and"author"
.
I also learnt the following:
- ChatGPT makes up some API endpoints that don’t exist. But I got the gist.
- PowerShell reads JSON automatically – No need to convert
GET
responses. - For
POST
/PATCH
/PUT
requests, always useConvertTo-Json
. - Set
-ContentType "application/json"
to tell the API you’re sending JSON.
Step 3: Learning Authentication with Access Tokens
Some API’s like Microsoft Graph API require authentication. Authentication was the missing piece for me. Unlike PowerShell modules that abstract authentication, raw API requests require an access token to prove identity and permissions. Microsoft Graph uses OAuth 2.0, which relies on API permissions to control access.
I first dabled with our internal Project Management tool’s API which requires an API key. But ChatGPT suggested I sign up for a free API key at OpenWeather to try it’s examples. I skipped that after playing with my ProofHub API.
Before you can interact with Microsoft Graph API using PowerShell, you need to register an Azure AD App to obtain an authentication token.
Since Microsoft Graph requires authentication, I needed to create an App Registration and a client secret. This process is well documented, but there are two important API permission concepts I needed to understand first – Delegated vs Application permissions.
Understanding API Permissions in Microsoft Graph
- Delegated Permissions – Used when a signed-in user is accessing the API. The application acts on behalf of the user, requiring consent.
- Application Permissions – Used for background services or automation scripts, requiring admin consent in Entra ID.
If I used delegated permission in my App Registration, this means the API call is made on behalf of the signed-in user (in a PowerShell session). The user must therefore have the necessary permissions. Also the signed-in user must have the right role with privileges to run the required query.
If you don’t want to depend on a user signing in, switch to Application Permissions instead of Delegated Permissions and grant admin consent.
How Permissions Are Included in the Access Token
When an application authenticates, the OAuth flow issues an access token containing the approved permissions (scopes). The token is then passed in the request header to authorize API calls.
$tenantId = "your-tenant-id"
$clientId = "your-client-id"
$clientSecret = "your-client-secret"
$scope = "https://graph.microsoft.com/.default"
$body = @{
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
scope = $scope
}
$tokenResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Body $body
$accessToken = $tokenResponse.access_token
The scope parameter determines the permissions requested. The access token must be included in the Authorization header of every request.
Step 4: Making API Calls Without a PowerShell Module
Once I had the token, I could make direct API calls. Here’s how I retrieved user data manually:
$headers = @{ Authorization = "Bearer $accessToken" }
$response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users" -Headers $headers -Method Get
$response.value
Seeing the raw API response gave me a much clearer understanding of how data is retrieved and structured.
Taking the Next Step: Applying This Knowledge to Microsoft Graph
Once I understood the underlying API structure, I returned to the Microsoft Graph PowerShell module with a new perspective. Now, when I run:
Get-MgUser -Top 5
I know that behind the scenes, it’s making an API call to https://graph.microsoft.com/v1.0/users with proper authentication.
Conclusion
My journey into Microsoft Graph API started with frustration but ended with a much deeper appreciation for how APIs work. By breaking the process down—playing with open APIs, learning REST fundamentals, understanding authentication, and making raw API calls—I’ve gained confidence in using Graph API beyond just the PowerShell module.
For IT admins who find themselves in the same boat, my advice is: take the time to learn APIs at a fundamental level, and you’ll unlock powerful new capabilities in PowerShell automation.
Have you had a similar experience learning Graph API? Let’s discuss in the comments!