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
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
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.
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