From 2570e0f8771b77b7792404dd1267368f6e96acfd Mon Sep 17 00:00:00 2001 From: Nisheet Jain Date: Fri, 9 Oct 2026 11:40:38 -0700 Subject: [PATCH 1/4] docs: add SMS cost estimation guide Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 749249f5-dd43-4595-896b-1c4b439ae048 --- README.md | 4 +- docs/COST-CALCULATOR.md | 149 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 docs/COST-CALCULATOR.md diff --git a/README.md b/README.md index 3376fb0..39bb29e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,9 @@ policy. Those steps remain part of your onboarding. approved tenant onboarding/test procedure. The sample does not establish eligibility, licensing, or preview enrollment. It is not production certification: it has no durable queue, automatic send retries, deduplication, whole-request deadline, or overlapping key rotation. Review the -[limitations](docs/CONTRACT.md#production-limitations) with your owners. +[limitations](docs/CONTRACT.md#production-limitations) with your owners. Use the +[SMS cost estimation guide](docs/COST-CALCULATOR.md) to create a planning estimate, then confirm +destination rates, fees, and final costs with your provider. ## Single-region architecture diff --git a/docs/COST-CALCULATOR.md b/docs/COST-CALCULATOR.md new file mode 100644 index 0000000..deebe84 --- /dev/null +++ b/docs/COST-CALCULATOR.md @@ -0,0 +1,149 @@ +# Estimate SMS provider costs + +Use this guide to create a planning estimate for SMS traffic before onboarding an +External Phone Provider (EPP). The report groups recent sign-in activity for users +who have a phone authentication method. Apply your provider's country or region +rates to the report to estimate a budget. + +**This is an estimate, not a usage or billing report.** A sign-in does not prove that +an SMS was sent, and one sign-in can result in no message or more than one message. +The sign-in location is based on the sign-in event and might not match the country +of the destination phone number. Confirm the assumptions, supported destinations, +message segmentation, taxes, fees, and final pricing with your provider. + +## Prerequisites + +Run the script in PowerShell 7 from a secured administrator workstation. The signed-in +account must be allowed to consent to or use these Microsoft Graph delegated permissions: + +- `User.Read.All` +- `UserAuthenticationMethod.Read.All` +- `AuditLog.Read.All` + +Access to sign-in logs also depends on your Microsoft Entra licensing, directory role, +and log-retention period. Review the requested permissions before consenting. The output +contains user principal names and sign-in location data; store, share, and delete it +according to your organization's privacy and retention requirements. + +Create `C:\temp` before running the script, or change `$outputPath` to an approved folder. + +## Generate the activity report + +The following sample installs any missing Microsoft Graph modules for the current user, +connects to Microsoft Graph, identifies users with a phone authentication method, and +groups up to 500 recent sign-ins per user by sign-in country or region. + +```powershell +# 1. Install missing modules silently +"Microsoft.Graph.Users", "Microsoft.Graph.Identity.SignIns", "Microsoft.Graph.Reports" | + ForEach-Object { + if (-not (Get-Module -ListAvailable $_)) { + Install-Module $_ -Scope CurrentUser -Force -AllowClobber | Out-Null + } + } + +# 2. Connect to Microsoft Graph +Connect-MgGraph ` + -Scopes "User.Read.All", "UserAuthenticationMethod.Read.All", "AuditLog.Read.All" | + Out-Null + +# 3. Process users and build the report +$report = [System.Collections.Generic.List[PSCustomObject]]::new() +$users = Get-MgUser -All -Property Id, UserPrincipalName + +foreach ($user in $users) { + $phoneMethods = Get-MgUserAuthenticationPhoneMethod ` + -UserId $user.Id ` + -ErrorAction SilentlyContinue + + if ($phoneMethods) { + $logs = Get-MgAuditLogSignIn ` + -Filter "userId eq '$($user.Id)'" ` + -Top 500 ` + -ErrorAction SilentlyContinue + + if ($logs) { + $logs | + Group-Object -Property { $_.Location.CountryOrRegion } | + ForEach-Object { + $countryOrRegion = if ([string]::IsNullOrWhiteSpace($_.Name)) { + "Unknown" + } + else { + $_.Name + } + + $report.Add([PSCustomObject]@{ + UserPrincipalName = $user.UserPrincipalName + CountryOrRegion = $countryOrRegion + SignInCount = $_.Count + }) + } + } + } +} + +# 4. Display and export the report +$outputPath = "C:\temp\SMS_Voice_Users_Country_Counts.csv" +$report | Sort-Object CountryOrRegion, UserPrincipalName | Format-Table -AutoSize +$report | Export-Csv -Path $outputPath -NoTypeInformation +``` + +The script suppresses per-user read errors so that one inaccessible record does not stop +the report. Investigate unexpectedly missing users or countries before relying on the +result. For a large tenant or a longer analysis window, replace the 500-record cap with +an organization-approved reporting approach that handles Microsoft Graph pagination, +throttling, and your available sign-in-log retention. + +## Convert activity into a cost estimate + +First, summarize the exported activity by country or region: + +```powershell +$activity = Import-Csv "C:\temp\SMS_Voice_Users_Country_Counts.csv" + +$activity | + Group-Object CountryOrRegion | + ForEach-Object { + [PSCustomObject]@{ + CountryOrRegion = $_.Name + SignInCount = ($_.Group.SignInCount | Measure-Object -Sum).Sum + } + } | + Sort-Object CountryOrRegion | + Export-Csv "C:\temp\SMS_Activity_By_Country.csv" -NoTypeInformation +``` + +For each country or region, obtain the provider's applicable price and estimate: + +```text +Estimated messages = sign-in count x assumed SMS messages per sign-in +Estimated cost = estimated messages x provider price per SMS +Projected cost = estimated cost x projected days / observed days +``` + +For example, if the report covers 30 days, contains 10,000 relevant sign-ins, and your +planning assumption is 0.25 SMS messages per sign-in: + +```text +Estimated messages = 10,000 x 0.25 = 2,500 +``` + +Apply the provider's destination-specific rates to those estimated messages. Use separate +rows when rates differ by destination, sender type, route, or message category. Include a +contingency for growth, retries, fallback behavior, and seasonal peaks. + +## Confirm the final estimate with your provider + +Work with your provider to validate: + +- Whether pricing uses the destination phone number rather than sign-in location. +- Country and carrier coverage, sender registration, and route-specific rates. +- SMS segment rules, including Unicode and messages that exceed one segment. +- Minimum commitments, volume tiers, taxes, regulatory fees, and other surcharges. +- Charges for failed, rejected, retried, or duplicate submissions. +- The expected ratio of SMS sends to sign-ins for your authentication policies and users. + +After deployment, compare the estimate with provider billing and approved operational +telemetry. Do not treat this report as proof of message submission, acceptance, delivery, +or the amount that the provider will invoice. From 491aee27c93f09f1f171981e87a80eed0e7c0890 Mon Sep 17 00:00:00 2001 From: Nisheet Jain Date: Fri, 9 Oct 2026 16:08:56 -0700 Subject: [PATCH 2/4] docs: aggregate SMS estimates by country Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 749249f5-dd43-4595-896b-1c4b439ae048 --- docs/COST-CALCULATOR.md | 117 ++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 77 deletions(-) diff --git a/docs/COST-CALCULATOR.md b/docs/COST-CALCULATOR.md index deebe84..5971afb 100644 --- a/docs/COST-CALCULATOR.md +++ b/docs/COST-CALCULATOR.md @@ -21,100 +21,63 @@ account must be allowed to consent to or use these Microsoft Graph delegated per - `AuditLog.Read.All` Access to sign-in logs also depends on your Microsoft Entra licensing, directory role, -and log-retention period. Review the requested permissions before consenting. The output -contains user principal names and sign-in location data; store, share, and delete it -according to your organization's privacy and retention requirements. +and log-retention period. Review the requested permissions before consenting. The script +processes directory users, authentication methods, and sign-in location data. Store, share, +and delete the aggregated output according to your organization's privacy and retention +requirements. -Create `C:\temp` before running the script, or change `$outputPath` to an approved folder. +Create `C:\temp` before running the script, or change the `Export-Csv` path to an +approved folder. ## Generate the activity report The following sample installs any missing Microsoft Graph modules for the current user, connects to Microsoft Graph, identifies users with a phone authentication method, and -groups up to 500 recent sign-ins per user by sign-in country or region. +groups sign-ins from a selected UTC time range by sign-in country or region. Replace +`$start` and `$end` with the period you want to analyze. ```powershell # 1. Install missing modules silently "Microsoft.Graph.Users", "Microsoft.Graph.Identity.SignIns", "Microsoft.Graph.Reports" | - ForEach-Object { - if (-not (Get-Module -ListAvailable $_)) { - Install-Module $_ -Scope CurrentUser -Force -AllowClobber | Out-Null - } - } - -# 2. Connect to Microsoft Graph -Connect-MgGraph ` - -Scopes "User.Read.All", "UserAuthenticationMethod.Read.All", "AuditLog.Read.All" | - Out-Null - -# 3. Process users and build the report -$report = [System.Collections.Generic.List[PSCustomObject]]::new() -$users = Get-MgUser -All -Property Id, UserPrincipalName - -foreach ($user in $users) { - $phoneMethods = Get-MgUserAuthenticationPhoneMethod ` - -UserId $user.Id ` - -ErrorAction SilentlyContinue - - if ($phoneMethods) { - $logs = Get-MgAuditLogSignIn ` - -Filter "userId eq '$($user.Id)'" ` - -Top 500 ` - -ErrorAction SilentlyContinue - - if ($logs) { - $logs | - Group-Object -Property { $_.Location.CountryOrRegion } | - ForEach-Object { - $countryOrRegion = if ([string]::IsNullOrWhiteSpace($_.Name)) { - "Unknown" - } - else { - $_.Name - } - - $report.Add([PSCustomObject]@{ - UserPrincipalName = $user.UserPrincipalName - CountryOrRegion = $countryOrRegion - SignInCount = $_.Count - }) - } - } - } -} - -# 4. Display and export the report -$outputPath = "C:\temp\SMS_Voice_Users_Country_Counts.csv" -$report | Sort-Object CountryOrRegion, UserPrincipalName | Format-Table -AutoSize -$report | Export-Csv -Path $outputPath -NoTypeInformation + ? { -not (Get-Module -ListAvailable $_) } | + % { Install-Module $_ -Scope CurrentUser -Force -AllowClobber | Out-Null } + +# 2. Connect to Graph API +Connect-MgGraph -Scopes "User.Read.All", "UserAuthenticationMethod.Read.All", "AuditLog.Read.All" | Out-Null + +# 3. Define Start and End Time Range (ISO 8601 UTC) +$start = "2026-10-01T00:00:00Z" +$end = "2026-10-30T23:59:59Z" + +# 4. Single Pipeline: Filter users with Phone Auth -> Fetch Sign-ins -> Extract Country -> Group & Count +$report = Get-MgUser -All -Property Id | + ? { Get-MgUserAuthenticationPhoneMethod -UserId $_.Id -ErrorAction SilentlyContinue } | + % { Get-MgAuditLogSignIn -Filter "userId eq '$($_.Id)' and createdDateTime ge $start and createdDateTime le $end" -All -ErrorAction SilentlyContinue } | + Group-Object -Property { $_.Location.CountryOrRegion } | + Select-Object @{N="CountryCode"; E={$_.Name}}, @{N="SignInCount"; E={$_.Count}} | + Sort-Object SignInCount -Descending + +# 5. Output and Export +$report | Format-Table -AutoSize +$report | Export-Csv -Path "C:\temp\SMS_Voice_SignIns_By_Country.csv" -NoTypeInformation ``` The script suppresses per-user read errors so that one inaccessible record does not stop the report. Investigate unexpectedly missing users or countries before relying on the -result. For a large tenant or a longer analysis window, replace the 500-record cap with -an organization-approved reporting approach that handles Microsoft Graph pagination, -throttling, and your available sign-in-log retention. +result. `-All` requests all available pages, but large tenants should still account for +Microsoft Graph throttling and execution time. -## Convert activity into a cost estimate - -First, summarize the exported activity by country or region: +The Microsoft Graph +[list signIns API documentation](https://learn.microsoft.com/en-us/graph/api/signin-list?view=graph-rest-1.0&tabs=http) +states: **"The maximum and default page size is 1,000 objects and by default, the most +recent sign-ins are returned first. Only sign-in events that occurred within the +Microsoft Entra ID default retention period are available."** Selecting an earlier +`$start` date does not make events outside the available retention period accessible. -```powershell -$activity = Import-Csv "C:\temp\SMS_Voice_Users_Country_Counts.csv" - -$activity | - Group-Object CountryOrRegion | - ForEach-Object { - [PSCustomObject]@{ - CountryOrRegion = $_.Name - SignInCount = ($_.Group.SignInCount | Measure-Object -Sum).Sum - } - } | - Sort-Object CountryOrRegion | - Export-Csv "C:\temp\SMS_Activity_By_Country.csv" -NoTypeInformation -``` +## Convert activity into a cost estimate -For each country or region, obtain the provider's applicable price and estimate: +The exported report already contains one aggregated row per sign-in country or region. +For each row, obtain the provider's applicable price and estimate: ```text Estimated messages = sign-in count x assumed SMS messages per sign-in From 4d29811cf96db4b89a4a10cb5cc25c755e60b1f1 Mon Sep 17 00:00:00 2001 From: Nisheet Jain Date: Fri, 9 Oct 2026 17:01:09 -0700 Subject: [PATCH 3/4] docs: estimate costs by phone calling code Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 749249f5-dd43-4595-896b-1c4b439ae048 --- docs/COST-CALCULATOR.md | 104 +++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 32 deletions(-) diff --git a/docs/COST-CALCULATOR.md b/docs/COST-CALCULATOR.md index 5971afb..956a3c3 100644 --- a/docs/COST-CALCULATOR.md +++ b/docs/COST-CALCULATOR.md @@ -2,14 +2,16 @@ Use this guide to create a planning estimate for SMS traffic before onboarding an External Phone Provider (EPP). The report groups recent sign-in activity for users -who have a phone authentication method. Apply your provider's country or region -rates to the report to estimate a budget. +who have a phone authentication method by the registered phone number's international +calling code, such as `+1` or `+91`. Apply your provider's destination rates to the +report to estimate a budget. **This is an estimate, not a usage or billing report.** A sign-in does not prove that -an SMS was sent, and one sign-in can result in no message or more than one message. -The sign-in location is based on the sign-in event and might not match the country -of the destination phone number. Confirm the assumptions, supported destinations, -message segmentation, taxes, fees, and final pricing with your provider. +an SMS or voice call was sent. This guide uses one message or call per counted sign-in +as its planning assumption. Actual traffic can differ because a user can use another +authentication method, reuse an existing session, retry authentication, or fall back +between methods. Confirm the assumptions, supported destinations, message segmentation, +taxes, fees, and final pricing with your provider. ## Prerequisites @@ -22,9 +24,9 @@ account must be allowed to consent to or use these Microsoft Graph delegated per Access to sign-in logs also depends on your Microsoft Entra licensing, directory role, and log-retention period. Review the requested permissions before consenting. The script -processes directory users, authentication methods, and sign-in location data. Store, share, -and delete the aggregated output according to your organization's privacy and retention -requirements. +processes directory users, registered phone numbers, and sign-in records. It exports only +aggregated calling codes and counts, not full phone numbers. Store, share, and delete the +output according to your organization's privacy and retention requirements. Create `C:\temp` before running the script, or change the `Export-Csv` path to an approved folder. @@ -33,8 +35,8 @@ approved folder. The following sample installs any missing Microsoft Graph modules for the current user, connects to Microsoft Graph, identifies users with a phone authentication method, and -groups sign-ins from a selected UTC time range by sign-in country or region. Replace -`$start` and `$end` with the period you want to analyze. +groups sign-ins from a selected UTC time range by the registered phone number's calling +code. Replace `$start` and `$end` with the period you want to analyze. ```powershell # 1. Install missing modules silently @@ -49,23 +51,62 @@ Connect-MgGraph -Scopes "User.Read.All", "UserAuthenticationMethod.Read.All", "A $start = "2026-10-01T00:00:00Z" $end = "2026-10-30T23:59:59Z" -# 4. Single Pipeline: Filter users with Phone Auth -> Fetch Sign-ins -> Extract Country -> Group & Count -$report = Get-MgUser -All -Property Id | - ? { Get-MgUserAuthenticationPhoneMethod -UserId $_.Id -ErrorAction SilentlyContinue } | - % { Get-MgAuditLogSignIn -Filter "userId eq '$($_.Id)' and createdDateTime ge $start and createdDateTime le $end" -All -ErrorAction SilentlyContinue } | - Group-Object -Property { $_.Location.CountryOrRegion } | - Select-Object @{N="CountryCode"; E={$_.Name}}, @{N="SignInCount"; E={$_.Count}} | +# 4. Fetch sign-ins and associate them with the user's preferred registered phone method +$activity = Get-MgUser -All -Property Id | + % { + $user = $_ + $phoneMethod = Get-MgUserAuthenticationPhoneMethod ` + -UserId $user.Id ` + -ErrorAction SilentlyContinue | + Sort-Object @{ E = { + switch ($_.PhoneType) { + "mobile" { 1 } + "alternateMobile" { 2 } + "office" { 3 } + default { 4 } + } + }} | + Select-Object -First 1 + + if ($phoneMethod) { + $callingCode = if ($phoneMethod.PhoneNumber -match '^\s*(\+\d{1,3})(?:\s|$)') { + $Matches[1] + } + else { + "Unknown" + } + + Get-MgAuditLogSignIn ` + -Filter "userId eq '$($user.Id)' and createdDateTime ge $start and createdDateTime le $end" ` + -All ` + -ErrorAction SilentlyContinue | + % { + [PSCustomObject]@{ + CountryCallingCode = $callingCode + } + } + } + } + +# 5. Group and count by the phone number's international calling code +$report = $activity | + Group-Object CountryCallingCode | + Select-Object @{N="CountryCallingCode"; E={$_.Name}}, @{N="SignInCount"; E={$_.Count}} | Sort-Object SignInCount -Descending -# 5. Output and Export +# 6. Output and Export $report | Format-Table -AutoSize -$report | Export-Csv -Path "C:\temp\SMS_Voice_SignIns_By_Country.csv" -NoTypeInformation +$report | Export-Csv -Path "C:\temp\SMS_Voice_SignIns_By_Calling_Code.csv" -NoTypeInformation ``` The script suppresses per-user read errors so that one inaccessible record does not stop -the report. Investigate unexpectedly missing users or countries before relying on the -result. `-All` requests all available pages, but large tenants should still account for -Microsoft Graph throttling and execution time. +the report. It prefers a user's `mobile` method, followed by `alternateMobile`, then +`office`, so each sign-in is counted once when more than one phone method is registered. +The calling-code extraction expects the number to start with a plus-prefixed code followed +by a space, such as `+91 1234567890`; other formats are grouped as `Unknown`. Investigate +unexpectedly missing users or calling codes before relying on the result. `-All` requests +all available pages, but large tenants should still account for Microsoft Graph throttling +and execution time. The Microsoft Graph [list signIns API documentation](https://learn.microsoft.com/en-us/graph/api/signin-list?view=graph-rest-1.0&tabs=http) @@ -76,23 +117,22 @@ Microsoft Entra ID default retention period are available."** Selecting an earli ## Convert activity into a cost estimate -The exported report already contains one aggregated row per sign-in country or region. -For each row, obtain the provider's applicable price and estimate: +The exported report contains one aggregated row per registered phone-number calling code. +Under this guide's one-request-per-sign-in planning assumption, estimate: ```text -Estimated messages = sign-in count x assumed SMS messages per sign-in -Estimated cost = estimated messages x provider price per SMS -Projected cost = estimated cost x projected days / observed days +Estimated SMS/voice requests = sign-in count +Estimated cost = sign-in count x provider price per request +Projected cost = estimated cost x projected days / observed days ``` -For example, if the report covers 30 days, contains 10,000 relevant sign-ins, and your -planning assumption is 0.25 SMS messages per sign-in: +For example, if the report covers 30 days and contains 10,000 relevant sign-ins: ```text -Estimated messages = 10,000 x 0.25 = 2,500 +Estimated SMS/voice requests = 10,000 ``` -Apply the provider's destination-specific rates to those estimated messages. Use separate +Apply the provider's destination-specific rates to those estimated requests. Use separate rows when rates differ by destination, sender type, route, or message category. Include a contingency for growth, retries, fallback behavior, and seasonal peaks. @@ -100,7 +140,7 @@ contingency for growth, retries, fallback behavior, and seasonal peaks. Work with your provider to validate: -- Whether pricing uses the destination phone number rather than sign-in location. +- How each international calling code maps to the provider's destination pricing. - Country and carrier coverage, sender registration, and route-specific rates. - SMS segment rules, including Unicode and messages that exceed one segment. - Minimum commitments, volume tiers, taxes, regulatory fees, and other surcharges. From 9c34cea74ccdc1b3d117dc9331fa55d6048ed30c Mon Sep 17 00:00:00 2001 From: Nisheet Jain Date: Fri, 9 Oct 2026 17:26:22 -0700 Subject: [PATCH 4/4] docs: estimate actual phone authentication steps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 749249f5-dd43-4595-896b-1c4b439ae048 --- docs/COST-CALCULATOR.md | 153 +++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 73 deletions(-) diff --git a/docs/COST-CALCULATOR.md b/docs/COST-CALCULATOR.md index 956a3c3..32c6084 100644 --- a/docs/COST-CALCULATOR.md +++ b/docs/COST-CALCULATOR.md @@ -1,112 +1,118 @@ # Estimate SMS provider costs Use this guide to create a planning estimate for SMS traffic before onboarding an -External Phone Provider (EPP). The report groups recent sign-in activity for users -who have a phone authentication method by the registered phone number's international -calling code, such as `+1` or `+91`. Apply your provider's destination rates to the -report to estimate a budget. +External Phone Provider (EPP). The report finds SMS and voice authentication steps in +recent sign-in activity and groups them by the phone number's international calling +code, such as `+1` or `+91`. Apply your provider's destination rates to the report to +estimate a budget. **This is an estimate, not a usage or billing report.** A sign-in does not prove that -an SMS or voice call was sent. This guide uses one message or call per counted sign-in -as its planning assumption. Actual traffic can differ because a user can use another -authentication method, reuse an existing session, retry authentication, or fall back -between methods. Confirm the assumptions, supported destinations, message segmentation, -taxes, fees, and final pricing with your provider. +the provider accepted or delivered an SMS or voice call. Authentication details can +contain multiple SMS or voice steps for one sign-in, so this guide counts each matching +step as one estimated request. Actual provider billing can differ because of retries, +fallback, failures, message segmentation, and provider-specific charging rules. Confirm +the assumptions, supported destinations, taxes, fees, and final pricing with your provider. + +The `authenticationDetails` data used by this guide is currently available through the +[Microsoft Graph beta API](https://learn.microsoft.com/en-us/graph/api/resources/authenticationdetail?view=graph-rest-beta). +Beta APIs are subject to change and are not supported for production applications. Review +and test this reporting script before each planning exercise. ## Prerequisites Run the script in PowerShell 7 from a secured administrator workstation. The signed-in account must be allowed to consent to or use these Microsoft Graph delegated permissions: -- `User.Read.All` -- `UserAuthenticationMethod.Read.All` - `AuditLog.Read.All` Access to sign-in logs also depends on your Microsoft Entra licensing, directory role, and log-retention period. Review the requested permissions before consenting. The script -processes directory users, registered phone numbers, and sign-in records. It exports only -aggregated calling codes and counts, not full phone numbers. Store, share, and delete the -output according to your organization's privacy and retention requirements. +processes sign-in authentication details that can include phone numbers. It exports only +aggregated calling codes, methods, and counts, not full phone numbers. Store, share, and +delete the output according to your organization's privacy and retention requirements. Create `C:\temp` before running the script, or change the `Export-Csv` path to an approved folder. ## Generate the activity report -The following sample installs any missing Microsoft Graph modules for the current user, -connects to Microsoft Graph, identifies users with a phone authentication method, and -groups sign-ins from a selected UTC time range by the registered phone number's calling -code. Replace `$start` and `$end` with the period you want to analyze. +The following sample installs the Microsoft Graph beta reports module for the current +user, connects to Microsoft Graph, identifies SMS and voice authentication steps, and +groups them by the phone number's calling code. Replace `$start` and `$end` with the +period you want to analyze. ```powershell -# 1. Install missing modules silently -"Microsoft.Graph.Users", "Microsoft.Graph.Identity.SignIns", "Microsoft.Graph.Reports" | - ? { -not (Get-Module -ListAvailable $_) } | - % { Install-Module $_ -Scope CurrentUser -Force -AllowClobber | Out-Null } +# 1. Install the beta reports module if it is missing +if (-not (Get-Module -ListAvailable "Microsoft.Graph.Beta.Reports")) { + Install-Module "Microsoft.Graph.Beta.Reports" ` + -Scope CurrentUser ` + -Force ` + -AllowClobber | + Out-Null +} # 2. Connect to Graph API -Connect-MgGraph -Scopes "User.Read.All", "UserAuthenticationMethod.Read.All", "AuditLog.Read.All" | Out-Null +Connect-MgGraph -Scopes "AuditLog.Read.All" | Out-Null # 3. Define Start and End Time Range (ISO 8601 UTC) $start = "2026-10-01T00:00:00Z" $end = "2026-10-30T23:59:59Z" -# 4. Fetch sign-ins and associate them with the user's preferred registered phone method -$activity = Get-MgUser -All -Property Id | - % { - $user = $_ - $phoneMethod = Get-MgUserAuthenticationPhoneMethod ` - -UserId $user.Id ` - -ErrorAction SilentlyContinue | - Sort-Object @{ E = { - switch ($_.PhoneType) { - "mobile" { 1 } - "alternateMobile" { 2 } - "office" { 3 } - default { 4 } +# 4. Fetch sign-ins and emit one row for each SMS or voice authentication step +$activity = Get-MgBetaAuditLogSignIn ` + -Filter "createdDateTime ge $start and createdDateTime le $end" ` + -Property "authenticationDetails" ` + -All ` + -ErrorAction Stop | + ForEach-Object { + $_.AuthenticationDetails | + Where-Object { $_.AuthenticationMethod -in "SMS", "Voice" } | + ForEach-Object { + $callingCode = if ( + $_.AuthenticationMethodDetail -match '^\s*(\+\d{1,3})(?:\s|$)' + ) { + $Matches[1] + } + else { + "Unknown" } - }} | - Select-Object -First 1 - - if ($phoneMethod) { - $callingCode = if ($phoneMethod.PhoneNumber -match '^\s*(\+\d{1,3})(?:\s|$)') { - $Matches[1] - } - else { - "Unknown" - } - Get-MgAuditLogSignIn ` - -Filter "userId eq '$($user.Id)' and createdDateTime ge $start and createdDateTime le $end" ` - -All ` - -ErrorAction SilentlyContinue | - % { - [PSCustomObject]@{ - CountryCallingCode = $callingCode - } + [PSCustomObject]@{ + CountryCallingCode = $callingCode + AuthenticationMethod = $_.AuthenticationMethod } - } + } } -# 5. Group and count by the phone number's international calling code +# 5. Group and count by calling code and authentication method $report = $activity | - Group-Object CountryCallingCode | - Select-Object @{N="CountryCallingCode"; E={$_.Name}}, @{N="SignInCount"; E={$_.Count}} | - Sort-Object SignInCount -Descending + Group-Object CountryCallingCode, AuthenticationMethod | + ForEach-Object { + [PSCustomObject]@{ + CountryCallingCode = $_.Group[0].CountryCallingCode + AuthenticationMethod = $_.Group[0].AuthenticationMethod + EstimatedRequests = $_.Count + } + } | + Sort-Object CountryCallingCode, AuthenticationMethod -# 6. Output and Export +# 6. Display and export the aggregated report $report | Format-Table -AutoSize $report | Export-Csv -Path "C:\temp\SMS_Voice_SignIns_By_Calling_Code.csv" -NoTypeInformation ``` -The script suppresses per-user read errors so that one inaccessible record does not stop -the report. It prefers a user's `mobile` method, followed by `alternateMobile`, then -`office`, so each sign-in is counted once when more than one phone method is registered. -The calling-code extraction expects the number to start with a plus-prefixed code followed -by a space, such as `+91 1234567890`; other formats are grouped as `Unknown`. Investigate -unexpectedly missing users or calling codes before relying on the result. `-All` requests -all available pages, but large tenants should still account for Microsoft Graph throttling -and execution time. +Microsoft documents `SMS` and `Voice` as authentication method values and states that +`authenticationMethodDetail` can contain the phone number for those methods. The script +does not match an undocumented `Text` value. It also does not use sign-in geography or a +user's currently registered default number, because those can differ from the phone method +recorded for the authentication step. + +The calling-code extraction expects the detail to start with a plus-prefixed code followed +by a space, such as `+91 1234567890`. Masked values or other formats are grouped as +`Unknown`; review a small, securely handled sample in your tenant before relying on the +aggregation. `-All` requests all available pages, but large tenants should still account +for Microsoft Graph throttling and execution time. The script uses `-ErrorAction Stop` +rather than silently producing a partial report when the sign-in query fails. The Microsoft Graph [list signIns API documentation](https://learn.microsoft.com/en-us/graph/api/signin-list?view=graph-rest-1.0&tabs=http) @@ -117,16 +123,17 @@ Microsoft Entra ID default retention period are available."** Selecting an earli ## Convert activity into a cost estimate -The exported report contains one aggregated row per registered phone-number calling code. -Under this guide's one-request-per-sign-in planning assumption, estimate: +The exported report contains one row per calling code and authentication method. It counts +each SMS or voice authentication step as one estimated request: ```text -Estimated SMS/voice requests = sign-in count -Estimated cost = sign-in count x provider price per request +Estimated SMS/voice requests = matching authentication-step count +Estimated cost = estimated requests x provider price per request Projected cost = estimated cost x projected days / observed days ``` -For example, if the report covers 30 days and contains 10,000 relevant sign-ins: +For example, if the report covers 30 days and contains 10,000 matching authentication +steps: ```text Estimated SMS/voice requests = 10,000