<# .NOTES Name: OnlineArchiveReport-GUI.ps1 Authors: Agustin Gallegos THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.   .SYNOPSIS Get reports for Mailboxes and Archives hosted in Exchange Online. .DESCRIPTION Get reports for Mailboxes and Archives hosted in Exchange Online. Report can be viewed live in powershell interface, or send as HTML report by email.  .EXAMPLE PS C:\> OnlineArchiveReport-GUI.ps1 -EnableTranscript Connects to the tool and enables PowerShell Transcript .COMPONENT STORE, Archive .ROLE Support #> param( [switch]$EnableTranscript = $False ) $disclaimer = @" ################################################################################# # # The sample scripts are not supported under any Microsoft standard support # program or service. The sample scripts are provided AS IS without warranty # of any kind. Microsoft further 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. # ################################################################################# "@ Write-Host $disclaimer -foregroundColor Yellow Write-Host " " $script:nl = "`r`n" $ProgressPreference = "SilentlyContinue" function GenerateForm { #region Import the Assemblies Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName Microsoft.VisualBasic [System.Windows.Forms.Application]::EnableVisualStyles() #endregion #region Generated Form Objects $MainForm = New-Object System.Windows.Forms.Form $radiobutton1 = New-Object System.Windows.Forms.RadioButton $txtBoxMbxAlias = New-Object System.Windows.Forms.TextBox $radiobutton2 = New-Object System.Windows.Forms.RadioButton $buttonImportFile = New-Object System.Windows.Forms.Button $labImportFileHowTo = New-Object System.Windows.Forms.Label $radiobutton3 = New-Object System.Windows.Forms.RadioButton $HorizontalLine = New-Object System.Windows.Forms.Label $labelRecipients = New-Object System.Windows.Forms.Label $txtBoxRecipients = New-Object System.Windows.Forms.TextBox $checkboxOrgAdmins = New-Object System.Windows.Forms.Checkbox $buttonSendEmail = New-Object System.Windows.Forms.Button $buttonGo = New-Object System.Windows.Forms.Button $buttonExit = New-Object System.Windows.Forms.Button $dgResults = New-Object System.Windows.Forms.DataGridView  $txtBoxResults = New-Object System.Windows.Forms.Label $InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState #endregion Generated Form Objects if ($EnableTranscript) { Start-Transcript } #region Connect to EXO if no existing Session available if ( (Get-PSSession).Computername -notlike "*outlook*" ) { if ( !(Get-Module ExchangeOnlineManagement -ListAvailable) -and !(Get-Module ExchangeOnlineManagement) ) { Install-Module ExchangeOnlineManagement -Force -ErrorAction Stop } Import-Module ExchangeOnlineManagement Connect-ExchangeOnline } #endregion #region Processes #region GetDataProcess $GetDataProcess = { $statusBar.Text = "Running..." $dgResults.Visible = $False $txtBoxResults.Visible = $True if ($radiobutton1.Checked) { $mbxs = Get-EXOmailbox -Identity $txtBoxMbxAlias.text -PropertySets Quota -ErrorAction SilentlyContinue | Select-Object UserPrincipalname, RecoverableItemsQuota } elseif ($radiobutton2.Checked) { $csv = Import-Csv -Path $filename $mbxs = $csv | ForEach-Object { get-EXOmailbox -Identity $_.UserPrincipalName -PropertySets Quota -ErrorAction SilentlyContinue | Select-Object UserPrincipalname, RecoverableItemsQuota } } elseif ($radiobutton3.Checked) { $mbxs = Get-EXOMailbox -ResultSize unlimited -PropertySets Quota -ErrorAction SilentlyContinue | Select-Object UserPrincipalName, RecoverableItemsQuota } if ($null -eq $mbxs) { [Microsoft.VisualBasic.Interaction]::MsgBox("Mailbox(es) doesn't have an archive associated.", [Microsoft.VisualBasic.MsgBoxStyle]::Okonly, "Information Message") } else { $Global:array = New-Object System.Collections.ArrayList # Looping through each mailbox $i = 0 if ($null -eq $mbxs.count) { $mbxcount = 1 } else { $mbxcount = $mbxs.count } foreach ($mbx in $mbxs) { # show progess in text box $i++ $j = 0 # creating variable to store user data $MbxLocation = Get-MailboxLocation -User $mbx.UserPrincipalName Foreach ($MbxLoc in $MbxLocation) { $j++ $output = "Checking user: $i out of: $mbxcount" $output = $output + $nl + "Checking Mailbox object: $j out of: $($MbxLocation.count)" $output = $output + $nl + "This window will refresh automatically ..." $txtBoxResults.Text = $output $MainForm.refresh() # getting current mailbox RI quota and converting to MB $global:RIQuota = $mbx | Select-Object @{Name = "RIQuota"; E = { [math]::Round(($_.RecoverableItemsQuota.ToString().Split("(")[1].Split(" ")[0].Replace(",", "") / 1MB), 3) } } #getting mailbox stats to be displayed $stats = Get-EXOMailboxStatistics -Identity $MbxLoc.MailboxGuid.Guid -Properties lastlogontime | Select-Object ` @{N = "DisplayName"; E = { $MbxLoc.OwnerID } }, ` @{N = "MailboxLocationType"; E = { $MbxLoc.MailboxLocationType } }, ` itemcount, ` lastlogontime, ` totalitemsize, ` @{N = "RecoverableItemsSize"; E = { $_.totaldeleteditemsize } }, ` @{N = "Recoverable Items Usage Percentage"; E = { [math]::Round(($_.TotalDeletedItemSize.Value.ToString().Split("(")[1].Split(" ")[0].Replace(",", "") / 1MB) * 100 / $RIQuota.RIQuota, 3) } } $array.Add($stats) } # sleeping process for 500 milliseconds, to prevents PS micro delays Start-Sleep -Milliseconds 500 } $dgResults.datasource = $array $dgResults.AutoResizeColumns() $dgResults.Visible = $True $txtBoxResults.Visible = $False $MainForm.refresh() Clear-Variable i,j,output,txtBoxResults Write-Host "$((Get-Date).ToString("MM-dd-yyyy HH:mm:ss")) - Collecting user's statistics done" -ForegroundColor Yellow } $statusBar.Text = "Ready" } #endregion #region SendMail $SendMail = { $statusBar.Text = "Sending E-mail..." [string]$html = $array | ConvertTo-Html #Replaces the HTML code with a fancier one $HTML = $HTML.replace(' HTML TABLE ', ' Mailboxes Report

v1.27 (02/09/2022)

Mailbox Report

') $HTML = $HTML.Replace(' ', ' ') $listrecipients = New-Object System.Collections.ArrayList if ( $txtBoxRecipients.text -ne '') { $null = $listrecipients.Add($txtBoxRecipients.text) } # If Switch $OrgAdmins is in use, we will check current admins and include them to the recipients list if ($checkboxOrgAdmins.Checked -eq $True) { $TenantAdmins = Get-RoleGroupMember ((Get-RoleGroup tenantadmins_*).name) foreach ( $admin in $TenantAdmins.Name ) { $null = $listrecipients.Add( (Get-EXOMailbox $admin).PrimarySmtpAddress ) } } $Subject = "Mailbox Report $((Get-Date).ToString("yyyy-MM-dd HH:mm:ss"))" if ($Null -eq $cred) { $Global:cred = Get-Credential -Message "Type your Sender's credentials" } Send-MailMessage -From $cred.UserName -To $listrecipients -Body $html -BodyasHtml -SmtpServer smtp.office365.com -UseSsl -Port 587 -Subject $Subject -Credential $cred Write-Host "$((Get-Date).ToString("MM-dd-yyyy HH:mm:ss")) - E-mail report sent" -ForegroundColor Yellow $statusBar.Text = "Ready" } #endregion #region SelectFile Process $SelectFileProcess = { $statusBar.Text = "Running..." $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog $OpenFileDialog.initialDirectory = $initialDirectory $OpenFileDialog.ShowDialog() | Out-Null if ($OpenFileDialog.filename -ne "") { $Global:Filename = $OpenFileDialog.filename $txtBoxMbxAlias.Text = "...Imported from File..." Write-Host "$((Get-Date).ToString("MM-dd-yyyy HH:mm:ss")) - Select file Operation finished" -ForegroundColor Yellow } $radiobutton2.Checked = $True $statusBar.Text = "Process Completed" } #endregion SelectFile Process #endregion #region Handlers $OnLoadMainWindow_StateCorrection = { #Correct the initial state of the form to prevent the .Net maximized form issue $MainForm.WindowState = $InitialFormWindowState } $handler_labImportFileHowTo_Click = { # Get the link to Permissions link [Microsoft.VisualBasic.Interaction]::MsgBox("CSV file must contain a unique header named 'UserPrincipalName'. You should list a unique UserPrincipalName per line.", [Microsoft.VisualBasic.MsgBoxStyle]::Okonly, "Information Message") } #endregion #---------------------------------------------- #region Generated Form Code #Form $statusStrip = New-Object System.Windows.Forms.StatusStrip $statusStrip.name = "StatusStrip" $statusBar = New-Object System.Windows.Forms.ToolStripStatusLabel $null = $statusStrip.Items.Add($statusBar) $statusBar.Name = "statusBar" $statusBar.Text = "Ready..." $MainForm.Controls.Add($statusStrip) $MainForm.ClientSize = New-Object System.Drawing.Size(1000, 600) $MainForm.DataBindings.DefaultDataSourceUpdateMode = [System.Windows.Forms.DataSourceUpdateMode]::OnValidation $MainForm.Name = "form1" $MainForm.Text = "Online Mailbox and Archive reports" $MainForm.StartPosition = "CenterScreen" $MainForm.KeyPreview = $True $MainForm.Add_KeyDown({ if ($_.KeyCode -eq "Escape") { $MainForm.Close() } }) # # radiobutton1 # $radiobutton1.DataBindings.DefaultDataSourceUpdateMode = [System.Windows.Forms.DataSourceUpdateMode]::OnValidation $radiobutton1.Location = New-Object System.Drawing.Point(20, 20) $radiobutton1.Size = New-Object System.Drawing.Size(230, 20) $radiobutton1.TabIndex = 1 $radiobutton1.Text = "1 - Type the user's UserPrincipalName:" $radioButton1.Checked = $true $radiobutton1.UseVisualStyleBackColor = $True $MainForm.Controls.Add($radiobutton1) # # txtBoxMbxAlias # $txtBoxMbxAlias.DataBindings.DefaultDataSourceUpdateMode = 0 $txtBoxMbxAlias.Location = New-Object System.Drawing.Point(250, 20) $txtBoxMbxAlias.Size = New-Object System.Drawing.Size(150, 20) $txtBoxMbxAlias.Name = "txtBoxMbxAlias" $MainForm.Controls.Add($txtBoxMbxAlias) # # radiobutton2 # $radiobutton2.DataBindings.DefaultDataSourceUpdateMode = [System.Windows.Forms.DataSourceUpdateMode]::OnValidation $radiobutton2.Location = New-Object System.Drawing.Point(20, 60) $radiobutton2.Size = New-Object System.Drawing.Size(150, 20) $radiobutton2.TabIndex = 2 $radiobutton2.Text = "2 - import from CSV" $radioButton2.Checked = $false $radiobutton2.UseVisualStyleBackColor = $True $MainForm.Controls.Add($radiobutton2) # # "ImportFile" button # $buttonImportFile.DataBindings.DefaultDataSourceUpdateMode = 0 $buttonImportFile.ForeColor = [System.Drawing.Color]::FromArgb(255, 0, 0, 0) $buttonImportFile.Location = New-Object System.Drawing.Point(250, 55) $buttonImportFile.Size = New-Object System.Drawing.Size(150, 25) $buttonImportFile.Name = "ImportFile" $buttonImportFile.Text = ">>> Import from CSV <<<" $buttonImportFile.UseVisualStyleBackColor = $True $buttonImportFile.add_Click($SelectFileProcess) $MainForm.Controls.Add($buttonImportFile) # # Label "File how to" # $labImportFileHowTo.Location = New-Object System.Drawing.Point(405, 60) $labImportFileHowTo.Size = New-Object System.Drawing.Size(50, 25) $labImportFileHowTo.Text = "?" $labImportFileHowTo.ForeColor = "Blue" $labImportFileHowTo.add_Click($handler_labImportFileHowTo_Click) $MainForm.Controls.Add($labImportFileHowTo) # # radiobutton3 # $radiobutton3.DataBindings.DefaultDataSourceUpdateMode = [System.Windows.Forms.DataSourceUpdateMode]::OnValidation $radiobutton3.Location = New-Object System.Drawing.Point(20, 100) $radiobutton3.Size = New-Object System.Drawing.Size(300, 15) $radiobutton3.TabIndex = 3 $radiobutton3.Text = "3 - All available mailboxes in the tenant" $radiobutton3.Checked = $false $radiobutton3.UseVisualStyleBackColor = $True $MainForm.Controls.Add($radiobutton3) # # Horizontal Line # $HorizontalLine.Location = New-Object System.Drawing.Point(5, 130) $HorizontalLine.Size = New-Object System.Drawing.Size(990, 2) $HorizontalLine.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D $HorizontalLine.Name = "Recipients" $HorizontalLine.Text = "" $MainForm.Controls.Add($HorizontalLine) # # Label Recipients # $labelRecipients.Location = New-Object System.Drawing.Point(20, 140) $labelRecipients.Size = New-Object System.Drawing.Size(120, 60) $labelRecipients.Name = "Recipients" $labelRecipients.Text = "Recipients to send report (separated by commas):" $MainForm.Controls.Add($labelRecipients) # # TxtBoxRecipients # $TxtBoxRecipients.DataBindings.DefaultDataSourceUpdateMode = 0 $TxtBoxRecipients.Location = New-Object System.Drawing.Point(140, 140) $TxtBoxRecipients.Size = New-Object System.Drawing.Size(350, 20) $TxtBoxRecipients.Name = "TxtBoxRecipients" $MainForm.Controls.Add($TxtBoxRecipients) # # checkboxOrgAdmins # $checkboxOrgAdmins.DataBindings.DefaultDataSourceUpdateMode = 0 $checkboxOrgAdmins.Location = New-Object System.Drawing.Point(500, 140) $checkboxOrgAdmins.Size = New-Object System.Drawing.Size(150, 20) $checkboxOrgAdmins.Name = "checkboxOrgAdmins" $checkboxOrgAdmins.Text = "Include Tenant Admins" $MainForm.Controls.Add($checkboxOrgAdmins) # # buttonSendEmail # $buttonSendEmail.DataBindings.DefaultDataSourceUpdateMode = 0 $buttonSendEmail.ForeColor = [System.Drawing.Color]::FromArgb(255, 0, 0, 0) $buttonSendEmail.Location = New-Object System.Drawing.Point(700, 140) $buttonSendEmail.Size = New-Object System.Drawing.Size(80, 25) $buttonSendEmail.Name = "Send E-mail" $buttonSendEmail.Text = "Send E-mail" $buttonSendEmail.UseVisualStyleBackColor = $True $buttonSendEmail.add_Click($SendMail) $MainForm.Controls.Add($buttonSendEmail) # # "Go" button # $buttonGo.DataBindings.DefaultDataSourceUpdateMode = 0 $buttonGo.ForeColor = [System.Drawing.Color]::FromArgb(255, 0, 0, 0) $buttonGo.Location = New-Object System.Drawing.Point(700, 20) $buttonGo.Size = New-Object System.Drawing.Size(50, 25) $buttonGo.Name = "Go" $buttonGo.Text = "Run" $buttonGo.UseVisualStyleBackColor = $True $buttonGo.add_Click($GetDataProcess) $MainForm.Controls.Add($buttonGo) # # "Exit" button # $buttonExit.DataBindings.DefaultDataSourceUpdateMode = 0 $buttonExit.ForeColor = [System.Drawing.Color]::FromArgb(255, 0, 0, 0) $buttonExit.Location = New-Object System.Drawing.Point(700, 50) $buttonExit.Size = New-Object System.Drawing.Size(50, 25) $buttonExit.Name = "Exit" $buttonExit.Text = "Exit" $buttonExit.UseVisualStyleBackColor = $True $buttonExit.add_Click({ $MainForm.Close() ; $buttonExit.Dispose() }) $MainForm.Controls.Add($buttonExit) # # TextBox results # $txtBoxResults.DataBindings.DefaultDataSourceUpdateMode = 0 $txtBoxResults.Location = New-Object System.Drawing.Point(5, 200) $txtBoxResults.Size = New-Object System.Drawing.Size(990, 510) $txtBoxResults.Name = "TextResults" $txtBoxResults.BackColor = [System.Drawing.Color]::White $txtBoxResults.BorderStyle = [System.Windows.Forms.BorderStyle]::Fixed3D $Font = New-Object System.Drawing.Font("Consolas", 8) $txtBoxResults.Font = $Font  $MainForm.Controls.Add($txtBoxResults) # #dataGrid # $dgResults.Anchor = 15 $dgResults.DataBindings.DefaultDataSourceUpdateMode = 0 $dgResults.DataMember = "" $dgResults.Location = New-Object System.Drawing.Point(5, 200) $dgResults.Size = New-Object System.Drawing.Size(990, 510) $dgResults.Name = "dgResults" $dgResults.ReadOnly = $True $dgResults.RowHeadersVisible = $False $dgResults.Visible = $False $dgResults.AllowUserToOrderColumns = $True $dgResults.AllowUserToResizeColumns = $True $MainForm.Controls.Add($dgResults) #endregion Generated Form Code # Show Form #Save the initial state of the form $InitialFormWindowState = $MainForm.WindowState #Init the OnLoad event to correct the initial state of the form $MainForm.add_Load($OnLoadMainWindow_StateCorrection) $MainForm.Add_Shown({ $MainForm.Activate() }) $MainForm.ShowDialog() | Out-Null #exit if 'Exit' button is pushed if ($buttonExit.IsDisposed) { if ($EnableTranscript) { stop-transcript } ; return } } #Call the Function GenerateForm