From aac78893a284715af3593694eb1dcbe91897d9bf Mon Sep 17 00:00:00 2001 From: Robbie Ferguson Date: Mon, 22 Sep 2025 21:13:21 +0000 Subject: [PATCH] Make interactive --- message-counter.ps1 | 209 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 180 insertions(+), 29 deletions(-) diff --git a/message-counter.ps1 b/message-counter.ps1 index 3de70b3..97c7cd2 100644 --- a/message-counter.ps1 +++ b/message-counter.ps1 @@ -1,44 +1,195 @@ <# .SYNOPSIS - Count the number of received emails in a Microsoft 365 mailbox during a specified time window. + Interactive mailbox message counter for Microsoft 365 (Graph). .DESCRIPTION - This script queries Exchange Online via Microsoft Graph to return the total count of emails - received by a specific mailbox within a given date range. It searches all folders in the mailbox - (including Inbox, subfolders, Junk, and Deleted Items), ensuring that emails moved after delivery - are still included in the count. Drafts are excluded, and self-sent messages from the mailbox - owner are not counted as "received." + Prompts the user to choose "my mailbox" or "another/shared mailbox", gathers the target mailbox, + start date, and end date, then connects to Microsoft Graph with the appropriate delegated scope + and returns a count of RECEIVED messages (excluding drafts and self-sent) within the specified + local time window. It searches ALL folders (Inbox, subfolders, Junk, Deleted Items, etc.) so + post-delivery moves don't matter. - Date boundaries are applied using local time (Eastern Time in this example). - Adjust offsets (-05:00 / -04:00) for daylight savings or your region. + Time boundaries are created using the specified Windows time zone (default: "Eastern Standard Time" + for Toronto). The script computes the correct UTC offset for the provided dates (including DST) + and generates ISO 8601 timestamps with offsets (e.g., 2025-01-01T00:00:00-05:00). -.PARAMETER $u - The target user's email address. Example: 'f@d.org' +.REQUIREMENTS + - PowerShell 5.1+ (Windows) or PowerShell 7+ + - Microsoft.Graph PowerShell SDK (will auto-install for CurrentUser if missing) + - For "another/shared mailbox": + * Your signed-in account must have Full Access to that mailbox in Exchange Online. -.PARAMETER $start - The inclusive start of the time window in ISO 8601 format with time zone offset. - Example: '2025-01-01T00:00:00-05:00' for January 1st, 2025, 12:00 AM ET. - -.PARAMETER $end - The exclusive end of the time window in ISO 8601 format with time zone offset. - Example: '2025-02-01T00:00:00-05:00' for February 1st, 2025, 12:00 AM ET. +.PARAMETERS (prompted) + - Scope mode: "1 = My mailbox" or "2 = Another/shared mailbox" + - Mailbox SMTP address (e.g., user@domain.com) + - Start date (YYYY-MM-DD), inclusive + - End date (YYYY-MM-DD), exclusive .OUTPUTS - Returns a single integer representing the count of received messages. + Prints a single integer count and a short summary of inputs. .NOTES - Requirements: - - Microsoft.Graph PowerShell module - - Connect-MgGraph with Mail.Read delegated permission (if signed in as the user) - OR Mail.Read application permission (with admin consent) if run as a service. - - Install module: Install-Module Microsoft.Graph - - Examples to connect: - This is your mailbox: Connect-MgGraph -Scopes Mail.Read - This is a shared mailbox you are a "Full" delegate of: Connect-MgGraph -Scopes Mail.Read.Shared + Example month window: + Start: 2025-01-01 + End: 2025-02-01 + These will be interpreted at 00:00 local time of the configured time zone. (c) 2025 Robbie Ferguson. All rights reserved. #> -$u='mailbox@domain.com';$start='2025-01-01T00:00:00-05:00';$end='2025-02-01T00:00:00-05:00';$c=0; Get-MgUserMessage -UserId $u -Filter "receivedDateTime ge $start and receivedDateTime lt $end and isDraft eq false and (from/emailAddress/address ne '$u')" -CountVariable c -PageSize 1 | Out-Null; $c \ No newline at end of file +# --------------------------- User-editable settings --------------------------- +# Windows Time Zone ID (use `tzutil /l` to list). "Eastern Standard Time" covers Toronto with DST. +$TimeZoneId = 'Eastern Standard Time' +# ----------------------------------------------------------------------------- + +# Helper: ensure Microsoft Graph SDK is available +function Ensure-GraphModule { + $moduleName = 'Microsoft.Graph' + if (-not (Get-Module -ListAvailable -Name $moduleName)) { + Write-Host "Microsoft.Graph module not found. Installing for CurrentUser..." + try { + Set-PSRepository PSGallery -InstallationPolicy Trusted -ErrorAction SilentlyContinue + Install-Module $moduleName -Scope CurrentUser -Force -ErrorAction Stop + } catch { + Write-Error "Failed to install Microsoft.Graph: $($_.Exception.Message)" + Read-Host "Press Enter to exit" + exit 1 + } + } + Import-Module $moduleName -ErrorAction Stop +} + +# Helper: get TimeZoneInfo safely +function Get-TimeZoneInfoSafe { + param([string]$Id) + try { + return [System.TimeZoneInfo]::FindSystemTimeZoneById($Id) + } catch { + Write-Error "Time zone '$Id' not found. Edit `$TimeZoneId at the top (Windows TZ IDs, e.g., 'Eastern Standard Time')." + Read-Host "Press Enter to exit" + exit 1 + } +} + +# Helper: build ISO timestamp with correct offset for a date at 00:00 local time +function Get-ISOStartOfDayWithOffset { + param( + [datetime]$DateOnly, + [System.TimeZoneInfo]$Tz + ) + # Local 00:00 in the TZ + $local = Get-Date -Date "$($DateOnly.ToString('yyyy-MM-dd')) 00:00:00" + $offset = $Tz.GetUtcOffset($local) + $sign = if ($offset -lt [TimeSpan]::Zero) { '-' } else { '+' } + $hh = [math]::Abs($offset.Hours).ToString('00') + $mm = [math]::Abs($offset.Minutes).ToString('00') + $offStr = "$sign$hh`:$mm" + return "{0}T00:00:00{1}" -f $local.ToString('yyyy-MM-dd'), $offStr +} + +# Helper: connect to Graph with appropriate scope +function Connect-GraphWithScope { + param([string]$Scope) + try { + Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null + } catch {} + try { + Connect-MgGraph -Scopes $Scope -ErrorAction Stop | Out-Null + } catch { + Write-Error "Failed to connect to Microsoft Graph with scope '$Scope': $($_.Exception.Message)" + Read-Host "Press Enter to exit" + exit 1 + } +} + +# Helper: validate date input (YYYY-MM-DD) +function Read-DateStrict { + param([string]$Prompt,[datetime]$Default) + while ($true) { + $in = Read-Host "$Prompt (YYYY-MM-DD) [Enter for $($Default.ToString('yyyy-MM-dd'))]" + if ([string]::IsNullOrWhiteSpace($in)) { return $Default.Date } + if ([datetime]::TryParseExact($in, 'yyyy-MM-dd', $null, 'None', [ref]([datetime]$out))) { + return $out.Date + } + Write-Host "Invalid date. Please use YYYY-MM-DD." -ForegroundColor Yellow + } +} + +# Start +Write-Host "=== M365 Mailbox Message Counter ===" -ForegroundColor Cyan +Ensure-GraphModule + +# 1) Which mailbox type? +Write-Host "Select mailbox type:" -ForegroundColor Cyan +Write-Host " 1) My mailbox" +Write-Host " 2) Another/shared mailbox (requires delegation with Full Access)" +$mode = Read-Host "Enter 1 or 2" +if ($mode -notin @('1','2')) { + Write-Error "Invalid selection." + Read-Host "Press Enter to exit" + exit 1 +} + +# 2) Target mailbox SMTP +$Mailbox = Read-Host "Enter the mailbox SMTP address to check (e.g., user@domain.com)" +if ([string]::IsNullOrWhiteSpace($Mailbox) -or ($Mailbox -notmatch '^[^@\s]+@[^@\s]+\.[^@\s]+$')) { + Write-Error "Invalid email address." + Read-Host "Press Enter to exit" + exit 1 +} + +# 3) Date range (inclusive start, exclusive end) +# Defaults to January 2025 for convenience; change as needed +$defaultStart = Get-Date '2025-01-01' +$defaultEnd = Get-Date '2025-02-01' +$StartDate = Read-DateStrict -Prompt "Start date (inclusive)" -Default $defaultStart +$EndDate = Read-DateStrict -Prompt "End date (exclusive)" -Default $defaultEnd +if ($EndDate -le $StartDate) { + Write-Error "End date must be AFTER start date." + Read-Host "Press Enter to exit" + exit 1 +} + +# Prepare TZ and ISO strings with correct offsets for each boundary date +$tz = Get-TimeZoneInfoSafe -Id $TimeZoneId +$startISO = Get-ISOStartOfDayWithOffset -DateOnly $StartDate -Tz $tz +$endISO = Get-ISOStartOfDayWithOffset -DateOnly $EndDate -Tz $tz + +# 4) Connect with proper scope +$scope = if ($mode -eq '1') { 'Mail.Read' } else { 'Mail.Read.Shared' } +Connect-GraphWithScope -Scope $scope + +# Show who is signed in (helpful for multi-tenant admins) +try { + $ctx = Get-MgContext + if ($ctx) { Write-Host "Connected as: $($ctx.Account) | Tenant: $($ctx.TenantId) | Scope(s): $($ctx.Scopes -join ', ')" -ForegroundColor DarkGray } +} catch {} + +# 5) Perform count +try { + $count = 0 + # Exclude drafts and self-sent (from == mailbox) to count RECEIVED mail + $filter = "receivedDateTime ge $startISO and receivedDateTime lt $endISO and isDraft eq false and (from/emailAddress/address ne '$Mailbox')" + # Ask Graph for a count; we don't need to page through items (PageSize 1 trick) + Get-MgUserMessage -UserId $Mailbox -Filter $filter -CountVariable count -PageSize 1 | Out-Null + + Write-Host "" + Write-Host "Mailbox: $Mailbox" -ForegroundColor Green + Write-Host "Window: $($StartDate.ToString('yyyy-MM-dd')) to $($EndDate.ToString('yyyy-MM-dd')) ($TimeZoneId)" -ForegroundColor Green + Write-Host "Count of RECEIVED messages (all folders, excluding drafts & self-sent):" -ForegroundColor Green + Write-Host $count -ForegroundColor Cyan +} catch { + Write-Host "" + Write-Error "Query failed: $($_.Exception.Message)" + + # Common guidance + if ($mode -eq '2') { + Write-Host "If this is another/shared mailbox, ensure:" -ForegroundColor Yellow + Write-Host " - Your account has Full Access to $Mailbox in Exchange Online (Mailbox Delegation)." -ForegroundColor Yellow + } else { + Write-Host "If this is your own mailbox and it still fails, try reconnecting and consenting the scope." -ForegroundColor Yellow + } +} + +Write-Host "" +Read-Host "Press Enter to exit"