Skip to content

Instantly share code, notes, and snippets.

@mbrenig
Last active February 18, 2025 13:21
Show Gist options
  • Save mbrenig/39e51f31ec60c0fbe78b5b1a0dfc7c40 to your computer and use it in GitHub Desktop.
Save mbrenig/39e51f31ec60c0fbe78b5b1a0dfc7c40 to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S uv run
# /// script
# dependencies = [
# "azure-identity==1.20.0",
# "requests==2.32.3",
# ]
# requires-python = ">=3.9"
# ///
# Quick member list script for Azure AD groups using Azure Identity and raw HTTP requests.
#
# Usage:
# 1) Open Azure CLI
# 2) Run `pip install uv`
# 3) Run the script with the group's object ID, e.g.:
#
# uv run https://gist.githubusercontent.com/mbrenig/39e51f31ec60c0fbe78b5b1a0dfc7c40/raw/ced864a1326d3ad3a98839e15136a1090ea350b1/listmembers.py <group_id>
#
from azure.identity import DefaultAzureCredential
import requests
import argparse
GRAPH_ENDPOINT = "https://graph.microsoft.com/v1.0"
def parse_arguments():
parser = argparse.ArgumentParser(description="Retrieve members of an Azure AD group.")
parser.add_argument("group_id", help="The ID of the Azure AD group.")
return parser.parse_args()
def get_group_members(group_id: str, access_token: str):
"""
Retrieve all members of a Security Group using raw HTTP requests with a Bearer token.
Manually handle pagination using @odata.nextLink.
"""
members = []
url = f"{GRAPH_ENDPOINT}/groups/{group_id}/members?$select=displayName,userPrincipalName,onPremisesExtensionAttributes"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/json"
}
while url:
response = requests.get(url, headers=headers)
response.raise_for_status() # Raise HTTPError if the request failed
data = response.json()
members_page = data.get("value", [])
members.extend(members_page)
# Check if we have a nextLink for more pages
next_link = data.get("@odata.nextLink")
url = next_link if next_link else None
return members
def main():
args = parse_arguments()
# 1) Acquire a token from your existing Azure CLI login
credential = DefaultAzureCredential()
# The Graph permission scope is "https://graph.microsoft.com/.default"
token_object = credential.get_token("https://graph.microsoft.com/.default")
access_token = token_object.token
# 2) Retrieve the group members
members = get_group_members(args.group_id, access_token)
# 3) Print results
print(f"Found {len(members)} members in group {args.group_id}:")
for ix, member in enumerate(members, start=1):
type = member.get("@odata.type", "")
display_name = member.get("displayName", "No DisplayName")
upn = member.get("userPrincipalName", "")
onprem = member.get("onPremisesExtensionAttributes", {})
ea12 = onprem.get("extensionAttribute11", "")
ea11 = onprem.get("extensionAttribute11", "")
ea10 = onprem.get("extensionAttribute10", "")
print(f"{ix},{display_name},{upn},{ea10},{ea11},{ea12},{type}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment