Microsoft Graph PowerShell Cheatsheet

Rich
5 min readMar 20, 2023

--

TL;DR MSOL & the Azure AD module are being deprecated, start using Microsoft Graph.

Welcome to Part VI of our Cheatsheet Series!

Part I: Mimikatz cheatsheet

Part II: Set-Acl cheatsheet

Part III: Get-Acl cheatsheet

Part IV: Enumerating AD cheatsheet

Part V: Windows reverse shells cheatsheet

Part VI: MS Graph PowerShell cheatsheet

Part VII: Hash cracking cheatsheet

Part VIII: The Credential Theft Shuffle

Part IX: SACLs & Querying Collected Logs

Part X: Setting up a simple AD lab in Azure

Background

In the past we have used a couple random queries while setting up hybrid AD. The problem is that we were using the MSOL and Azure AD modules. These are getting deprecated soon, possibly around June 2023. Hence it was time to start getting comfortable with Microsoft Graph.

We create users, groups, and devices ‘on prem’ in AD and then sync them to AAD using Azure AD Connect and Azure AD Cloud Sync. Hence this cheatsheet is about querying, not creating things.

Register MS Graph in AAD

The first thing one has to do is create an app registration in the AAD portal for MicrosoftGraph. This is a one off and there’s guides like this one, so we will move on to the cheatsheet.

Connecting to MS Graph

If you run PowerShell as an Administrator then you can simply

Install-Module Microsoft.Graph -Scope AllUsers
Connect-MgGraph -Scopes "Directory.ReadWrite.All", "User.ReadWrite.All", "Group.ReadWrite.All", "GroupMember.Read.All, AuditLog.Read.All"

Everything we go over below will work fine with these parameters.

User Stuff

#Show all users [kinda like Get-ADUser –Filter *]
Get-MgUser

#Find a user based on an attribute
Get-MgUser | Where-Object {$_.Mail -like "*@gmail.com"}

#Update a user's info
Update-MgUser -UserId (Get-MgUser -Filter "DisplayName eq '<firstname> <lastname> ADM'").Id -BusinessPhones "(123) 456–7890" -MobilePhone "(123) 456-7890"

#Confirm
Get-MgUser -Filter "DisplayName eq '<firstname> <lastname> ADM'" | Select-Object BusinessPhones, MobilePhone

#Show groups the user is in (only shows group ID)
(Get-MgUser -Filter "DisplayName eq 'Twinsey'" -ExpandProperty MemberOf).MemberOf
Screenshots redacted IOT omit Tiny Human’s names & Ids

Bear in mind that Microsoft Graph and AAD use the Id attribute rather like AD uses the SamAccountName. For example ‘Get-ADUser mishka’ works as SamAccountName is the default.

Microsoft Graph however requires one to specify, for example

Get-MgUser -UserId <Id>

Additionally MS Graph only returns Ids for groups that the user is in, which aren’t easily recognizable like SamAccountNames tend to be. We will show an easy way to make this output much more legible shortly.

Group Stuff

#Group stuff [kinda like Get-ADGroup]
#Show all properties of a group (doesn't work well for showing members though)
Get-MgGroup -Filter "DisplayName eq 'SSPR'"

#or
Get-MgGroup | Where-Object {$_.DisplayName -like "*SSPR*"}

#Show group members
Get-MgGroup -Filter "DisplayName eq 'SSPR'" -ExpandProperty Members | Select-Object Members

Much like querying the groups that a given user is in, querying the users in a given group only shows the Id. Luckily there’s a solution to this.

Getting readable output

Simply save the below and import the PS1. One can then

Get-AADPrincipalGroupMembership "<firstname> <lastname>"

Or if one only wants to see the group names

(Get-AADPrincipalGroupMembership "<firstname> <lastname>").DisplayName
Screenshots redacted IOT omit Tiny Human’s names & Ids

One can query group membership the same way via ‘Get-AADGroupMember’. I am used to AD’s commands so I simply tweaked them for AAD.

Import-Module Microsoft.Graph
Connect-MgGraph -Scopes "Directory.ReadWrite.All", "User.ReadWrite.All", "Group.ReadWrite.All", "GroupMember.Read.All"

