Technology Solutions for Everyday Folks

A Basic Graph API Powershell Module

During a session planning conversation for MMS Flamingo last fall I had the idea to create a basic Powershell module as an illustration/starting point to using direct HTTP requests with Invoke-RestMethod against Graph API. The concept was simple: here's a couple of lines of code to Do Something in Graph, with direct HTTP requests, and be a launch point for quickly exploring and troubleshooting calls to other Graph endpoints without a lot of overhead or abstraction.

One thing it was not intended to be: the module isn't for production scenarios — just a learning and discovery platform. To maintain simplicity, there's no error handling involved, it's only configured for Application permissions, and it doesn't handle things like pagination, bearer token management, or other things that are often managed for you with proper tooling such as the official SDKs and modules.

The Module Basics

I've had the module publicly sitting over on GitHub since MMS Flamingo, and it has a full README explaining the few bits necessary to configure in the application setup. For the purposes of running the example script it includes hard-coding a few IDs for Group, User, and Device to obtain, or in one case update, information. It's implied that the user is already familiar with configuring the app registration in the Entra portal, the basics of which I covered in the previous post.

What Does The Module Provide?

Ultimately, the basic module provides three key functions:

  1. Obtaining a bearer token from provided credentials: a client ID and client secret;
  2. Using a small number of direct HTTP read-based requests with Invoke-RestMethod to illustrate default Graph requests, some with a $select query parameter, some with a $filter parameter, and some with both parameters to obtain data for devices, device groups, device users, and bitlocker keys; and
  3. A basic example of updating a JSON payload to update a device's extension attributes.

That's it. That's the module! Between those functions many of the common things folks need to know or fiddle with are illustrated in a basic manner. Because again the intent was to illustrate and simplify using Graph API, not be a fully-baked "take it and roll in production" piece of software.

Module Functions

This basic module exposes the following functions that I hope are easy to understand and follow:

Get-BearerToken

Get-BearerToken is the Invoke-RestMethod version of what I wrote several months ago: a basic OAuth request. That post used cURL for the HTTP calls, but with a little modification Invoke-RestMethod does the same thing. Intended to be used in code/example.

Module Example:

$bearerToken = Get-BearerToken -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret

This function has been provided as part of the module since you'll need a $bearerToken for all of the other calls within.

Important to note: Rolling your own authentication comes with risk, so while this method works great for testing or in your lab environment, be aware that there are other ways to more thoroughly handle authentication for your production stuff so you don't wind up causing a Resume-Generating Event (RGE).

Get-AllDevicesAllData

Get-AllDevicesAllData does just that: It will obtain and output all default data points for all devices in your tenant. There's no filtering or selection; just everything. Useful to quickly see a list of devices.

Module Example:

Get-AllDevicesAllData -bearerToken $bearerToken

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/devices?$select=id,deviceId,displayName,manufacturer,model

Module Output (truncated):

{
    "@odata.context":  "https://graph.microsoft.com/v1.0/$metadata#devices",
    "value":  [
                  {
                      "id":  "id-guid-is-here",
                      "deletedDateTime":  null,
                      "accountEnabled":  true,
                      "approximateLastSignInDateTime":  null,
                      "complianceExpirationDateTime":  null,
                      "displayName":  "GIT-DEV-IT-02",
                      
                      ...
                      
                      "alternativeSecurityIds":  ""
                  },
                  
                  ...
                  
              ]
}

Get-AllDevicesSelectData

Get-AllDevicesSelectData obtains a selected list of data points for all devices in your tenant using the $select query parameter to limit the number of fields returned. Useful to obtain a predefined set of data points for all devices.

Module Example:

Get-AllDevicesSelectData -bearerToken $bearerToken

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/devices?$select=id,deviceId,displayName,manufacturer,model

Module Output (truncated):

{
    "@odata.context":  "https://graph.microsoft.com/v1.0/$metadata#devices(id,deviceId,displayName,manufacturer,model)",
    "value":  [
                  {
                      "id":  "id-guid-is-here",
                      "deviceId":  "deviceId-guid-is-here",
                      "displayName":  "GIT-DEV-IT-02",
                      "manufacturer":  "Microsoft Corporation",
                      "model":  "Virtual Machine"
                  },
                  
                  ...
                  
              ]
}

Get-AllSecurityGroups

Get-AllSecurityGroups obtains a list of all "security" groups in your tenant using the $select query parameter to limit the number of fields returned and the $filter query parameter to only return securityEnabled groups. Useful to quickly see a list of groups.

Module Example:

Get-AllSecurityGroups -bearerToken $bearerToken

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/groups?$select=id,displayName,securityEnabled,groupTypes,createdDateTime&$filter=(securityEnabled eq true)

Module Output (truncated):

{
    "@odata.context":  "https://graph.microsoft.com/v1.0/$metadata#groups(id,displayName,securityEnabled,groupTypes,createdDateTime)",
    "value":  [
                  {
                      "id":  "group-id-guid-is-here",
                      "displayName":  "BrainStorm Demo Devices",
                      "securityEnabled":  true,
                      "groupTypes":  "",
                      "createdDateTime":  "2025-03-07T01:48:00Z"
                  },
                  
                  ...
                  
              ]
}

Get-GroupMembers

Get-GroupMembers obtains a list of the members of the provided $groupId in your tenant and uses the $select query parameter to limit the fields returned.

Module Example:

