MySite

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 samAccountName to userPrincipalName during 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:

👉 MSGraph-Perl repository

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.