Microsoft Graph: a Practical Guide - Part 2
From one-off to repeatable functions
Last time, I showed how to connect to Microsoft Graph with just the Microsoft.Graph.Authentication module and just using Invoke-MgGraphRequest. Using just the one Microsoft Graph SDK module dramatically lessens the Graph module overhead and simplifies authentication - win win!
This time, let’s talk about turning those Graph calls into small, reusable PowerShell fuctions (spoiler, the same ones that I use for my Intune automation projects).
These functions already are available in my public GitHub repo:
I’m not changing any of these scripts, but want to talk about why they are setup the way that they are and how you can build your own.
The Pattern
1.Keep auth simple ‧₊˚🖇️✩ ₊˚🎧⊹ ♡
Everything starts with Connect-ToGraph.ps1.
It’s small on purpose. Import the auth module, define the copes you need (I have it set to .default since I want to use the scopes of the OIDC connection I’m using for my repo), and connect. I like to explicitly set my Graph version to beta, but your milage may vary. 
Keeping the the Graph endpoint version the same is helpful in minimizing the “it worked yesterday, now it doesn’t” moments.
2.One function per endpoint ✩°。🎧 ✩°。⋆⸜
Each function does one job. 
To save myself the headache, I don’t do anything clever with wrappers and hiding the URL. I want to make sure that it’s easy for me to debug and figure out what I was doing 6 months from now.
- Policy→- /deviceManagement/deviceConfigurations
- Assignments→- /deviceManagement/deviceConfigurations/{id}/assignments
- Status→- /deviceManagement/deviceConfigurations/{id}/deviceStatuses
It’s the same pattern every time. Defined resource path, a $GraphApiVersion variable, and Invoke-MgGraphRequest.
How I find those paths is by using Graph Explorer, confirm that the path it works, and then paste it into PowerShell. I want readers (and future me) to see exactly what’s happening - visible, testable, and repeatable.
3.Return data, not decoration ✩ ♬₊.⋆☾⋆⁺₊✧✩♬
These functions are meant to return objects.
If you want formatting, pipe it!
Get-DeviceConfigurationPolicy | Select displayName, id
This keeps the function reusable. Feed it to reports, filters, GitHub Actions… without editing it.
Why this works
It’s lightweight, readable, and consistent.
You see the exact Graph endpoint, and you can test it in the same way Microsoft documents it.
That transparency helps when you’re debugging or teaching others how the Graph API actually behaves.
This approach also scales naturally. Once you understand the pattern, you can build your own functions for compliance policies, remediation scripts, or Intune assignments — same structure, same repeatable logic.
As you can see, relatively straightforward and something that you can build off of without needing to import the many, many, many Microsoft Graph modules.
Common Gotchas
- Portal vs v1.0: Intune endpoints are a mix between v1.0 and beta. If your results don’t match what you see in the portal, check your API version first and validate with Graph Explorer. 
- Paging: Graph responses with @odata.nextlink need to a loop. Build a one reusable paging helper and move on. 
- Status confusion: Device and user statuses are separate endpoints, similar to policy endpoints. Be clear about which you’re calling. 
Closing thoughts
I’ve heard so many iterations of Intune can’t be treated like infrastructure-as-code because “we only have one tenant.”
But once you structure your Graph calls similar to this - modular - this is one of the building blocks for version control, CI/CD, and creating your on promotion gates and criteria.
This is the foundation for what became IntuneStack: testing, promotion, and compliance gates built on the same lightweight Graph functions.
