Skip to content

Commit 7935b14

Browse files
authored
Merge pull request #915 from divya-akula/GetGraphUsers
Get graph users , retrieves unmasked users
2 parents 68c199f + fac50cf commit 7935b14

6 files changed

Lines changed: 388 additions & 1 deletion

File tree

scripts/graph-connect-to-graph/assets/sample.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@
5555
}
5656
]
5757
}
58-
]
58+
]
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
# Export Entra ID User MFA Phone Details (Unmasked) to CSV
2+
3+
This PowerShell script enumerates users in Microsoft Entra ID (formerly Azure AD) and retrieves their **Multi-Factor Authentication (MFA) / phone authentication methods** using Microsoft Graph.
4+
Unlike standard user directory properties that return **masked** phone numbers, this script queries the **Authentication Phone Methods API**, which returns the **actual configured MFA phone number**, and exports the data to CSV.
5+
6+
This is useful for administrators who need to reconcile MFA phone numbers, validate ownership, or support audit and migration activities.
7+
8+
> ⚠️ **Security Notice**
9+
> MFA phone numbers are sensitive data. Ensure correct governance, storage protection, and access controls when running or distributing this script.
10+
11+
12+
## ✨ Features
13+
14+
- Retrieves Microsoft Entra ID users
15+
- Calls the Graph `authentication/phoneMethods` endpoint to return **unmasked MFA phone numbers**
16+
- Cleans and normalizes phone numbers (digits-only format included)
17+
- Supports **app-only authentication** using OAuth2 client credentials
18+
- Exports structured output to CSV containing:
19+
- `DisplayName`
20+
- `UserPrincipalName`
21+
- `LDAPUserName` (`onPremisesSamAccountName`)
22+
- `PhoneNumber`
23+
- `CleanedPhone` (digits only)
24+
25+
---
26+
27+
## 🔐 Required Microsoft Graph Permissions (Application / App-Only)
28+
29+
Create an App Registration and grant **admin-consented** Graph API permissions such as:
30+
31+
- `AuthenticationMethod.Read.All`
32+
**or**
33+
- `AuthenticationMethods.Read.All`
34+
- `User.Read.All`
35+
- `Directory.Read.All`
36+
37+
> Use least-privilege permissions and validate in a non-production tenant first.
38+
39+
## 🧰 Prerequisites
40+
41+
- **PowerShell 7+ (recommended)**
42+
- Install required modules:
43+
44+
```powershell
45+
Install-Module -Name Microsoft.Graph -Scope CurrentUser -AllowClobber
46+
Install-Module -Name PnP.PowerShell -Scope CurrentUser
47+
```
48+
49+
- An Azure AD application with the required application permissions (granted by an administrator).
50+
- Suggested Graph application permissions (app-only) you'll likely need:
51+
- Authentication Methods / phone read permission (e.g., AuthenticationMethods.Read.All or AuthenticationMethods.ReadWrite.All) — grant admin consent.
52+
- User read permissions (User.Read.All) — grant admin consent.
53+
- Directory.Read.All - grant admin consent
54+
55+
Note: permission names sometimes vary between SDKs and portal UI; grant the minimal required app permissions and test in a non-production tenant first.
56+
57+
## ⚙️ Authentication & Security
58+
59+
The script securely prompts for the following values at runtime:
60+
61+
- **Tenant ID**
62+
- **Client ID**
63+
- **Client Secret**
64+
- **Output CSV path**
65+
66+
No credentials or secrets are stored in the script.
67+
68+
Authentication is performed using the **OAuth2 client credentials flow** against Microsoft Graph.
69+
70+
> 🔐 **Security Best Practice**
71+
> Use least-privileged access, store secrets securely, and rotate credentials regularly.
72+
73+
---
74+
75+
## ▶️ How It Works
76+
77+
1. Authenticates to Microsoft Graph using the App Registration
78+
2. Retrieves users via:
79+
80+
```powershell
81+
Get-MgUser
82+
```
83+
3. Queries each user’s authentication phone methods using:
84+
https://graph.microsoft.com/v1.0/users/{id}/authentication/phoneMethods
85+
4. Extracts the configured MFA phone number(s)
86+
87+
5. Normalizes phone values (including a digits-only variant)
88+
89+
6. Exports the results to CSV
90+
91+
---
92+
93+
## 📄 Output Columns
94+
95+
| Column | Description |
96+
|--------|-------------|
97+
| **DisplayName** | User display name |
98+
| **UserPrincipalName** | Sign-in name |
99+
| **LDAPUserName** | `onPremisesSamAccountName` |
100+
| **PhoneNumber** | Full MFA phone number |
101+
| **CleanedPhone** | Digits-only version of the phone |
102+
103+
Additional properties such as `@odata.type` may be included if required.
104+
105+
---
106+
107+
## ▶️ Running the Script
108+
109+
Run the script in PowerShell:
110+
111+
```powershell
112+
.\Export-UserMfaPhoneDetails.ps1
113+
```
114+
## 🛠 Troubleshooting
115+
116+
- **No phone number returned**
117+
The user does not have any phone-based MFA method configured.
118+
119+
- **403 – Permission Denied**
120+
Verify that the App Registration has the required **admin-consented** Microsoft Graph permissions.
121+
122+
- **Rate limiting / throttling**
123+
For large tenants, implement retry logic with exponential back-off when calling Microsoft Graph.
124+
125+
- **Masked phone values appear**
126+
Ensure the script is querying the **Authentication Phone Methods API** and not user profile attributes.
127+
128+
---
129+
130+
## 🔄 Suggested Enhancements
131+
132+
- Replace raw REST calls with Microsoft Graph PowerShell SDK equivalents
133+
- Secure credentials using environment variables or Azure Key Vault
134+
- Add structured logging and verbose tracing
135+
- Introduce paging and server-side filtering to limit user scope
136+
- Implement retry and resilience policies for Graph calls
137+
- Optionally upsert results into a SharePoint List
138+
139+
---
140+
141+
## 🛡 Governance & Data Handling
142+
143+
Because this script retrieves **unmasked MFA phone numbers**, ensure that:
144+
145+
- Script execution is restricted to authorized administrators
146+
- Exported CSV files are encrypted and stored securely
147+
- Data retention policies are followed
148+
- Script usage and access are auditable
149+
150+
Treat MFA phone numbers as **confidential information**.
151+
152+
---
153+
154+
## 📝 Script
155+
156+
# [Microsoft Graph PowerShell](#tab/graphps)
157+
158+
```powershell
159+
<#
160+
.SYNOPSIS
161+
Export Entra ID user MFA phone details (unmasked) to CSV.
162+
163+
.DESCRIPTION
164+
Retrieves MFA phone authentication methods from Microsoft Graph
165+
using app-only authentication and exports the full MFA phone number
166+
along with user metadata to CSV.
167+
168+
Warning: Output contains sensitive authentication data.
169+
#>
170+
171+
# ================================
172+
# Get access token (interactive)
173+
# ================================
174+
function Get-UserToken {
175+
# Read from user
176+
$TenantId = Read-Host "Enter Tenant Id (Directory Id / GUID)"
177+
$ClientId = Read-Host "Enter Client Id (App Registration Id)"
178+
$SecSecret = Read-Host "Enter Client Secret"
179+
180+
# Convert secure string to plain text for token request
181+
$Body = @{
182+
grant_type = "client_credentials"
183+
scope = "https://graph.microsoft.com/.default"
184+
client_id = $ClientId
185+
client_secret = $SecSecret
186+
}
187+
188+
try {
189+
$TokenResponse = Invoke-RestMethod -Method Post `
190+
-Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" `
191+
-Body $Body -ErrorAction Stop
192+
193+
$AccessToken = $TokenResponse.access_token
194+
Write-Host "Access Token Obtained Successfully" -ForegroundColor Green
195+
return $AccessToken
196+
}
197+
catch {
198+
Write-Error "Failed to obtain access token: $($_.Exception.Message)"
199+
throw
200+
}
201+
}
202+
203+
# ================================
204+
# Export MFA phone details to CSV
205+
# ================================
206+
function Export-UserMfaPhoneDetailsToCsv {
207+
param (
208+
[Parameter(Mandatory)]
209+
[string]$AccessToken,
210+
211+
[Parameter(Mandatory)]
212+
[string]$OutputPath
213+
)
214+
215+
$headers = @{
216+
Authorization = "Bearer $AccessToken"
217+
'Content-Type' = 'application/json'
218+
}
219+
220+
# Helper: clean phone (digits only)
221+
function CleanPhone {
222+
param([string]$p)
223+
if ([string]::IsNullOrWhiteSpace($p)) { return '' }
224+
return ($p.ToCharArray() | Where-Object { [char]::IsDigit($_) }) -join ''
225+
}
226+
227+
# Get all users (UPN starts with 'T')
228+
$users = Get-MgUser -All -Property "displayName,userPrincipalName,onPremisesSamAccountName,Id"
229+
Write-Host "Retrieved $($users.Count) users from Graph" -ForegroundColor Cyan
230+
231+
$results = @()
232+
233+
foreach ($u in $users) {
234+
$userId = $u.id
235+
$displayName = $u.displayName
236+
$upn = $u.userPrincipalName
237+
$ldapUser = $u.onPremisesSamAccountName # This is your LDAP username
238+
239+
$pmUrl = "https://graph.microsoft.com/v1.0/users/$([uri]::EscapeDataString($userId))/authentication/phoneMethods"
240+
try {
241+
$pm = Invoke-RestMethod -Headers $headers -Uri $pmUrl -Method Get -ErrorAction Stop
242+
}
243+
catch {
244+
Write-Verbose "Failed phoneMethods for $upn : $($_.Exception.Message)"
245+
continue
246+
}
247+
# Write-Host $pm.value
248+
if (-not $pm.value) {
249+
# No phone methods: still record the user if you want them in the CSV
250+
$results += [PSCustomObject]@{
251+
DisplayName = $displayName
252+
UserPrincipalName = $upn
253+
LDAPUserName = $ldapUser
254+
PhoneNumber = $null
255+
CleanedPhone = $null
256+
}
257+
continue
258+
}
259+
260+
foreach ($m in $pm.value) {
261+
$phone = $m.phoneNumber
262+
$cleaned = CleanPhone $phone
263+
264+
$results += [PSCustomObject]@{
265+
DisplayName = $displayName
266+
UserPrincipalName = $upn
267+
LDAPUserName = $ldapUser
268+
PhoneNumber = $phone
269+
CleanedPhone = $cleaned
270+
}
271+
}
272+
}
273+
274+
# Write to CSV
275+
$results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
276+
Write-Host $results.Count
277+
Write-Host "Exported $($results.Count) rows to $OutputPath" -ForegroundColor Green
278+
}
279+
280+
# ================================
281+
# Main
282+
# ================================
283+
$token = Get-UserToken
284+
285+
$outputPath = Read-Host "Enter full path for CSV output (e.g. C:\Temp\MfaUserData.csv)"
286+
287+
Export-UserMfaPhoneDetailsToCsv -AccessToken $token -OutputPath $outputPath
288+
```
289+
290+
[!INCLUDE [More about Microsoft Graph PowerShell SDK](../../docfx/includes/MORE-GRAPHSDK.md)]
291+
***
292+
293+
## Contributors
294+
295+
| Author(s) |
296+
|-----------|
297+
| [Divya Akula](https://github.com/divya-akula)|
298+
299+
[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]
300+
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/graph-get-mfa-user-number" aria-hidden="true" />
396 KB
Loading
400 KB
Loading
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
DisplayName,UserPrincipalName,LDAPUserName,PhoneNumber,CleanedPhone
2+
Adele Vance,AdeleV@5kswv1.onmicrosoft.com,,,
3+
Divya Akula,akula.divya_gmail.com#EXT#@5kswv1.onmicrosoft.com,,,
4+
Alex Wilber,AlexW@5kswv1.onmicrosoft.com,,,
5+
Diego Siciliani,DiegoS@5kswv1.onmicrosoft.com,,,
6+
Dinesh Akula,DineshAkula@5kswv1.onmicrosoft.com,,,
7+
Divya Akula,divya.akula_tarento.com#EXT#@5kswv1.onmicrosoft.com,,,
8+
Divya Akula,divya@5kswv1.onmicrosoft.com,,+91 99999999,919999999999
9+
Divya Akula,divyaakula_m365devtoy.onmicrosoft.com#EXT#@5kswv1.onmicrosoft.com,,,
10+
Grady Archie,GradyA@5kswv1.onmicrosoft.com,,,
11+
Hajira Begum,hajira.begum_tarento.com#EXT#@5kswv1.onmicrosoft.com,,,
12+
Hajira,Hajira@5kswv1.onmicrosoft.com,,,
13+
Henrietta Mueller,HenriettaM@5kswv1.onmicrosoft.com,,,
14+
Isaiah Langer,IsaiahL@5kswv1.onmicrosoft.com,,,
15+
Johanna Lorenz,JohannaL@5kswv1.onmicrosoft.com,,,
16+
Joni Sherman,JoniS@5kswv1.onmicrosoft.com,,,
17+
Lee Gu,LeeG@5kswv1.onmicrosoft.com,,,
18+
Lidia Holloway,LidiaH@5kswv1.onmicrosoft.com,,,
19+
Lynne Robbins,LynneR@5kswv1.onmicrosoft.com,,,
20+
Megan Bowen,MeganB@5kswv1.onmicrosoft.com,,,
21+
Miriam Graham,MiriamG@5kswv1.onmicrosoft.com,,,
22+
Nestor Wilke,NestorW@5kswv1.onmicrosoft.com,,,
23+
Patti Fernandez,PattiF@5kswv1.onmicrosoft.com,,,
24+
User,powerplatform_devtarento.onmicrosoft.com#EXT#@5kswv1.onmicrosoft.com,,,
25+
Pradeep Gupta,PradeepG@5kswv1.onmicrosoft.com,,,
26+
test user,testuer@5kswv1.onmicrosoft.com,,,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
[
2+
{
3+
"name": "graph-get-mfa-user-number",
4+
"source": "pnp",
5+
"title": "Export Entra ID User MFA Phone Details (Unmasked) to CSV",
6+
"shortDescription": "This PowerShell script enumerates users in Microsoft Entra ID (formerly Azure AD) and retrieves their **Multi-Factor Authentication (MFA) / phone authentication methods** using Microsoft Graph.",
7+
"url": "https://pnp.github.io/script-samples/graph-get-mfa-user-number/README.html",
8+
"longDescription": [
9+
""
10+
],
11+
"creationDateTime": "2026-01-10",
12+
"updateDateTime": "2026-01-10",
13+
"products": [
14+
"Graph"
15+
],
16+
"metadata": [
17+
{
18+
"key": "PNP-POWERSHELL",
19+
"value": "1.11.0"
20+
},
21+
{
22+
"key": "GRAPH-POWERSHELL",
23+
"value": "1.0.0"
24+
}
25+
],
26+
"categories": [
27+
"Report"
28+
],
29+
"tags": [
30+
"<Cmdlets-Used>"
31+
],
32+
"thumbnails": [
33+
{
34+
"type": "image",
35+
"order": 100,
36+
"url": "https://raw.githubusercontent.com/pnp/script-samples/main/scripts/graph-get-mfa-user-number/assets/preview.png",
37+
"alt": "Preview of the sample Export Entra ID User MFA Phone Details (Unmasked) to CSV"
38+
}
39+
],
40+
"authors": [
41+
{
42+
"gitHubAccount": "divya-akula",
43+
"company": "",
44+
"pictureUrl": "https://github.com/divya-akula.png",
45+
"name": "Divya Akula"
46+
}
47+
],
48+
"references": [
49+
{
50+
"name": "Want to learn more about PnP PowerShell and the cmdlets",
51+
"description": "Check out the PnP PowerShell site to get started and for the reference to the cmdlets.",
52+
"url": "https://aka.ms/pnp/powershell"
53+
},
54+
{
55+
"name": "Want to learn more about Microsoft Graph PowerShell SDK and the cmdlets",
56+
"description": "Check out the Microsoft Graph PowerShell SDK documentation site to get started and for the reference to the cmdlets.",
57+
"url": "https://learn.microsoft.com/graph/powershell/get-started"
58+
}
59+
]
60+
}
61+
]

0 commit comments

Comments
 (0)