Ex.GetLargeItems.psm1
<#
. [COPYRIGHT] . © 2011-2019 Microsoft Corporation. All rights reserved. . . [DISCLAIMER] . This sample script is not supported under any Microsoft standard support . program or service. The sample scripts are provided AS IS without warranty of . any kind. Microsoft disclaims all implied warranties including, without . limitation, any implied warranties of merchantability or of fitness for a . particular purpose. The entire risk arising out of the use or performance of . the sample scripts and documentation remains with you. In no event shall . Microsoft, its authors, or anyone else involved in the creation, production, . or delivery of the scripts be liable for any damages whatsoever (including, . without limitation, damages for loss of business profits, business . interruption, loss of business information, or other pecuniary loss) arising . out of the use of or inability to use the sample scripts or documentation, . even if Microsoft has been advised of the possibility of such damages. . . [AUTHOR] . Jason Parker, Sr. Consultant . . [CONTRIBUTORS] . Michael Hall, Service Engineer . Jayme Bowers, Senior Service Engineer . Stuart Murray, Consultant . Dmitry Kazantsev, Senior Consultant . . [Module] . Ex.GetMailboxItems . . [VERSION] . 3.0.0 . . [VERSION HISTORY / UPDATES] . Removed all prevsion version notes. Version history can be found in the archive. . . 2.3 - MAJOR UPDATE . Jason Parker - Updated all function with proper verb / noun association . Jason Parker - Fixed logic in Get-FolderItems where the script would get stuck in an endless loop . Jason Parker - Updated Get-FolderItems so that when a set of itmes is returned, the whole array is evaluated for exceeding the ItemSizeLimit, which increases performance . Jason Parker - Updated Get-FolderItems so that the search only returns a small subset of properties on the items instead of every possible property. . Jason Parker - Removed Large Item Notice - will bring back in 2.3.1 . Jason Parker - Added functions for better menu and dialog feedback . Jason Parker - Updated progress functionality . . 3.0 - NEW RELEASE #> Function Get-MailboxItems { <# .SYNOPSIS This function will run a series of cmdlets / functions using Exchange Web Services to search mailboxes for items over a specified size. Useful for Office 365 on-boarding where you need to remediate large items before migration. .DESCRIPTION This function arranges building-block cmdlets / functions to connect to an Exchange environment and loops through all or a subset of mailboxes with an impersonator account using Exchange Web Services API. The impersonator account will enumerate every item in every folder and identify items that are exceeding a specific size. The function is designed to be executed before on-boarding an Organization to Office 365. .PARAMETER ServiceAccountName Specifies the UserPrincipalName of the user which has elevated permissions (impersonation and mailbox export). .PARAMETER ServicePassword Specifies the password for the Service Account Users (stored in clear text). .PARAMETER ItemSizeLimit Sets the value from which you will measure items against (in MB). This value should be set to the same value used in your Office 365 Tenant (Max Send / Receive Size). The maximum size allowed in Office 365 is 150 MB. .PARAMETER MailboxLocation MailboxLocation is a [ValidateSet] parameter and will only accept 3 possible values: Primary: The function will target the users primary mailbox and folders (MsgFolderRoot) Archive: The function will target the users archive mailbox and folders (ArchiveMsgFolderRoot) RecoverableItems: The function will target the hidden mailbox dumpter and folders (RecoverableItemsRoot), which is useful for mailboxes under litigation hold. .PARAMETER Action Action is a [ValidateSet] parameter and will only accept 4 possible values: ReportOnly ----------- No action is taken on any item, only data collection which is output into a CSV file MoveLargeItems --------------- Function will prompt for a folder name, get / create the folder (regardless of MailboxLocation parameter, all folders will be created in the users Primary mailbox), and MOVE any item found larger than the ItemSizeLimit into that folder. NOTE: If moving large items from locations other than the users Primary mailbox, their mailbox must be able to support these new items! MoveAndExportItems ------------------- Function will prompt for a folder name, get / create the folder (regardless of MailboxLocation parameter, all folders will be created in the users Primary mailbox), MOVE any item found larger than the ItemSizeLimit into that folde, and finally create a New-MailboxExportRequest which will only export items from the Large Item folder. ExportLargeItems ----------------- Does NOT check any items and will ONLY prompt for the folder name used from a previous MoveLargeItems operation and and finally create a New-MailboxExportRequest which will only export items from the Large Item folder. -------------------------- PST EXPORT LOCATIONS -------------------------- When selecting an action with Export, the function will prompt where the PST Export files should be stored. The function will allow 2 choices: Home Directory --------------- Will read the homedirectory attribute from Active Directory and attempt to export to that location Centralized Network Share -------------------------- Function will prompt for the Server and Share Name and allows the operator to select the folder to be used .PARAMETER Uri Sets the Uri for the Exchange Web Services endpoint. Useful when you can't leverage Autodiscover or Autodiscover fails. .EXAMPLE Get-MailboxItems -ServiceAccountName <User@domain.com> -ServicePassword <Password> -ItemSizeLimit <Value in MB> -MailboxLocation Primary -ScriptAction ReportOnly -- CREATES CSV REPORT OF MAILBOXES WITH LARGE ITEMS -- In this example, the -MailboxLocation is set to Primary and the -ScriptAction has been set to ReportOnly which will create a CSV file containing all the item violations from all the mailboxes that were scanned. This CSV Report can be used in a subsequent execution where an alternate -ScriptAction is used (e.g. MoveLargeItems or MoveAndExportItems). This may save considerable execution time if the function is run multiple times. .EXAMPLE Get-MailboxItems -ServiceAccountName <User@domain.com> -ServicePassword <Password> -ItemSizeLimit <Value in MB> -MailboxLocation Archive -ScriptAction ReportOnly -- CREATES CSV REPORT OF ARCHIVE MAILBOXES WITH LARGE ITEMS -- In this example, the -MailboxLocation is set to Archive and the -ScriptAction has been set to ReportOnly which will create a CSV file containing all the item violations from all the mailboxes that were scanned. This CSV Report can be used in a subsequent execution where an alternate -ScriptAction is used (e.g. MoveLargeItems or MoveAndExportItems). This may save considerable execution time if the function is run multiple times. .EXAMPLE Get-MailboxItems -ServiceAccountName <User@domain.com> -ServicePassword <Password> -ItemSizeLimit <Value in MB> -MailboxLocation RecoverableItems -ScriptAction ReportOnly -- CREATE CSV REPORT OF MAILBOXES WITH LARGE ITEMS (LEGAL / LITIGATION HOLD) -- In this example, the -MailboxLocation is set to RecoverableItems and the -ScriptAction has been set to ReportOnly which will create a CSV file containing all the item violations from all the mailboxes that were scanned. This CSV Report can be used in a subsequent execution where an alternate -ScriptAction is used (e.g. MoveLargeItems or MoveAndExportItems). This may save considerable execution time if the function is run multiple times. .EXAMPLE Get-MailboxItems -ServiceAccountName <User@domain.com> -ServicePassword <Password> -ItemSizeLimit <Value in MB> -ScriptAction ExportLargeItems -- EXPORT LARGE ITEMS ONLY -- In this example, the function is not using the -MailboxLocation parameter because the mailbox will not be searched using EWS. This ScriptAction will only attempt to create a New-MailboxExportRequest and export the contents of the Large Item folder to a PST. .NOTES Large environments will take a significant amount of time to process (hours/days). You can reduce the run time by either using a CSV import file with a smaller subset of users or running multiple instances of the function concurrently, targeting mailboxes on different servers. Running multiple instances assumes your Exchange Web Services endpoint is behind a network load balancer. Important: Do not run too many instances or against too many mailboxes at once. Doing so could cause performance issues, affecting users. Microsoft is not responsible for any such performance issue or improper use and planning. [PERMISSIONS REQUIRED] This function requires elevated permissions beyond the typical RBAC roles. [EXCHANGE 2013/2016/2019 PERMISSIONS] There are two sets of permissions required to properly execute the function in an Exchange 2013 / 2016 / 2019 environment. Impersonation and Export permissions. Both sets of permissions will require changing or creating of RBAC Management Role Assignments. [IMPERSONATION PERMISSIONS] From the Exchange Management Shell, run the New-ManagementRoleAssignment cmdlet to add the permission to impersonate to the specified user: New-ManagementRoleAssignment –Name:impersonationAssignmentName –Role:ApplicationImpersonation –User:ServiceAccount [NEW-MAILBOXEXPORTREQUEST PERMISSIONS] This cmdlet is available only in the Mailbox Import Export role, and by default, that role isn't assigned to a role group. To use this cmdlet, you need to add the Mailbox Import Export role to a role group (for example, to the Organization Management role group). For more information, see the "Add a role to a role group" section in Manage role groups. New-ManagementRoleAssignment –Role “Mailbox Import Export” –User Domain\User When specifying an Export Action, please ensure that the network location has NTFS Read/Write permissions for the "Exchange Trusted Subsystem" Group .LINK Install the EWS Managed API 2.2: http://www.microsoft.com/en-us/download/details.aspx?id=42951 .LINK Configure Exchange Web Services Impersonation: https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2007/bb204095(v=exchg.80) .LINK Exchange 2013 / 2016 / 2019 Manage Role Groups: https://technet.microsoft.com/en-us/library/jj657480(v=exchg.160).aspx #> Param ( [Parameter(Position = 1, Mandatory = $True, HelpMessage = "Please provide the UserID for the Service Account")] [System.String]$ServiceAccountName, [Parameter(Position = 2, Mandatory = $True, HelpMessage = "Please provide the password for the Service Account")] [System.String]$ServicePassword, [Parameter(Position = 3, Mandatory = $True, ValueFromPipeline = $True, HelpMessage = "Enter the item size in Megabytes you want to search for in each mailbox")] [ValidateRange(1, 150)] [System.Int32]$ItemSizeLimit, [Parameter(Position = 4, Mandatory = $false)] [ValidateSet("Primary", "Archive", "RecoverableItems")] [System.String]$MailboxLocation, [Parameter(Position = 5, Mandatory = $true)] [ValidateSet("ReportOnly", "MoveLargeItems", "MoveAndExportItems", "ExportLargeItems")] [System.String]$Action, [Parameter(Mandatory = $False)] [System.URI]$Uri ) #region MessageBanners $HomeDirWarning = (@" Exporting to a User's Home Directory will require that the account used to execute this function has sufficient permissions for the operation! "@) $CentralizedExportWarning = (@" Centralized Exports will require enough free space to accomodate all the PST files for Users checked using this function. For performance purposes, do not select a Network location, but use a server with a good amount of local storage. "@) $ExportOnlyWarning = (@" SELECTING THIS OPTION SHOULD ONLY BE DONE IF DURING A PREVIOUS OPERATION LARGE ITEMS WERE MOVED TO A LARGE ITEM FOLDER. YOU MUST USE THE *SAME* LARGE ITEM FOLDER NAME FROM THE PREVIOUS OPERATION OR YOU WILL NOT EXPORT THE CORRECT DATA! THIS PROCESS EXPORTS FROM THE LARGE ITEM FOLDER ONLY! "@) $GetMailboxWarning = (@" By selecting [A]LL MAILBOXES, the function will use the Cmdlet Get-Mailbox with the -ResultSize Unlimited parameter. Depending on the size of your organization, this may be a long operation. "@) #endregion ############################################################################## # Main Script ############################################################################## $Error.Clear() Write-Debug "Script Start" $Stopwatch = New-Object System.Diagnostics.Stopwatch $Stopwatch.Start() $Script:LargeFolderName = $null $Script:CurrentPSSession = $null $Script:AllLargeItems = $null $Script:CentralizedExport = $null $Script:ExportPath = $null $Script:MailboxObjects = $null $myTitle = @" \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ \\\\\\\\\\ \\\\\\\\\\ Title: Office 365 Get-MailboxItems \\\\\\\\\\ Purpose: Find items in mailboxes over $($ItemSizeLimit) MB and performs an action(s) \\\\\\\\\\ Actions: ReportOnly, MoveLargeItems, MoveAndExportItems, ExportLargeItems \\\\\\\\\\ Script: Get-MailboxItems \\\\\\\\\\ \\\\\\\\\\ Help: Get-Help Get-MailboxItems -Full \\\\\\\\\\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ "@ Show-Menu -Title $myTitle -ClearScreen -DisplayOnly -Style None -Color White If (-NOT ((Test-ConsoleVersion -eq $true) -and (Test-ConsoleRights -eq $true))) { Show-Menu -Title "This Console does NOT meet the minimum requirements!" -DisplayOnly -Style Full -Color Yellow Write-Host " >> PLEASE USE POWERSHELL V3 AND RUN AS ADMINISTRATOR <<`n`r" -ForegroundColor Yellow Read-Host "Press any key to exit" | Out-Null Clear-Host Return } try { $ScriptPath = (Get-Location).Path $CurrentDateTime = Get-Date -Format yyyyMMdd_HHmmss $LogFile = "$ScriptPath\LargeItemChecks_ScriptLog_$CurrentDateTime.log" #region Validate / Connect to Exchange Write-Log -Type INFO -Text "Looking for Microsoft Exchange Server Management Tools" $RootRegPath = 'HKLM:\SOFTWARE\Microsoft' If (Test-Path -Path $RootRegPath'\ExchangeServer\v15\AdminTools') { [System.String]$ExchangeVersion = "E15" $env:ExchangeInstallPath = (Get-ItemProperty $RootRegPath'\ExchangeServer\v15\Setup').MsiInstallPath Write-Log -Type INFO -Text ("Setting Exchange Version to: {0}" -f $ExchangeVersion) } Else { Write-Log -Type ERROR -Text ("Microsoft Exchange Server Management Tools cannot be found or are not installed, function will now exit") Return } Write-Log -Type INFO -Text ("Checking PowerShell Console for Exchange Management Cmdlets") If (-NOT (Get-Command Get-ExchangeServer -ErrorAction SilentlyContinue)) { Write-Log -Type INFO -Text ("Attempting to load Exchange Management Shell based on version") If (Test-Path $env:ExchangeInstallPath'bin\RemoteExchange.ps1') { . $env:ExchangeInstallPath'bin\RemoteExchange.ps1' Connect-ExchangeServer -auto $Script:CurrentPSSession = Get-PSSession -InstanceId (Get-OrganizationConfig).RunspaceId.Guid } Else { Write-Log -Type INFO -Text ("Microsoft Exchange Server Management Shell could not be loaded, function will now exit") Return } } Else { $Script:CurrentPSSession = Get-PSSession -InstanceId (Get-OrganizationConfig).RunspaceId.Guid Write-Log -Type INFO -Text ("Found Exchange Management Cmdlets | Connected to: {0}" -f $CurrentPSSession.ComputerName) } #endregion #region Validating ACTION based parameters Write-Log -Type INFO -Text ("Validating ACTION based parameters") Switch ($Action) { "ReportOnly" { Write-Log -Type INFO -Text ("ACTION: {0}" -f $Action) If ([System.String]::IsNullOrEmpty($MailboxLocation)) { $Script:MailboxLocation = "Primary" } [System.Collections.ArrayList]$Script:AllLargeItems = @() } "MoveLargeItems" { Write-Log -Type INFO -Text ("ACTION: {0}" -f $Action) Write-Log -Type INFO -Text ("Gathering required variables...") If ([System.String]::IsNullOrEmpty($MailboxLocation)) { $Script:MailboxLocation = "Primary" } Get-LargeFolderName [System.Collections.ArrayList]$Script:AllLargeItems = @() } "MoveAndExportItems" { Write-Log -Type INFO -Text ("ACTION: {0}" -f $Action) Write-Log -Type INFO -Text ("Gathering required variables...") If ([System.String]::IsNullOrEmpty($MailboxLocation)) { $Script:MailboxLocation = "Primary" } Get-LargeFolderName $optHomeDirs = New-Object System.Management.Automation.Host.ChoiceDescription "&User Home Directory", "Query Active Directory for the User's Home Directory attribute and Export the PST to that location" $optCentralExport = New-Object System.Management.Automation.Host.ChoiceDescription "&Centralized Location", "Select a folder location to store all PST Export files" $Options = [System.Management.Automation.Host.ChoiceDescription[]]($optHomeDirs, $optCentralExport) Switch ($Host.UI.PromptForChoice("`n >> PST EXPORT LOCATION <<", "`n Which type of location do you want to store the PST Export Files?`n`r", $Options, 1)) { 0 { Show-Menu -Title "WARNING: HOME DIRECTORY EXPORT SELECTED!" -ClearScreen -DisplayOnly -Style Info -Color Yellow Write-Host $HomeDirWarning -ForegroundColor Yellow If ((Get-ChoicePrompt -OptionList "&OK", "&Cancel" -Title " OK to Continue, Cancel to exit`n`r" -Message $null -default 1) -eq 1) { Write-Log -Type INFO -Text "// HOME DIRECTORY SELECTION | User Cancelled the operation!" Return } Else { $Script:CentralizedExport = $False $Script:ExportPath = "Home Directories" } } 1 { Show-Menu -Title "WARNING: CENTRALIZED EXPORT SELECTED!" -ClearScreen -DisplayOnly -Style Info -Color Yellow Write-Host $CentralizedExportWarning -ForegroundColor Yellow If ((Get-ChoicePrompt -OptionList "&OK", "&Cancel" -Title " OK to Continue, Cancel to exit`n`r" -Message $null -default 1) -eq 1) { Write-Log -Type INFO -Text "// CENTRALIZED EXPORT SELECTION | User Cancelled the operation!" Return } Else { $Script:CentralizedExport = $True $NetworkSharePath = Get-NetworkSharePath [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null $FolderDialog = New-Object System.Windows.Forms.FolderBrowserDialog $FolderDialog.SelectedPath = $NetworkSharePath Do { $DialogResult = $FolderDialog.ShowDialog() } Until ($DialogResult -eq "OK") $Script:ExportPath = $FolderDialog.SelectedPath } } } [System.Collections.ArrayList]$Script:AllLargeItems = @() } "ExportLargeItems" { Write-Log -Type INFO -Text ("ACTION: {0}" -f $Action) Write-Log -Type INFO -Text ("Gathering required variables...") Show-Menu -Title "WARNING: EXPORT LARGE ITEMS SELECTED!" -ClearScreen -DisplayOnly -Style Info -Color Yellow Write-Host $ExportOnlyWarning -ForegroundColor Yellow If ((Get-ChoicePrompt -OptionList "&OK", "&Cancel" -Title " OK to Continue, Cancel to exit`n`r" -Message $null -default 1) -eq 1) { Write-Log -Type INFO -Text "// EXPORT LARGE ITEMS SELECTION | User Cancelled the operation!" Return } Get-LargeFolderName $optHomeDirs = New-Object System.Management.Automation.Host.ChoiceDescription "&User Home Directory", "Query Active Directory for the User's Home Directory attribute and Export the PST to that location" $optCentralExport = New-Object System.Management.Automation.Host.ChoiceDescription "&Centralized Location", "Select a folder location to store all PST Export files" $Options = [System.Management.Automation.Host.ChoiceDescription[]]($optHomeDirs, $optCentralExport) Switch ($Host.UI.PromptForChoice("`n >> PST EXPORT LOCATION <<", "`n Which type of location do you want to store the PST Export Files?`n`r", $Options, 1)) { 0 { Show-Menu -Title "WARNING: HOME DIRECTORY EXPORT SELECTED!" -ClearScreen -DisplayOnly -Style Info -Color Yellow Write-Host $HomeDirWarning -ForegroundColor Yellow If ((Get-ChoicePrompt -OptionList "&OK", "&Cancel" -Title " OK to Continue, Cancel to exit`n`r" -Message $null -default 1) -eq 1) { Write-Log -Type INFO -Text "// HOME DIRECTORY SELECTION | User Cancelled the operation!" Return } Else { $Script:CentralizedExport = $False $Script:ExportPath = "Home Directories" } } 1 { Show-Menu -Title "WARNING: CENTRALIZED EXPORT SELECTED!" -ClearScreen -DisplayOnly -Style Info -Color Yellow Write-Host $CentralizedExportWarning -ForegroundColor Yellow If ((Get-ChoicePrompt -OptionList "&OK", "&Cancel" -Title " OK to Continue, Cancel to exit`n`r" -Message $null -default 1) -eq 1) { Write-Log -Type INFO -Text "// CENTRALIZED EXPORT SELECTION | User Cancelled the operation!" Return } Else { $Script:CentralizedExport = $True $NetworkSharePath = Get-NetworkSharePath [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null $FolderDialog = New-Object System.Windows.Forms.FolderBrowserDialog $FolderDialog.SelectedPath = $NetworkSharePath Do { $DialogResult = $FolderDialog.ShowDialog() } Until ($DialogResult -eq "OK") $Script:ExportPath = $FolderDialog.SelectedPath } } } } } #endregion #region Mailbox Selection Process (Single User, CSV, or ALL) Write-Log -Type INFO -Text ("Determining which Exchange Mailboxes to process") $optSingleUser = New-Object System.Management.Automation.Host.ChoiceDescription "&Single User", "Provide a single user PrimarySMTPAddress" $optCSVImport = New-Object System.Management.Automation.Host.ChoiceDescription "&CSV Import", "Select a CSV file to Import - MUST HAVE PrimarySMTPAddress as a header value" $optAllMailboxes = New-Object System.Management.Automation.Host.ChoiceDescription "&ALL MAILBOXES", "Query Exchange for ALL MAILBOXES in the Organization" $Options = [System.Management.Automation.Host.ChoiceDescription[]]($optSingleUser, $optCSVImport, $optAllMailboxes) Switch ($Host.UI.PromptForChoice("`n >> MAILBOX SELECTION <<", "`n Please select which Mailboxes to peform Large Item Checks?`n`r", $Options, 0)) { # Single User 0 { $MailboxSelection = "SINGLE USER" Write-Log -Type INFO -Text ("Mailbox Selection Criteria: {0}" -f $MailboxSelection) $Script:MailboxObjects = Get-SingleMailboxUser } # CSV Import 1 { $MailboxSelection = "CSV IMPORT" Write-Log -Type INFO -Text ("Mailbox Selection Criteria: {0}" -f $MailboxSelection) Do { $ImportFile = Get-CSVFileDialog If ([System.String]::IsNullOrEmpty($ImportFile)) { Write-Log -Type ERROR -Text ("No File was selected") Return } Else { If ((Import-Csv -Path $ImportFile | Get-Member).Name -Contains "PrimarySMTPAddress") { $ValidCSVFile = $true } Else { Write-Log -Type WARNING -Text ("The CSV File {0} does not contain a valid PrimarySMTPAddress header!" -f $ImportFile) $ValidCSVFile = $false Start-Sleep -Milliseconds 999 } } } Until ($ValidCSVFile -eq $True) $Script:MailboxObjects = Import-Csv -Path $ImportFile Write-Log -Type INFO -Text ("Mailbox Objects to process: {0:N0}" -f $Script:MailboxObjects.Count) Start-Sleep -Milliseconds 999 } # ALL MAILBOXES 2 { $MailboxSelection = "ALL MAILBOXES" Write-Log -Type INFO -Text ("Mailbox Selection Criteria: {0}" -f $MailboxSelection) Show-Menu -Title "WARNING: ALL MAILBOXES SELECTED!" -ClearScreen -DisplayOnly -Style Info -Color Yellow Write-Host $GetMailboxWarning -ForegroundColor Yellow Switch (Get-ChoicePrompt -OptionList "&OK", "&Cancel" -Title " OK to Continue, Cancel to exit`n`r" -Message $null -default 1) { 0 { If ((Get-Job).Count -gt 0) { Write-Log -Type WARNING -Text ("Multiple Jobs exists, running Remove-Job...") Start-Sleep -Milliseconds 2250 Get-Job | Remove-Job } If (Get-PSSession | Where-Object { $_.ConfigurationName -eq "Microsoft.Exchange" -and $_.State -eq "Opened" }) { Write-Log -Type INFO -Text ("Getting current Exchange PS Session") If (-NOT $CurrentPSSession) { $CurrentPSSession = Get-PSSession -InstanceId (Get-OrganizationConfig).RunspaceId.Guid } Write-Log -Type INFO -Text ("PS Session Connected to: {0}" -f $CurrentPSSession.ComputerName) $ScriptBlock = { Get-Mailbox -ResultSize Unlimited -WA SilentlyContinue | Select-Object PrimarySMTPAddress } Invoke-Command -Session $CurrentPSSession -ScriptBlock $ScriptBlock -AsJob -JobName "GetMailboxes" | Out-Null Write-Host "`n`r Getting Mailboxes, please wait..." -NoNewline While ((Get-Job -Name GetMailboxes).State -eq "Running") { Write-Host "." -NoNewline Start-Sleep -Milliseconds 7500 } If ((Get-Job -Name GetMailboxes).State -eq "Failed") { Write-Host "FAILED!" -ForegroundColor White -BackgroundColor Red Write-Log -Type ERROR -Text ("Background Job Failed running Get-Mailboxes, using alternate method (Jobless)") $Script:MailboxObjects = Get-Mailbox -ResultSize Unlimited -WA SilentlyContinue | Where-Object { $_.PrimarySMTPAddress -notlike "extest*" } | Select-Object PrimarySMTPAddress Write-Log -Type INFO -Text ("Mailbox Objects to process: {0:N0}" -f $MailboxObjects.Count) } ElseIf ((Get-Job -Name GetMailboxes).State -eq "Completed") { Write-Host "SUCCESS! " -ForegroundColor Green $Script:MailboxObjects = Get-Job -Name GetMailboxes | Receive-Job $MailboxObjects = $MailboxObjects | Where-Object { $_.PrimarySMTPAddress -notlike "extest*" } | Select-Object PrimarySMTPAddress If (($Script:MailboxObjects | Measure-Object).Count -gt 0) { Get-Job -Name GetMailboxes | Remove-Job } Write-Log -Type INFO -Text ("Mailbox Objects to process: {0:N0}" -f $MailboxObjects.Count) Start-Sleep -Milliseconds 2250 } Else { Write-Host "UNKNOWN!" -ForegroundColor Black -BackgroundColor Gray Write-Log -Type ERROR -Text ("Getting Mailboxes failed in an UNKNOWN State") Return } } Else { Write-Log -Type ERROR -Text ("Failed to find a valid PowerShell Session") Return } } 1 { Write-Log -Type INFO -Text "// ALL MAILBOXES SELECTION | User Cancelled the operation!" Return } } } } #endregion [System.Console]::Clear() $InfoBanner = (@" ################################################################################ # LARGE ITEM CHECK PRE-EXECUTION REPORT # ----------------------------------------------------------------------------- # [*] SERVICE ACCOUNT: $ServiceAccountName # [*] SIZE LIMIT: $ItemSizeLimit # [*] EXCHANGE VERSION: $ExchangeVersion # [*] PS SESSION: $($CurrentPSSession.ComputerName) # [*] MAILBOX LOCATION: $MailboxLocation # [*] ACTION: $Action # [*] LARGE ITEM FOLDER: $LargeFolderName # [*] CENTRALIZE EXPORT: $CentralizedExport # [*] EXPORT PATH: $ExportPath # [*] MAILBOX SELECTION: $MailboxSelection # [*] MAILBOX OBJECTS: $("{0:N0}" -f ($MailboxObjects | Measure-Object).Count) # ################################################################################ Press any key to continue or [N] to cancel "@) [System.Console]::ForegroundColor = "Cyan" $Execute = Read-Host -Prompt $InfoBanner [System.Console]::ResetColor() If ($Execute.ToUpper() -eq "N") { Write-Log -Type INFO -Text (" // PRE-EXECUTION REPORT | User cancelled the operation") Return } Write-Debug "Start Foreach Loop" $i = 0 $Count = ($MailboxObjects | Measure-Object).Count Foreach ($Mailbox in $MailboxObjects) { If ($Count -gt 1) { Write-Progress -Id 7 -Activity ("Current User: {0}" -f $Mailbox.PrimarySMTPAddress) -Status ("[ACTION: {0}] Processing Mailboxes..." -f $Action) -CurrentOperation ("Mailbox {0} of {1}" -f ($i + 1), $Count) -PercentComplete (($i / $Count) * 100) } Write-Log -Type INFO -Text ("MAILBOX: {0} | Getting Active Directory Properties" -f $Mailbox.PrimarySMTPAddress) $Script:ADInfo = Get-ADProperties -Property mail -Value $Mailbox.PrimarySMTPAddress If ($Action -eq "ExportLargeItems") { Write-Debug "BEGIN ExportLargeItems" If ($CentralizedExport) { Export-LargeItems -Identity $Mailbox.PrimarySMTPAddress -Path $ExportPath -FolderName $LargeFolderName } Else { Export-LargeItems -Identity $Mailbox.PrimarySMTPAddress -Path $ADInfo.homedirectory -FolderName $LargeFolderName } } Else { Write-Log -Type INFO -Text ("MAILBOX: {0} | Creating EWS Impersonation Service Object" -f $Mailbox.PrimarySMTPAddress) Write-Debug "Creating EWS Service" If ([System.String]::IsNullOrEmpty($Uri.AbsoluteUri)) { $EWSService = New-ImpersonationService -Identity $Mailbox.PrimarySMTPAddress -ImpersonatorAccountName $ServiceAccountName -ImpersonatorAccountPassword $ServicePassword -ExchangeVersion Exchange2013_SP1 } Else { $EWSService = New-ImpersonationService -Identity $Mailbox.PrimarySMTPAddress -ImpersonatorAccountName $ServiceAccountName -ImpersonatorAccountPassword $ServicePassword -ExchangeVersion Exchange2013_SP1 -Uri $uri.AbsoluteUri } <# Commented out due to Exchange 2010 reaching end of support Switch ($ExchangeVersion) { "E14" { If ([System.String]::IsNullOrEmpty($Uri.AbsoluteUri)) { $EWSService = New-ImpersonationService -Identity $Mailbox.PrimarySMTPAddress -ImpersonatorAccountName $ServiceAccountName -ImpersonatorAccountPassword $ServicePassword -ExchangeVersion Exchange2010_SP2 } Else { $EWSService = New-ImpersonationService -Identity $Mailbox.PrimarySMTPAddress -ImpersonatorAccountName $ServiceAccountName -ImpersonatorAccountPassword $ServicePassword -ExchangeVersion Exchange2010_SP2 -Uri $uri.AbsoluteUri } } "E15" { If ([System.String]::IsNullOrEmpty($Uri.AbsoluteUri)) { $EWSService = New-ImpersonationService -Identity $Mailbox.PrimarySMTPAddress -ImpersonatorAccountName $ServiceAccountName -ImpersonatorAccountPassword $ServicePassword -ExchangeVersion Exchange2013_SP1 } Else { $EWSService = New-ImpersonationService -Identity $Mailbox.PrimarySMTPAddress -ImpersonatorAccountName $ServiceAccountName -ImpersonatorAccountPassword $ServicePassword -ExchangeVersion Exchange2013_SP1 -Uri $uri.AbsoluteUri } } } #> Write-Log -Type INFO -Text ("MAILBOX: {0} | Attempting to get Mailbox Folders ({1})" -f $Mailbox.PrimarySMTPAddress, $MailboxLocation) Write-Debug "Get Mailbox Folders" $MailboxFolders = Get-MailboxFolders -Service $EWSService -SearchLocation $MailboxLocation Write-Log -Type INFO -Text ("MAILBOX: {0} | Attempting to get Mailbox Large Items" -f $Mailbox.PrimarySMTPAddress) Write-Debug "Get Mailbox Large Items" If ($MailboxFolders.DisplayName -contains $LargeFolderName) { $MailboxFolders = $MailboxFolders | Where-Object { $_.DisplayName -ne $LargeFolderName } } $MailboxLargeItems = Get-FolderItems -ItemSizeLimit $ItemSizeLimit -Folders $MailboxFolders -Service $EWSService -Action $Action If (($MailboxLargeItems | Measure-Object).Count -gt 0) { Write-Log -Type INFO -Text ("MAILBOX: {0} | Merging Mailbox Large Items to Large Item Output" -f $Mailbox.PrimarySMTPAddress) Write-Debug "Merge Large Items" $MailboxLargeItems | ForEach-Object { [Void]$Script:AllLargeItems.Add($_) } If ($Action -eq "MoveAndExportItems") { If ($CentralizedExport) { Export-LargeItems -Identity $Mailbox.PrimarySMTPAddress -Path $ExportPath -FolderName $LargeFolderName } Else { Export-LargeItems -Identity $Mailbox.PrimarySMTPAddress -Path $ADInfo.homedirectory -FolderName $LargeFolderName } } } } $i++ } Write-Progress -Id 7 -Activity ("Current User: {0}" -f $Mailbox.PrimarySMTPAddress) -Completed Write-Debug "end of loop" If (($Script:AllLargeItems | Measure-Object).Count -gt 0) { $Script:AllLargeItems | New-LargeItemReport } } Catch { Write-Debug "Catch block" Write-Log -Type ERROR -Text ("Function terminated unexpectedly, details saved to $ScriptPath\ErrorOutput.txt") -Verbose $ErrorOutput = $_ | Get-ErrorDetails -ScriptSyntax $PSCmdlet.MyInvocation.Line [System.Console]::ForegroundColor = "Red" $ErrorOutput [System.Console]::ResetColor() $ErrorOutput | Out-File "$ScriptPath\ErrorOutput.txt" -Force } Finally { $Stopwatch.Stop() $ElapsedTime = ("{0} Days, {1} Hours, {2} Minutes, {3} Seconds" -f $StopWatch.Elapsed.Days, $StopWatch.Elapsed.Hours, $StopWatch.Elapsed.Minutes, $StopWatch.Elapsed.Seconds) Write-Log -Type INFO -Text ("Large Items found: {0:N0}" -f $Script:AllLargeItems.Count) -Verbose Write-Log -Type INFO -Text ("Log file: {0}" -f $LogFile) -Verbose Write-Log -Type INFO -Text ("Process Completed | $ElapsedTime") -Verbose } } Function New-ImpersonationService { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [System.String]$Identity, [Parameter(Mandatory = $true)] [System.String]$ImpersonatorAccountName, [Parameter(Mandatory = $true)] [System.String]$ImpersonatorAccountPassword, [Parameter(Mandatory = $true)] [ValidateSet("Exchange2010_SP2", "Exchange2013_SP1")] $ExchangeVersion, [System.URI]$Uri ) BEGIN { TRY { If (-NOT [System.String]::IsNullOrEmpty($MBX.DisplayName)) { $Identity = $MBX.DisplayName } Write-Log -Type INFO -Text ("MAILBOX: {0} | Validating Installation of EWS Managed API" -f $Identity) $EWSRegistryPath = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Exchange\Web Services' -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1 -ExpandProperty Name If ($EWSRegistryPath) { $EWSInstallDirectory = (Get-ItemProperty -Path Registry::$EWSRegistryPath).'Install Directory' $EWSVersion = $EWSInstallDirectory.SubString(($EWSInstallDirectory.Length - 4), 3) $EWSDLL = $EWSInstallDirectory + "Microsoft.Exchange.WebServices.dll" If (Test-Path $EWSDLL) { If ($EWSVersion -lt 2.0) { $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::New( [System.ArgumentOutOfRangeException]::New("EWS Version is too old, Please install EWS Managed API 2.0 or later"), "EWSVersionOutOfDate", [System.Management.Automation.ErrorCategory]::InvalidResult, $EWSVersion ) ) } Else { Write-Log -Type INFO -Text ("MAILBOX: {0} | EWS Managed API 2.0 or later is INSTALLED" -f $Identity) Import-Module $EWSDLL } } Else { $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::New( [System.IO.FileNotFoundException]::New("Unable to find EWS Managed API DLL"), "FileNotFound", [System.Management.Automation.ErrorCategory]::ObjectNotFound, $EWSDLL ) ) } } Else { $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::New( [System.IO.FileNotFoundException]::New("EWS Managed API Registry Path Not Found"), "FileNotFound", [System.Management.Automation.ErrorCategory]::ObjectNotFound, "HKLM:\SOFTWARE\Microsoft\Exchange\Web Services" ) ) } } CATCH { $PSCmdlet.ThrowTerminatingError($PSItem) } } PROCESS { TRY { <# SSL Check / Bypass functionality . [AUTHOR] . Carter Shanklin . . [URL] . http://poshcode.org/624 #> $Provider = New-Object Microsoft.CSharp.CSharpCodeProvider $Provider.CreateCompiler() $Params = New-Object System.CodeDom.Compiler.CompilerParameters $Params.GenerateExecutable = $False $Params.GenerateInMemory = $True $Params.IncludeDebugInformation = $False [Void]$Params.ReferencedAssemblies.Add("System.DLL") $TASource = @" namespace Local.ToolkitExtensions.Net.CertificatePolicy { public class TrustAll : System.Net.ICertificatePolicy { public TrustAll() {} public bool CheckValidationResult( System.Net.ServicePoint sp, System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem ) {return true;} } } "@ $TAResults = $Provider.CompileAssemblyFromSource($Params, $TASource) $TAAssembly = $TAResults.CompiledAssembly $TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") [System.Net.ServicePointManager]::CertificatePolicy = $TrustAll Write-Debug ("[{0}] Check Exchange Version" -f $PSCmdlet.MyInvocation.MyCommand) Switch ($ExchangeVersion) { "Exchange2010_SP2" { Write-Log -Type INFO -Text ("MAILBOX: {0} | Creating EWS Service Object (Exchange2010_SP2)" -f $Identity) [Microsoft.Exchange.WebServices.Data.ExchangeService]$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2) } "Exchange2013_SP1" { Write-Log -Type INFO -Text ("MAILBOX: {0} | Creating EWS Service Object (Exchange2013_SP1)" -f $Identity) [Microsoft.Exchange.WebServices.Data.ExchangeService]$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1) } } $Service.Credentials = New-Object Net.NetworkCredential($ImpersonatorAccountName, $ImpersonatorAccountPassword) Write-Debug ("[{0}] Check for Uri value to determine Autodiscover address" -f $PSCmdlet.MyInvocation.MyCommand) If ([String]::IsNullOrEmpty($Uri)) { Write-Log -Type INFO -Text ("MAILBOX: {0} | Autodiscover in process" -f $Identity) $Service.AutodiscoverUrl($Identity, { $True }) Write-Log -Type INFO -Text ("MAILBOX: {0} | Using EWS URL: {1}" -f $Identity, $Service.Url) } Else { $Service.Url = $Uri.AbsoluteUri } Write-Log -Type INFO -Text ("MAILBOX: {0} | Attemping to Impersonate {1}" -f $Identity, $Identity) $Service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $Identity) #Increase the timeout for larger mailboxes $Service.Timeout = 150000 Return $Service } CATCH { Write-Log -Type ERROR -Text ("MAILBOX: {0} | Failed to Impersonate the User" -f $Identity) Write-Log -Type ERROR -Text ("{0}" -f $_.Exception.Message) Continue } } } Function Get-MailboxFolders { [CmdletBinding()] Param( [Parameter(Mandatory = $true)] $Service, [Parameter(Mandatory = $true)] [ValidateSet("Primary", "Archive", "RecoverableItems")] [String]$SearchLocation ) try { [Microsoft.Exchange.WebServices.Data.FolderView]$View = New-Object Microsoft.Exchange.WebServices.Data.FolderView([System.Int32]::MaxValue) $View.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly) $View.PropertySet.Add([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName) $View.PropertySet.Add([Microsoft.Exchange.WebServices.Data.FolderSchema]::ChildFolderCount) $View.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep Switch ($SearchLocation) { "Primary" { Write-Log -Type INFO -Text ("MAILBOX: {0} | Finding WellKnownFolders in MsgFolderRoot" -f $Service.ImpersonatedUserId.Id) [Microsoft.Exchange.WebServices.Data.FindFoldersResults]$Folders = $Service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot, $View) Write-Log -Type INFO -Text ("MAILBOX: {0} | Found {1} folders (PRIMARY MAILBOX)" -f $Service.ImpersonatedUserId.Id, $Folders.TotalCount) } "Archive" { Write-Log -Type INFO -Text ("MAILBOX: {0} | Finding WellKnownFolders in ArchiveMsgFolderRoot" -f $Service.ImpersonatedUserId.Id) [Microsoft.Exchange.WebServices.Data.FindFoldersResults]$Folders = $Service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveMsgFolderRoot, $View) Write-Log -Type INFO -Text ("MAILBOX: {0} | Found {1} folders (ARCHIVE MAILBOX)" -f $Service.ImpersonatedUserId.Id, $Folders.TotalCount) } "RecoverableItems" { Write-Log -Type INFO -Text ("MAILBOX: {0} | Finding WellKnownFolders in RecoverableItemsRoot" -f $Service.ImpersonatedUserId.Id) [Microsoft.Exchange.WebServices.Data.FindFoldersResults]$Folders = $Service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecoverableItemsRoot, $View) Write-Log -Type INFO -Text ("MAILBOX: {0} | Found {1} folders (RECOVERABLE ITEMS STORE)" -f $Service.ImpersonatedUserId.Id, $Folders.TotalCount) } } Return $Folders } CATCH { Write-Log -Type ERROR -Text ("MAILBOX: {0} | Failed to get Mailbox Folders ({1})" -f $Service.ImpersonatedUserId.Id, $SearchLocation) Write-Log -Type ERROR -Text ("{0}" -f $_.Exception.Message) Continue } } Function Get-FolderItems { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [int]$ItemSizeLimit, [Parameter(Mandatory = $true)] $Folders, [Parameter(Mandatory = $true)] $Service, [Parameter(Mandatory = $true)] $Action ) $LargeItemCount = 0 [System.Collections.ArrayList]$colLargeItems = @() $fldrIndex = 0 foreach ($Folder in $Folders) { $CurrentFolder = $Folder.DisplayName Write-Progress -ParentId 7 -Id 42 -Activity ("Current Folder: {0}" -f $CurrentFolder) -Status "Checking folders for items larger than: $ItemSizeLimit MB" -CurrentOperation ("Processing: {0:N0} of {1:N0} | Large Items: {2}" -f ($fldrIndex + 1), $Folders.Count, $LargeItemCount) -PercentComplete (($fldrIndex / $Folders.count) * 100) $Items = $Null $PageSize = 1000 $Offset = 0 $MoreItemsAvailable = $True Write-Log -Type INFO -Text ("MAILBOX: {0} | Started Processing folder: {1}" -f $Service.ImpersonatedUserId.Id, $CurrentFolder) $TotalItems = 0 Do { try { $ItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView($PageSize, $Offset, [Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning) $PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly) $ItemView.PropertySet = $PropertySet $ItemView.PropertySet.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject) $ItemView.PropertySet.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size) $ItemView.PropertySet.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass) $ItemView.PropertySet.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeCreated) Write-Debug "FindItems Method on Folder: $CurrentFolder" $Items = $Folder.FindItems($ItemView) } catch { Write-Debug ("CATCH BLOCK -> Folder: {0}" -f $CurrentFolder) Write-Log -Type ERROR -Text ("MAILBOX: {0}, FOLDER: {1} | Unable to read items, SKIPPING Folder" -f $Service.ImpersonatedUserId.Id, $CurrentFolder) Write-Log -Type ERROR -Text ("{0}" -f $_.Exception.Message) $MoreItemsAvailable = $False Break } $TotalItems = ($TotalItems + ($Items | Measure-Object).Count) Write-Progress -ParentId 42 -Activity ("Folder contains MORE THAN {0:N0} Items: {1}" -f $PageSize, $Items.MoreAvailable) -Status ("Processing items...") -CurrentOperation ("Items in folder: {0:N0}" -f $TotalItems) -PercentComplete -1 $LargeItems = $null $LargeItems = $Items | Where-Object { [Math]::Round(($_.Size / 1000000), 2) -gt $ItemSizeLimit } If (($LargeItems | Measure-Object).Count -gt 0) { Write-Debug ("Found {0} Items Larger than {1} MB" -f ($LargeItems | Measure-Object).Count, $ItemSizeLimit) Foreach ($Item in $LargeItems) { If ([System.String]::IsNullOrEmpty($Item.Subject)) { $Subject = "NULL" $ItemViolation = New-LargeItemViolation -SMTPAddress $Service.ImpersonatedUserId.Id -Created $Item.DateTimeCreated -Subject $Subject -FolderDisplayName $Folder.DisplayName -Size $Item.Size -ItemClass $Item.ItemClass [Void]$colLargeItems.Add($ItemViolation) } Else { Write-Debug ("[{0}] - Logging Item Violation" -f $PSCmdlet.MyInvocation.MyCommand) $ItemViolation = New-LargeItemViolation -SMTPAddress $Service.ImpersonatedUserId.Id -Created $Item.DateTimeCreated -Subject $Item.Subject -FolderDisplayName $Folder.DisplayName -Size $Item.Size -ItemClass $Item.ItemClass [Void]$colLargeItems.Add($ItemViolation) } If ($Action -eq "MoveLargeItems" -or $Action -eq "MoveAndExportItems") { Write-Debug ("Moving Large Items to: {0}" -f $LargeFolderName) $LargeItemFolder = New-MailboxFolder -FolderName $LargeFolderName -Service $Service If ($LargeItemFolder) { Write-Log -Type INFO -Text ("MAILBOX: {0} | Moving Item [{1}] from folder [{2}] to folder [{3}]" -f $Service.ImpersonatedUserId.Id, $Item.Subject, $CurrentFolder, $LargeFolderName) [void]$Item.Move($LargeItemFolder.Id) } Else { Write-Log -Type ERROR -Text ("MAILBOX: {0} | Unable to Move Item [{1}], FAILED to create folder [{2}]" -f $Service.ImpersonatedUserId.Id, $Item.Subject, $LargeFolderName) } } $LargeItemCount++ } } If ($Items.MoreAvailable -eq $False) { $MoreItemsAvailable = $false Write-Log -Type INFO -Text ("MAILBOX: {0} | Finished Processing folder: {1} ({2:N0} Items)" -f $Service.ImpersonatedUserId.Id, $CurrentFolder, $TotalItems) } ElseIf ($Items.MoreAvailable -eq $true) { $Offset += $PageSize } } While ($MoreItemsAvailable) Write-Progress -ParentId 42 -Activity ("Processing items...") -Completed $fldrIndex++ } Write-Progress -Id 1 -Activity "Checking folders for items larger than: $ItemSizeLimit MB" -Completed Write-Log -Type INFO -Text ("MAILBOX: {0} | Number of Large Items found: {1}" -f $Service.ImpersonatedUserId.Id, $LargeItemCount) $colLargeItems } Function New-LargeItemViolation { [CmdletBinding()] Param ( [System.String]$SMTPAddress, [System.String]$Subject, [System.String]$ItemClass, [System.String]$FolderDisplayName, [System.DateTime]$Created, [System.Int32]$Size ) Write-Debug "Item Violation" $Violations = [PSCustomObject][Ordered]@{ PrimarySMTPAddress = $SMTPAddress Subject = $Subject ItemClass = $ItemClass FolderName = $FolderDisplayName CreatedDate = [DateTime]$Created Size = ("{0:N2}" -f [Math]::Round($Size / 1000000, 2)) } $Violations } Function New-MailboxFolder { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String]$FolderName, [Parameter(Mandatory = $true)] $Service ) try { Write-Debug "start" $FolderID = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot) $FolderRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, $FolderID) $View = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1) $Filter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName, $FolderName) $LargeItemFolder = $Service.FindFolders($FolderRoot.Id, $Filter, $View) If ($LargeItemFolder.DisplayName -ne $FolderName) { Write-Log -Type INFO -Text ("MAILBOX: {0} | Folder {1} was not found, creating the folder" -f $Service.ImpersonatedUserId.Id, $FolderName) $LargeItemFolder = New-Object Microsoft.Exchange.WebServices.Data.Folder($Service) $LargeItemFolder.DisplayName = $FolderName $LargeItemFolder.FolderClass = "IPF.Note" $LargeItemFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot) $LargeItemFolder } Else { $LargeItemFolder } } catch { Write-Log -Type ERROR -Text ("MAILBOX: {0} | Unable to create or find the {1} Folder" -f $Service.ImpersonatedUserId.Id, $FolderName) Write-Log -Type ERROR -Text $_.Exception.Message } } Function New-LargeItemReport { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNull()] $InputObject ) begin { $OutputFile = ("{0}\{1}_Large_Item_Violations.csv" -f $ScriptPath, (Get-Date -Format yyyyMMdd_HHmmss)) Show-Menu -Title ("Creating the Large Item Report: {0}" -f $OutputFile) -DisplayOnly -Style Info -Color Green Write-Log -Type INFO -Text ("Exporting records to {0}" -f $OutputFile) -Verbose } process { $InputObject | Export-Csv $OutputFile -NoTypeInformation -Append } } Function Export-LargeItems { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [ValidateNotNull()] $Identity, [Parameter(Mandatory = $true)] [ValidateNotNull()] [System.String]$Path, [Parameter(Mandatory = $true)] [ValidateNotNull()] [System.String]$FolderName ) If (Test-Path $Path) { Write-Log -Type INFO -Text ("MAILBOX: {0} | Export Path Validated ({1})" -f $Identity, $Path) Write-Log -Type INFO -Text ("MAILBOX: {0} | Attempting to Export Large Item Folder to PST" -f $Identity) If (Get-MailboxExportRequest -Mailbox $Identity) { Write-Warning ("[{0}] | Found Existing Mailbox Export Request!" -f $Identity) Write-Log -Type INFO -Text ("MAILBOX: {0} | FOUND and REMOVING Mailbox Export Request" -f $Identity) Get-MailboxExportRequest -Mailbox $Identity | Remove-MailboxExportRequest -Confirm:$False Write-Log -Type INFO -Text ("MAILBOX: {0} | Creating New Mailbox Export Request" -f $Identity) $NewMERObject = New-MailboxExportRequest -Name ("{0}_LargeItems" -f $ADInfo.samaccountname) -Mailbox $Identity -FilePath ("{0}\{1}_LargeItems.pst" -f $Path, $ADInfo.samaccountname) -IncludeFolders $FolderName -Confirm:$False -ExcludeDumpster -ErrorAction SilentlyContinue If ($NewMERObject) { Write-Log -Type INFO -Text ("MAILBOX: {0} | New Mailbox Export Request created SUCCESSFULLY" -f $Identity) } Else { Write-Warning ("[{0}] | FAILED to Create New Mailbox Export Request" -f $Identity) Write-Log -Type WARNING -Text ("MAILBOX: {0} | New Mailbox Export Request FAILED" -f $Identity) } } Else { Write-Log -Type INFO -Text ("MAILBOX: {0} | Creating New Mailbox Export Request" -f $Identity) Write-Debug "Create New Export Request" $NewMERObject = New-MailboxExportRequest -Name ("{0}_LargeItems" -f $ADInfo.samaccountname) -Mailbox $Identity -FilePath ("{0}\{1}_LargeItems.pst" -f $Path, $ADInfo.samaccountname) -IncludeFolders $FolderName -Confirm:$False -ExcludeDumpster -ErrorAction SilentlyContinue If ($NewMERObject) { Write-Log -Type INFO -Text ("MAILBOX: {0} | New Mailbox Export Request created SUCCESSFULLY" -f $Identity) } Else { Write-Warning ("[{0}] | FAILED to Create New Mailbox Export Request" -f $Identity) Write-Log -Type WARNING -Text ("MAILBOX: {0} | New Mailbox Export Request FAILED" -f $Identity) } } } Else { Write-Warning ("[{0}] | FAILED to Validate the Export Path" -f $Identity) Write-Log -Type WARNING -Text ("MAILBOX: {0} | Export to PST failed becasue the path: {1} was not found" -f $Identity, $Path) } } Function Get-ErrorDetails { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $ErrorRecord, $ScriptSyntax ) process { If ($ErrorRecord -is [Management.Automation.ErrorRecord]) { [PSCustomObject]@{ Reason = $ErrorRecord.CategoryInfo.Reason Exception = $ErrorRecord.Exception.Message Target = $ErrorRecord.CategoryInfo.TargetName Script = $ErrorRecord.InvocationInfo.ScriptName Syntax = $ScriptSyntax Command = $ErrorRecord.InvocationInfo.MyCommand LineNumber = $ErrorRecord.InvocationInfo.ScriptLineNumber Column = $ErrorRecord.InvocationInfo.OffsetInLine Date = Get-Date User = $env:USERNAME } } } } Function Get-ChoicePrompt { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String[]]$OptionList, [Parameter(Mandatory = $false)] [String]$Title, [Parameter(Mandatory = $False)] [String]$Message = $null, [int]$Default = 0 ) $Options = New-Object System.Collections.ObjectModel.Collection[System.Management.Automation.Host.ChoiceDescription] $OptionList | ForEach-Object { $Options.Add((New-Object "System.Management.Automation.Host.ChoiceDescription" -ArgumentList $_)) } $Host.ui.PromptForChoice($Title, $Message, $Options, $Default) } Function Show-Menu { Param( [Parameter(Mandatory = $true)] [System.String]$Title, [System.String]$Menu, [Switch]$ClearScreen, [Switch]$DisplayOnly, [ValidateSet("Full", "Mini", "Info", "None")] $Style, [ValidateSet("White", "Cyan", "Magenta", "Yellow", "Green", "Red", "Gray", "DarkGray")] $Color = "Gray" ) If ($ClearScreen) { [System.Console]::Clear() } Switch ($Style) { "Full" { $menuPrompt = "/" * (95) $menuPrompt += "`n`r////`n`r//// $Title`n`r////`n`r" $menuPrompt += "/" * (95) $menuPrompt += "`n`n" } "Mini" { $menuPrompt = "`n`r" $menuPrompt += "\" * (80) $menuPrompt += "`n\\\\ $Title`n" $menuPrompt += "\" * (80) $menuPrompt += "`n" } "Info" { $menuPrompt = "`n`r" $menuPrompt += "-" * (80) $menuPrompt += "`n-- $Title`n" $menuPrompt += "-" * (80) $menuPrompt += "`n" } "None" { $menuPrompt = $Title } Default { $menuPrompt = "`n`r" $menuPrompt += "\" * (80) $menuPrompt += "`n\\\\ $Title`n" $menuPrompt += "\" * (80) $menuPrompt += "`n" } } [System.Console]::ForegroundColor = $Color If ($DisplayOnly) { Write-Host $menuPrompt } Else { $menuPrompt += $menu Read-Host -Prompt $menuprompt } [System.Console]::ResetColor() } Function Write-Log { [CmdletBinding()] Param ( [Parameter(Mandatory = $false)] [ValidateSet("INFO", "WARNING", "ERROR", "DEBUG")] [String]$Type = "INFO", [String]$Text ) If (-Not ([System.String]::IsNullOrEmpty($Logfile))) { If (-Not (Test-Path -Path $LogFile)) { If ($VerbosePreference -eq "Continue") { Write-Verbose "[$Type] - $Text" } Else { If ($Type -eq "WARNING") { Write-Host "[$Type] - $Text" -ForegroundColor Yellow } If ($Type -eq "ERROR") { Write-Host "[$Type] - $Text" -ForegroundColor Red } } New-Item $LogFile -ItemType File -Force | Out-Null $fsMode = [System.IO.FileMode]::Append $fsAccess = [System.IO.FileAccess]::Write $fsSharing = [System.IO.FileShare]::Read $fsLog = New-Object System.IO.FileStream($Logfile, $fsMode, $fsAccess, $fsSharing) $swLog = New-Object System.IO.StreamWriter($fsLog) $swLog.WriteLine("$(Get-Date), [$Type], ====> $Text") $swLog.Close() } Else { If ($VerbosePreference -eq "Continue") { Write-Verbose "[$Type] - $Text" } Else { If ($Type -eq "WARNING") { Write-Host "[$Type] - $Text" -ForegroundColor Yellow } If ($Type -eq "ERROR") { Write-Host "[$Type] - $Text" -ForegroundColor Red } } $fsMode = [System.IO.FileMode]::Append $fsAccess = [System.IO.FileAccess]::Write $fsSharing = [System.IO.FileShare]::Read $fsLog = New-Object System.IO.FileStream($Logfile, $fsMode, $fsAccess, $fsSharing) $swLog = New-Object System.IO.StreamWriter($fsLog) $swLog.WriteLine("$(Get-Date), [$Type], ====> $Text") $swLog.Close() } } Else { Write-Host "//MISSING LOGFILE// [$Type] - $Text" -ForegroundColor Yellow } } Function Get-ADProperties { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String]$Property, [Parameter(Mandatory = $true)] [String]$Value ) $ADFilter = "(&($Property=$Value))" $ADSearch = New-Object System.DirectoryServices.DirectorySearcher $ADSearch.ClientTimeout = "00:00:15" $ADSearch.ServerTimeLimit = "00:00:30" $ADSearch.ServerPageTimeLimit = "00:00:15" $ADSearch.Filter = $ADFilter $colPropList = @("samaccountname", "mailnickname", "homedirectory") $ADSearch.PropertiesToLoad.AddRange($colPropList) $ADResult = $ADSearch.FindOne() $ADInfo = $ADResult | Select-Object ` @{N = "samaccountname"; E = { $_.Properties["samaccountname"] } }, @{N = "mailnickname"; E = { $_.Properties["mailnickname"] } }, @{N = "homedirectory"; E = { $_.Properties["homedirectory"] } } Return $ADInfo } Function Test-ConsoleRights { Try { If (-Not ([security.principal.windowsprincipal][security.principal.windowsidentity]::GetCurrent()).IsInRole([security.principal.windowsbuiltinrole] "administrator")) { Return $false } return $true } Catch [System.Exception] { Throw "Unable to determine if console is running with elevated permissions"; Return } } Function Test-ConsoleVersion { Try { If ($PSVersionTable.PSVersion.Major -gt "2") { Return $true } Return $false } Catch [System.Exception] { Throw "Unable to determine console version"; Return } } Function Get-LargeFolderName { [CmdletBinding()] Param() Show-Menu -Title "Function: Get-LargeFolderName" -DisplayOnly -Style Info -Color White $Complete = $false $Script:LargeFolderName = Read-Host "Enter the folder name where Large Items will be moved" Write-Host ("`n`rLarge Folder Name: {0}" -f $Script:LargeFolderName) -ForegroundColor Cyan Do { Switch (Get-ChoicePrompt -OptionList "&Yes", "&No" -Message "`nIs the Large Item Folder name correct?" -Default 1) { "0" { Write-Host "`nLarge Item Folder stored as `$Script:LargeFolderName`n" -ForegroundColor Green $Complete = $true } "1" { Get-LargeFolderName } } } While ($Complete -eq $false) } Function Get-SingleMailboxUser { [CmdletBinding()] Param() Write-Host "`n" Show-Menu -Title "Function: Get-SingleMailboxUser" -DisplayOnly -Style Info -Color White $SingleMailboxUser = [PSCustomObject] @{ PrimarySMTPAddress = (Read-Host "Enter the PrimarySMTPAddress of the Mailbox User") } Write-Host ("`n`rPrimarySMTPAddress: {0}" -f $SingleMailboxUser.PrimarySMTPAddress) -ForegroundColor Cyan Switch (Get-ChoicePrompt -OptionList "&Yes", "&No" -Message "`nIs the Mailbox User PrimarySMTPAddress correct?" -Default 1) { "0" { Return $SingleMailboxUser } "1" { Get-SingleMailboxUser } } } Function Get-NetworkSharePath { [CmdletBinding()] Param() Show-Menu -Title "Function: Get-NetworkSharePath" -DisplayOnly -Style Info -Color White $Complete = $false $ServerName = Read-Host " Enter the Server FQDN for the Network Share" $ShareName = Read-Host " Enter the Share Folder Name (Hidden shared allowed)" $NetworkSharePath = ("\\{0}\{1}" -f $ServerName, $ShareName) Write-Host ("`n`r Network Share Path: {0}" -f $NetworkSharePath) -ForegroundColor Cyan Do { Switch (Get-ChoicePrompt -OptionList "&Yes", "&No" -Title "`n Is the Network Share Path correct?`n" -Message $null -Default 1) { "0" { $Complete = $true } "1" { Get-NetworkSharePath } } } While ($Complete -eq $false) Return $NetworkSharePath } Function Get-CSVFileDialog { Param () [System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog $OpenFileDialog.Title = "[LargeItemChecks] CSV Import File" $OpenFileDialog.initialDirectory = [System.Environment]::GetFolderPath("Desktop") $OpenFileDialog.filter = "CSV files (*.csv)| *.csv| All files (*.*)| *.*" $Result = $OpenFileDialog.ShowDialog() If ($Result -eq "OK") { $File = $OpenFileDialog.filename Return $File } Else { Return } } #endregion |