Get-GroupMembers -bearerToken $bearerToken -groupId $groupId

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/groups/$groupId/transitiveMembers?`$select=id,displayName,deviceId

Module Output (truncated):

{
    "@odata.context":  "https://graph.microsoft.com/v1.0/$metadata#directoryObjects(id,displayName,deviceId)",
    "value":  [
                  {
                      "@odata.type":  "#microsoft.graph.device",
                      "id":  "id-guid-is-here",
                      "displayName":  "GIT-DEV-IT-01",
                      "deviceId":  "deviceId-guid-is-here"
                  },
                  
                  ...
                  
              ]
}

Get-AllUsersSelectData

Get-AllUsersSelectData obtains a selected list of data points for all users in your tenant using the $select query parameter to limit the number of fields returned. Useful to obtain a predefined set of data points for all users.

Module Example:

Get-AllUsersSelectData -bearerToken $bearerToken

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/users?$select=id,displayName,mail,userPrincipalName

Module Output (truncated):

{
    "@odata.context":  "https://graph.microsoft.com/v1.0/$metadata#users(id,displayName,mail,userPrincipalName)",
    "value":  [
                  {
                      "id":  "id-guid-is-here",
                      "displayName":  "Adele Vance",
                      "mail":  "AdeleV@domain-name-here.com",
                      "userPrincipalName":  "AdeleV@domain-name-here.com"
                  },
                  
                  ...
                  
              ]
}

Get-DeviceGroupMemberships

Get-DeviceGroupMemberships obtains and displays a list of group names for which a given $deviceId is a member by using the $select query parameter. Useful to quickly see a list of group memberships for a device.

Module Example:

Get-DeviceGroupMemberships -bearerToken $bearerToken -deviceId $deviceId

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/devices/$deviceId/memberOf/$/microsoft.graph.group?`$select=displayName

Module Output:

{
    "@odata.context":  "https://graph.microsoft.com/v1.0/$metadata#groups(displayName)",
    "value":  [
                  {
                      "displayName":  "MMS Demo Devices"
                  },
                  {
                      "displayName":  "BrainStorm Demo Devices"
                  }
              ]
}

Get-DevicesOwnedByUser

Get-DevicesOwnedByUser obtains and displays a list of device names for which a given $userId is a owner by using the $select query parameter. Useful to quickly see a list of devices owned by a given user.

Module Example:

Get-DevicesOwnedByUser -bearerToken $bearerToken -userId $userId

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/users/$userId/ownedDevices?`$select=displayName

Module Output:

{
    "@odata.context":  "https://graph.microsoft.com/v1.0/$metadata#devices",
    "value":  [
                  {
                      "@odata.type":  "#microsoft.graph.device",
                      "displayName":  "GIT-DEV-IT-01"
                  },
                  {
                      "@odata.type":  "#microsoft.graph.device",
                      "displayName":  "GIT-DEV-IT-02"
                  }
              ]
}

Get-DeviceDetails

Get-DeviceDetails obtains all default data points for a given $deviceId. There's no filtering or selection; just everything. Useful to quickly see a list of all data points of a given device.

Module Example:

Get-DeviceDetails -bearerToken $bearerToken -deviceId $deviceId

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/devices/$deviceId

Module Output (truncated):

{
    "@odata.context":  "https://graph.microsoft.com/v1.0/$metadata#devices/$entity",
    "id":  "id-guid-is-here",
    "deletedDateTime":  null,
    "accountEnabled":  true,
    "approximateLastSignInDateTime":  null,
    "complianceExpirationDateTime":  null,
    "displayName":  "GIT-DEV-IT-02",
    
    ...
    
}

Update-DeviceDetails

Update-DeviceDetails will update the extensionAttribute1 field for the given $deviceId. It uses a simple JSON payload and the HTTP PATCH method:

$deviceUpdateData = '{
  "extensionAttributes": {
      "extensionAttribute1": "This is an awesome object now"
  }
}'

Module Example:

Update-DeviceDetails -bearerToken $bearerToken -deviceId $deviceId -updateJson $deviceUpdateData

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/devices/$deviceId

The module output will be empty if this was successful. To see your changes, you would re-run the Get-DeviceDetails cmdlet with the same $deviceId to see the change in extension attributes.

Get-DeviceBitlockerRecoveryKey

Get-DeviceBitlockerRecoveryKey obtain and display all BitLocker recovery keys for a given $deviceId. Under the hood this is a multi-step process (two functions in the module are used, but only one function is exposed outside the module) due to how keys are managed and stored in Entra/Intune. Useful to see how commands can be stacked and combined in the moment to do something more complex.

Module Example:

Get-DeviceBitlockerRecoveryKey -bearerToken $bearerToken -deviceId $deviceId

Underlying REST Endpoint:

https://graph.microsoft.com/v1.0/informationProtection/bitlocker/recoveryKeys?`$filter=deviceId eq '$deviceId'

The exposed function uses of a second REST endpoint to obtain the recovery key in full for each $rKeyId (escrowed key):

https://graph.microsoft.com/v1.0/informationProtection/bitlocker/recoveryKeys/$rKeyId/?`$select=key

The module output at the command line will be a proper BitLocker recovery key (or one per line) if one exists.

Module Release Outcomes

I've had a number of folks reach out and express how useful this little module has been to help them wrap their heads around using Graph calls, both directly with HTTP requests, but also as a way they were able to clarify what they're looking to do with the official modules (pivoting to the equivalent cmdlets). When combined with awesome tools like Graph X-Ray, Graph Explorer, Postman, and Merill's Graph Permissions Explorer, cutting to the basics with a module like mine has proven useful in folks' distillations as they work through developing their own automations or mechanizations using Graph API. And that's outstanding — exactly the sort of outcome I was hoping for!

So go forward, give Graph API a try, and see if little tooling bits like this help you out! Good luck!