From c0aec1cb578f6eb417a8de1b1250cff08963eefb Mon Sep 17 00:00:00 2001 From: Chris Boonham Date: Wed, 16 Oct 2024 17:23:51 +0100 Subject: [PATCH 1/5] New script to retrieves folder statistics for mailboxes with large numbers of folders, working around potential timeouts. --- Store/Get-LargeMailboxFolderStatistics.ps1 | 68 +++++++++++++++++++ .../Store/Get-LargeMailboxFolderStatistics.md | 36 ++++++++++ 2 files changed, 104 insertions(+) create mode 100644 Store/Get-LargeMailboxFolderStatistics.ps1 create mode 100644 docs/Store/Get-LargeMailboxFolderStatistics.md diff --git a/Store/Get-LargeMailboxFolderStatistics.ps1 b/Store/Get-LargeMailboxFolderStatistics.ps1 new file mode 100644 index 000000000..df647b5f1 --- /dev/null +++ b/Store/Get-LargeMailboxFolderStatistics.ps1 @@ -0,0 +1,68 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Retrieves folder statistics for mailboxes with large numbers of folders. + +.DESCRIPTION + This script retrieves folder statistics for large mailboxes or a specified user identity. + It can target either primary or archive mailboxes and processes the data in batches. + +.PARAMETER Identity + The identity of the user whose mailbox folder statistics are to be retrieved. + +.PARAMETER MailboxType + Specifies the type of mailbox to target. Valid values are "Primary" and "Archive". + Default is "Archive". + +.PARAMETER BatchSize + Specifies the number of items to process in each batch. Default is 5000. + +.PARAMETER Properties + Specifies the properties to include in the output. Default is "Name, FolderPath, ItemsInFolder, FolderSize, FolderAndSubfolderSize". + +.EXAMPLE + $folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com + $folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Primary + $folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Archive -BatchSize 5000 -Properties "Name, FolderPath" +#> +param( + [Parameter(Mandatory = $true, Position = 0)] + $Identity, + [Parameter(Mandatory = $false, Position = 1)] + [ValidateSet("Primary", "Archive")] + $MailboxType = "Archive", + [Parameter(Mandatory = $false, Position = 2)] + $BatchSize = 5000, + [Parameter(Mandatory = $false, Position = 3)] + $Properties = "Name, FolderPath, ItemsInFolder, FolderSize, FolderAndSubfolderSize" +) + +Process { + $allContentFolders = $null + $propertyArray = $Properties -split ',' | ForEach-Object { $_.Trim() } + $start = Get-Date + Write-Host "$start Running Get-MailboxFolderStatistics for $Identity $MailboxType locations, in batches of $BatchSize" + + $mailboxLocations = Get-MailboxLocation -User $Identity + + foreach ($location in $mailboxLocations) { + if (($location.MailboxLocationType -like '*Archive' -and $MailboxType -like 'Archive') -or ($location.MailboxLocationType -like '*Primary' -and $MailboxType -like 'Primary')) { + $loopCount = 0 + do { + $skipCount = $BatchSize * $loopCount + $batch = Get-MailboxFolderStatistics -Identity $($location.Identity) -ResultSize $batchSize -SkipCount $skipCount + $allContentFolders += $batch | Where-Object { $_.ContentFolder -eq "TRUE" } | Select-Object -Property $propertyArray + Write-Host "$(Get-Date):$loopCount Found $($batch.Count) content folders from $($location.MailboxLocationType):$($location.MailboxGuid)" + $loopCount += 1 + } + while ($($batch.Count) -eq $BatchSize) + } + } + + $end = Get-Date + Write-Host "$end Found $($allContentFolders.Count) total content folders in $(($end-$start).ToString()) duration" + + $allContentFolders +} diff --git a/docs/Store/Get-LargeMailboxFolderStatistics.md b/docs/Store/Get-LargeMailboxFolderStatistics.md new file mode 100644 index 000000000..58f4175a4 --- /dev/null +++ b/docs/Store/Get-LargeMailboxFolderStatistics.md @@ -0,0 +1,36 @@ +# Get-LargeMailboxFolderStatistics + +Download the latest release: [Get-LargeMailboxFolderStatistics.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/Get-LargeMailboxFolderStatistics.ps1) + + +This script runs the Get-MailboxFolderStatistics cmdlet and works around the problem of cmdlet timeouts where there are a large number of folders in the mailbox. This is particularly useful with mailboxes with more than 10k folders, especially Archive mailboxes. +Although it can work with both Primary and Archive mailboxes. + +By default the script will try and retrieve the folder statistics for a user's Archive mailbox. It will retrieve the folders in batches of 5000 and just retrieve the commonly required properties Name, FolderPath, ItemsInFolder, FolderSize, FolderAndSubfolderSize. + + +#### Syntax: + +Example to get the mailbox folder statistics for an Archive mailbox. +```PowerShell +$folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com +``` + +Example to get the mailbox folder statistics for a Primary mailbox. +```PowerShell +$folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Primary +``` + +Example to get the mailbox folder statistics for a Archive mailbox, in batches of 5000 and just the folder properties Name and FolderPath +```PowerShell +$folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Archive -BatchSize 5000 -Properties "Name, FolderPath" +``` + + + +##### Further information
+ +The sweet spot seems to be retrieving folders in batches of about 5000 at a time. This prevents cmdlet timeouts but also achieves a good overall run time. + +The script has been used successfully against archives mailboxes with up to 60K folders. + From 854bf81f7096a8e85bae199561aa16f30d1abfbe Mon Sep 17 00:00:00 2001 From: Chris Boonham Date: Thu, 17 Oct 2024 11:43:18 +0100 Subject: [PATCH 2/5] Update Store/Get-LargeMailboxFolderStatistics.ps1 Co-authored-by: David Paulson --- Store/Get-LargeMailboxFolderStatistics.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Store/Get-LargeMailboxFolderStatistics.ps1 b/Store/Get-LargeMailboxFolderStatistics.ps1 index df647b5f1..54e32a940 100644 --- a/Store/Get-LargeMailboxFolderStatistics.ps1 +++ b/Store/Get-LargeMailboxFolderStatistics.ps1 @@ -36,7 +36,7 @@ param( [Parameter(Mandatory = $false, Position = 2)] $BatchSize = 5000, [Parameter(Mandatory = $false, Position = 3)] - $Properties = "Name, FolderPath, ItemsInFolder, FolderSize, FolderAndSubfolderSize" + [string[]]$Properties = @("Name", "FolderPath", "ItemsInFolder", "FolderSize", "FolderAndSubfolderSize") ) Process { From 74f90a87247e4213a81fff470ad8c6343a8ca5bb Mon Sep 17 00:00:00 2001 From: Chris Boonham Date: Thu, 17 Oct 2024 12:37:18 +0100 Subject: [PATCH 3/5] Address code review comments --- {Store => M365}/Get-LargeMailboxFolderStatistics.ps1 | 9 +++++---- docs/{Store => M365}/Get-LargeMailboxFolderStatistics.md | 2 +- mkdocs.yml | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) rename {Store => M365}/Get-LargeMailboxFolderStatistics.ps1 (90%) rename docs/{Store => M365}/Get-LargeMailboxFolderStatistics.md (98%) diff --git a/Store/Get-LargeMailboxFolderStatistics.ps1 b/M365/Get-LargeMailboxFolderStatistics.ps1 similarity index 90% rename from Store/Get-LargeMailboxFolderStatistics.ps1 rename to M365/Get-LargeMailboxFolderStatistics.ps1 index 54e32a940..4145c0207 100644 --- a/Store/Get-LargeMailboxFolderStatistics.ps1 +++ b/M365/Get-LargeMailboxFolderStatistics.ps1 @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +#Requires -Modules @{ ModuleName="ExchangeOnlineManagement"; RequiredVersion="3.4.0" } + <# .SYNOPSIS Retrieves folder statistics for mailboxes with large numbers of folders. @@ -25,7 +27,7 @@ .EXAMPLE $folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com $folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Primary - $folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Archive -BatchSize 5000 -Properties "Name, FolderPath" + $folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Archive -BatchSize 5000 -Properties @("Name", "FolderPath") #> param( [Parameter(Mandatory = $true, Position = 0)] @@ -40,8 +42,7 @@ param( ) Process { - $allContentFolders = $null - $propertyArray = $Properties -split ',' | ForEach-Object { $_.Trim() } + $allContentFolders = New-Object System.Collections.Generic.List[System.Management.Automation.PSCustomObject] $start = Get-Date Write-Host "$start Running Get-MailboxFolderStatistics for $Identity $MailboxType locations, in batches of $BatchSize" @@ -53,7 +54,7 @@ Process { do { $skipCount = $BatchSize * $loopCount $batch = Get-MailboxFolderStatistics -Identity $($location.Identity) -ResultSize $batchSize -SkipCount $skipCount - $allContentFolders += $batch | Where-Object { $_.ContentFolder -eq "TRUE" } | Select-Object -Property $propertyArray + $allContentFolders += $batch | Where-Object { $_.ContentFolder -eq "TRUE" } | Select-Object -Property $Properties Write-Host "$(Get-Date):$loopCount Found $($batch.Count) content folders from $($location.MailboxLocationType):$($location.MailboxGuid)" $loopCount += 1 } diff --git a/docs/Store/Get-LargeMailboxFolderStatistics.md b/docs/M365/Get-LargeMailboxFolderStatistics.md similarity index 98% rename from docs/Store/Get-LargeMailboxFolderStatistics.md rename to docs/M365/Get-LargeMailboxFolderStatistics.md index 58f4175a4..b859fb9d8 100644 --- a/docs/Store/Get-LargeMailboxFolderStatistics.md +++ b/docs/M365/Get-LargeMailboxFolderStatistics.md @@ -23,7 +23,7 @@ $folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com Example to get the mailbox folder statistics for a Archive mailbox, in batches of 5000 and just the folder properties Name and FolderPath ```PowerShell -$folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Archive -BatchSize 5000 -Properties "Name, FolderPath" +$folderStats = .\Get-LargeMailboxFolderStatistics.ps1 -Identity fred@contoso.com -MailboxType Archive -BatchSize 5000 -Properties @("Name", "FolderPath") ``` diff --git a/mkdocs.yml b/mkdocs.yml index 6f2e83b82..f47ea6f9e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -79,6 +79,7 @@ nav: - MDO: - MDOThreatPolicyChecker: M365/MDO/MDOThreatPolicyChecker.md - DLT365Groupsupgrade: M365/DLT365Groupsupgrade.md + - Get-LargeMailboxFolderStatistics: M365/Get-LargeMailboxFolderStatistics.md - Performance: - ExPerfWiz: Performance/ExPerfWiz.md - ExPerfAnalyzer: Performance/ExPerfAnalyzer.md From 8e42a9ef788af986c3f44ac0734f0ac9e0f868d1 Mon Sep 17 00:00:00 2001 From: Chris Boonham Date: Thu, 17 Oct 2024 15:22:46 +0100 Subject: [PATCH 4/5] Fix CodeFormatter issues --- M365/Get-LargeMailboxFolderStatistics.ps1 | 2 +- docs/M365/Get-LargeMailboxFolderStatistics.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/M365/Get-LargeMailboxFolderStatistics.ps1 b/M365/Get-LargeMailboxFolderStatistics.ps1 index 4145c0207..eee5e062b 100644 --- a/M365/Get-LargeMailboxFolderStatistics.ps1 +++ b/M365/Get-LargeMailboxFolderStatistics.ps1 @@ -41,7 +41,7 @@ param( [string[]]$Properties = @("Name", "FolderPath", "ItemsInFolder", "FolderSize", "FolderAndSubfolderSize") ) -Process { +process { $allContentFolders = New-Object System.Collections.Generic.List[System.Management.Automation.PSCustomObject] $start = Get-Date Write-Host "$start Running Get-MailboxFolderStatistics for $Identity $MailboxType locations, in batches of $BatchSize" diff --git a/docs/M365/Get-LargeMailboxFolderStatistics.md b/docs/M365/Get-LargeMailboxFolderStatistics.md index b859fb9d8..b10c9191c 100644 --- a/docs/M365/Get-LargeMailboxFolderStatistics.md +++ b/docs/M365/Get-LargeMailboxFolderStatistics.md @@ -1,4 +1,4 @@ -# Get-LargeMailboxFolderStatistics +# Get-LargeMailboxFolderStatistics Download the latest release: [Get-LargeMailboxFolderStatistics.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/Get-LargeMailboxFolderStatistics.ps1) From 33bf1c04c724eca12cc9f23a6ac6e9da961d3de8 Mon Sep 17 00:00:00 2001 From: Chris Boonham Date: Fri, 18 Oct 2024 09:22:59 +0100 Subject: [PATCH 5/5] Use generic list for performance --- M365/Get-LargeMailboxFolderStatistics.ps1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/M365/Get-LargeMailboxFolderStatistics.ps1 b/M365/Get-LargeMailboxFolderStatistics.ps1 index eee5e062b..02a8cc014 100644 --- a/M365/Get-LargeMailboxFolderStatistics.ps1 +++ b/M365/Get-LargeMailboxFolderStatistics.ps1 @@ -42,7 +42,7 @@ param( ) process { - $allContentFolders = New-Object System.Collections.Generic.List[System.Management.Automation.PSCustomObject] + $allContentFolders = New-Object System.Collections.Generic.List[object] $start = Get-Date Write-Host "$start Running Get-MailboxFolderStatistics for $Identity $MailboxType locations, in batches of $BatchSize" @@ -54,7 +54,10 @@ process { do { $skipCount = $BatchSize * $loopCount $batch = Get-MailboxFolderStatistics -Identity $($location.Identity) -ResultSize $batchSize -SkipCount $skipCount - $allContentFolders += $batch | Where-Object { $_.ContentFolder -eq "TRUE" } | Select-Object -Property $Properties + [System.Array]$contentFolders = $batch | Where-Object { $_.ContentFolder -eq "TRUE" } | Select-Object -Property $Properties + if ($contentFolders.Count -gt 0) { + $allContentFolders.AddRange($contentFolders) + } Write-Host "$(Get-Date):$loopCount Found $($batch.Count) content folders from $($location.MailboxLocationType):$($location.MailboxGuid)" $loopCount += 1 }