Using Microsoft Graph from Perl
For my work I needed to interact with Microsoft Graph.
The obvious choices would have been PowerShell or Node.js.
I did try Node.js for a bit, but it never really clicked. At the time I was far more comfortable in Perl, so I decided to see how far I could get using that instead.
Background: why this started
At the time, I was working on integrating our student information system (Magister) with Microsoft Teams.
A previous implementation used Microsoft SDS as an interface layer. That turned out to be unreliable, and upcoming changes made it unclear whether it would continue to work.
That was the point where I decided to remove SDS from the equation and talk to Microsoft Graph directly.
“How hard could it be?”
Starting with curl
My first approach was the simplest one I knew: using
curl from the command line.
That turned out to be a useful learning phase, especially when dealing with OAuth2 and token acquisition.
I described that part earlier in more detail (in Dutch):
👉 https://mysite.prjv.nl/article/oauth2_authenticatie_met_curl
Understanding that flow was essential before moving to something more structured.
Moving to Perl (and LWP)
Initially I tried calling curl from within Perl, but that quickly felt wrong.
If I was going to do this in Perl, I wanted it to be native.
That led me to LWP, a well-established Perl library for HTTP requests. It provided a cleaner and more maintainable approach.
An ugly but useful first step
My first Perl implementation was essentially a direct translation of my earlier curl-based script.
It worked — but it wasn’t pretty.
It was a linear script that:
- requested an access token
- used that token to call a Graph endpoint
- dumped the result
Something like this (simplified, but very close to the original):
my $ua = LWP::UserAgent->new;
# request access token
my $token_request = HTTP::Request->new(
POST => $token_url,
[
'Content-Type' => 'application/x-www-form-urlencoded'
],
"grant_type=client_credentials" .
"&client_id=$app_id" .
"&client_secret=$app_secret" .
"&scope=$graph_endpoint/.default"
);
my $token_result = $ua->request($token_request);
my $access_token = decode_json($token_result->decoded_content)->{'access_token'};
# call Graph API
my $api_request = HTTP::Request->new(
GET => "$graph_endpoint/v1.0/groups",
[
'Authorization' => "Bearer $access_token"
],
);
my $api_result = $ua->request($api_request);
print $api_result->decoded_content;From scripts to something maintainable
At that point I had multiple scripts doing similar things:
- fetching data from Graph
- handling authentication
- parsing responses
Maintaining that quickly became a problem.
So instead of adding more scripts, I moved the logic into reusable modules.
That turned:
- one-off scripts
→ into
- a structured system with clear responsibilities
Structuring things with Moose
I used Moose to create a small object model:
MsGraph→ authentication and token handling
MsGroups→ group-related operations
MsUser→ user-related operations
The base module (MsGraph) provides the foundation. Other
modules build on top of it and implement actual Graph operations.
Real-world use cases
These modules were created to solve concrete problems:
- Mapping
samAccountNametouserPrincipalNameduring an LDAP → Azure AD transition
- Investigating group ownership inconsistencies
- Replacing LDAP-based scripts with Graph-based equivalents
Some parts are therefore quite focused rather than fully generic.
From modules to a provisioning system
Over time, these modules grew into the foundation of a larger EduTeams provisioning system.
It handled tasks like:
- group lifecycle management
- membership synchronization
- Teams-related operations
What changed later
This Perl-based system has since been replaced by a HelloID-based implementation using PowerShell.
That made sense from an operational and integration perspective.
Why this code still matters
Even though it’s no longer the active implementation, I still use this codebase:
- as a reference for new PowerShell functionality
- for testing Graph behavior
- for ad-hoc scripting
The underlying logic hasn’t changed — only the implementation language.
Source and reference implementation
The code described here is available on GitHub:
The repository reflects the evolution from proof-of-concept scripts to a more structured module-based approach.
It is no longer the active implementation, but it remains a useful reference for understanding how the system was built.
Final thoughts
This wasn’t about proving that Perl is the best tool for Microsoft Graph.
It was simply the most effective way for me to get things working at the time.
The real value was in the process:
- understanding OAuth2 flows
- learning how the Graph API behaves
- structuring logic into predictable building blocks
That knowledge carried over directly into later implementations.
And that’s probably the real takeaway.