I’ve spent a lot of time using the Microsoft Graph for interacting with Azure Active Directory, Intune, and Windows Autopilot. Most of this has been done using PowerShell (scripting) or C# (programming). But as I got questions about using Python from my son (college classes) and others, I decided to spend a little of my summer vacation learning something new.
Since the easiest way to learn is through practical experience (for me at least), I thought that I’d try to use Python to interact with the Microsoft Graph. How hard can that be? There are multiple pieces to that:
- The language itself. The Python language is relatively straight-forward: It is case-sensitive and leverages indention for nested flows. It would be best described as an “interpreted programming language” (as compared to PowerShell, which is a case-insensitive scripting language).
- The extensibility. Your productivity is governed by how much you need to do yourself, versus what you can borrow/steal/use from others. In the Python world, you leverage modules (a single Python script file that defines functions or classes that you can use) or packages (a collection of modules bundled together). These are obtained using utilities like pip, which can pull them from a public repository – very convenient. (This is similar to the PowerShell Gallery or other NuGet repositories.)
- The specifics of what you want to do. For example, to talk to the Microsoft Graph, I need to be able to authenticate to Azure AD, call REST methods, and work with JSON, so I needed to know how to do each of those using Python.
So let’s walk through that step by step, starting from the beginning with installing Python itself. I installed that manually by downloading it from the main Python site. That installed both the x86 and x64 versions of Python, and added some entries to the PATH to point to them:
Notice that it installed per user (in my user profile) too.
The next step is to authenticate with Azure AD. The Microsoft Authentication Library, a.k.a. MSAL, has a Python version available. So that can be installed from a command line using “pip install msal”. I installed that and then tried to use it and got an error that it wasn’t found when trying to import it. Interestingly, it was installed by default as a 32-bit module, not a 64-bit one. OK, so it seems the preference is to use 32-bit Python. I’m OK with that – seems like most people prefer the 32-bit version if they aren’t doing things that require lots of RAM.
To make running Python easier, I added an entry to the Windows Terminal configuration:
(The starting directory was something I added to make testing easier, more on that later. I also left out an icon, because it wasn’t necessary.) Once that entry is added and the file is saved, you can see it show up in the Terminal menu:
And then you can select it to start a Python session:
So, back to MSAL. I initially thought I would use a similar method to what I use with PowerShell: Call a “connect” method that prompts for credentials and authenticates to Microsoft Graph automatically. In the Python case, there is no UI provided by MSAL to do this. I could have used a username and password, but the MSAL docs frown on that. OK, so I looked at app-based authentication. If you read my previous blog on that, you’ll know that you need to do some work in Azure AD to create an app, then create an app secret (basically, a password) for that app that you can leverage.
Note that there is one additional API permission that needs to be added, beyond what was described in that blog. You also need to include “DeviceManagementManagedDevices.ReadWrite.All” in order to query the list of Autopilot devices. I suppose that makes sense, as each Autopilot device references an Intune device, but when I did that initial blog that wasn’t required.
Once you have that, logic like this will authenticate with Microsoft Graph:
You can pick up on some of the language details here too: Variables don’t need to be declared (unless you want to use global variables inside locally-scoped functions). Everything is case-sensitive. All blocks (including if/then/else) use indention to show the flow. Arrays are defined using brackets. Dictionaries (equivalent to hashtables in PowerShell) are defined using curly braces. Parameters can be passed to functions or constructors positionally (e.g. the appID value above) or referenced by name (e.g. authority=authority, where the first authority is the parameter name and the second is the variable name to be passed to that parameter – yes, I made that more confusing than it needed to be). Overall, pretty reasonable.
So if you were to run that (with your own tenant, app ID, and app secret details) you would get an Azure AD bearer token that can then be used for subsequent Microsoft Graph calls.
OK, so it’s then time to make a Graph call. After some trial and error, I ended up with this structure:
So I added a few more modules (installed via pip if they aren’t already present on your device):
- requests, used to make REST calls.
- json, used to handle JSON objects (e.g. the results from the REST calls).
- pandas, the Python Data Analysis Library used (in my particular case) for displaying tabular data (it can do lots more).
That enables me to do things like this:
Remember before when I mentioned the starting (working) directory? I specified that because the “pyAutopilot.py” module (which is just a single file) is located in that directory, so I can then do an “import pyAutopilot” without having to place the file in a module folder that is in the path. (Just a laziness item in this case.)
So I first called the authenticate method to get a bearer token, then called the devices method to get a list of all the Autopilot devices in my tenant. Those are then displayed in a table. If I wanted the JSON I could get that too:
Note that you could, if you granted sufficient rights to the app, query any Azure AD or Intune objects using the query function – nothing in this is specific to Autopilot. Here’s an example to get all the Azure AD users (which required granting User.ReadWrite.All, or really just User.Read.All in this case, to the app, then re-authenticating to get a new bearer token):
Now there are still lots of things that could be done in this Python module, including support for paging of results (I don’t have enough devices in my tenant to run into that), adding objects, modifying existing objects, proper error handling, etc. But that’s enough learning for the day.
I’ve attached the Python module if you are interested in trying it out or modifying it (provided as-is).