Function Get-AADPrincipalGroupMembership {
param (
[parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]$User
)
Try {
$Groups = (Get-MgUserMemberOf -UserId (Get-MgUser | Where-Object {$_.DisplayName -eq "$User"}).Id).Id
ForEach ($Group in $Groups)
{
Get-MgGroup -GroupId $Group -ExpandProperty Members
}}
Catch {Write-Host "User not found. These are you options:"; Get-MgUser}
}

Function Get-AADGroupMember {
param (
[parameter(Mandatory=$True)]
[ValidateNotNullOrEmpty()]$Group
)
Try {
$Members = (Get-MgGroupMember -GroupId (Get-MgGroup | Where-Object {$_.DisplayName -eq "$Group"}).Id).Id
ForEach ($Member in $Members)
{
Get-MgUser -UserId $Member -ExpandProperty MemberOf
}}
Catch {Write-Host "Group not found. These are you options:"; Get-MgGroup}
}

Other

#Show devices [kinda like Get-ADComputer]
Get-MgDevice | Select-Object DisplayName, Id, DeviceId, OperatingSystem, OperatingSystemVersion

#Show license info
Get-MgUserLicenseDetail -UserId <Id> | Select-Object *

One can also query via the web API, just in case there something that the PowerShell module doesn’t support yet.

(Invoke-MgGraphRequest -Method GET https://graph.microsoft.com/v1.0/groups).Value | Where-Object {$_.DisplayName -like "*SSPR*"}

One can substitute ‘users’ or ‘devices’ for ‘groups’ on the end of the URL to query those. There is a much more comprehensive guide to the Rest API here.

Sidenote on AD vs AAD attributes

There’s some attributes in AD that aren’t in AAD. This can complicate some administrative and auditing tasks such as finding stale users. In AD this is as simple as one can find inactive users in a given OU who are still enabled via

Get-ADUser -Filter {LastLogonTimeStamp -lt $time -and Enabled -eq “True”} -SearchBase “<given OU path>” -Properties * | Select-Object Name, EmployeeID, LastLogonDate, CreateTimeStamp, Enabled | Export-Csv ‘.\Inactive Users.csv’

We can of course set the variable $time to whatever policy states as the timeframe to start disabling inactive users, 90 days for example.

$time = (Get-Date).Adddays(-90)

However as AAD does not have an attribute for the user’s last logon we can’t query it via a Get-MgUser filter. We can however check the audit logs for a given user’s last sign in. If we need to check all users we can simply use a ForEach loop.

# Sets the search field to 90 days ago
$OldDate = (Get-Date).AddDays(-90)

$Users = (Get-MgUser).UserPrincipalName
ForEach ($User in $Users)
{
$LastLogon = (Get-MgAuditLogSignIn -Property * | Where-Object {$_.UserPrincipalName -like "*$User*"}).CreatedDateTime | Select-Object -First 1
If ($LastLogon -le $OldDate)
{
#Write-Host "$User has not logged in since $LastLogon"
$User | Export-Csv "C:\Temp\StaleUsers.csv" -Append
}
}

Note that we don’t have to use the UPN, AAD also stores the UserDisplayName and UserId so we can filter on those. I just tend to use the UPN as we utilize it to sync hybrid AD.

Screenshots redacted IOT omit Tiny Human’s names & Ids

Summary

This cheatsheet is a work in progress and mostly a stub at this point. I’ll add to it as I go. We are still in the very early steps of working with hybrid AD. As mentioned earlier we create users, groups, and devices ‘on prem’ in AD and then sync them to AAD using Azure AD Connect and Azure AD Cloud Sync. Hence this cheatsheet is about querying, not creating things.

References

Register MS Graph: https://support.newoldstamp.com/en/articles/5967487-how-to-register-the-microsoft-graph-app-on-microsoft-azure-active-directory

Azure AD Graph is being deprecated Jun 2023: https://learn.microsoft.com/en-us/graph/migrate-azure-ad-graph-overview

MSOL is also being deprecated: https://techcommunity.microsoft.com/t5/microsoft-365/microsoft-sets-new-deprecation-schedule-for-azure-ad-powershell/m-p/3259810

Installing Microsoft Graph PowerShell module: https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation?view=graph-powershell-1.0

Microsoft.Graph.Users: https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users/?view=graph-powershell-1.0

Microsoft Graph Rest API: https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0

--

--

Rich

I work various IT jobs & like Windows domain security as a hobby. Most of what’s here is my notes from auditing or the lab.