Public/Invoke-ModernMailHybridReport.ps1
function Invoke-ModernMailHybridReport { <# .SYNOPSIS ModernMail Hybrid Report / Exchange Hybrid Report / Microsoft365 Hybrid Report .DESCRIPTION Generates a comprehensive Exchange Hybrid report for Microsoft 365 environments. The report includes user statistics, shared mailbox details, directory sync status, and license information. Outputs a styled HTML file with interactive charts and summary statistics. .PARAMETER OutPath The output path for the generated HTML report. Can be a directory (FileName: 'ExchangeHybridReport.html') or a full file path. .EXAMPLE Invoke-ModernMailHybridReport -OutPath "C:\Reports" Invoke-ModernMailHybridReport -OutPath "C:\Reports\ExchangeHybridReport.html" .LINK https://exchangepermissions.alweys.ch/modernmailtools/Invoke-ModernMailHybridReport .INPUTS None .OUTPUTS .NOTES #> [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$OutPath ) if ($DisableTelemetry -or $Script:DisableTelemetry) { $Script:DisableTelemetry = $true } else { Write-Telemetry -EventName "Invoke-ModernMailHybridReport" } # Connect-ExchangeServer # > Connect-MMExchangeServer # Connect-Exchange Online #$session = Get-ConnectionInformation #If (!$session) { # Connect-ExchangeOnline -ShowBanner:$false -Prefix "EXCH" # $session = Get-ConnectionInformation #} $session = Connect-MMExchangeOnline Write-Debug $session.UserPrincipalName $userPrincipalName = $session.UserPrincipalName ## Organization | $organisationName Write-Debug $session.TenantID $tenantID = $session.TenantID $__MMTSession = @{ #GraphCache = @{} #Connections = @() UserCache = @{} MailboxCache = @{} CountCache = @{} #GroupCache = @{} TableCache = @{} } New-Variable -Name __MMTSession -Value $__MMTSession -Scope Script -Force #Total Users #$usersCount = $users.Count $users = Get-MMCloudUser -CountVariable usersCount #; $usersCount $__MMTSession.CountCache.Add("Total Users", $usersCount) #$usersGrouped = $users | Group RecipientType $usersGrouped = $users | Group-Object RecipientTypeDetails #$usersGrouped[0].Name #$usersGrouped[0].Count #$users | Group IsDirSynced #$isDirSyncedCount = ($users | Where-Object {$_.IsDirSynced}).count $null = Get-MMCloudUser -SyncedUser -CountVariable isDirSyncedCount #; $isDirSyncedCount $__MMTSession.CountCache.Add("Total Directory Synced Users", $isDirSyncedCount) #$sharedMailboxes = $users | Where-Object {$_.RecipientTypeDetails -eq "SharedMailbox"} #$sharedMailboxCount = $sharedMailboxes.count $null = Get-MMCloudUser -SharedUser -CountVariable sharedMailboxCount #; $sharedMailboxCount $__MMTSession.CountCache.Add("Total Shared Mailboxes", $sharedMailboxCount) # After Migration $userMailboxUnlicensed = Get-MMCloudUser -RegularUser -UnlicensedUser -CountVariable userMailboxUnlicensedCount #; $sharedMailboxLicensedCount $__MMTSession.CountCache.Add("Regular Mailboxes which are Unlicensed", $userMailboxUnlicensedCount) $__MMTSession.TableCache.Add("Regular Mailboxes which are Unlicensed", ($userMailboxUnlicensed | Select-Object DisplayName, UserPrincipalName, RecipientTypeDetails, Name, Alias)) # ToBeQuestioned #$sharedMailboxEnabled = $sharedMailboxes | Where-Object {$_.AccountDisabled -eq $false} #$sharedMailboxEnabledCount = $sharedMailboxEnabled.count $sharedMailboxEnabled = Get-MMCloudUser -SharedUser -EnabledUser -CountVariable sharedMailboxEnabledCount #; $sharedMailboxEnabledCount $__MMTSession.CountCache.Add("Shared Mailboxes which are Enabled", $sharedMailboxEnabledCount) $__MMTSession.TableCache.Add("Shared Mailboxes which are Enabled", ($sharedMailboxEnabled | Select-Object DisplayName, UserPrincipalName, RecipientTypeDetails, Name, Alias)) #$users | Group AccountDisabled #$sharedMailboxDisabled = $sharedMailboxes | Where-Object {$_.AccountDisabled -eq $true} #$sharedMailboxDisabledCount = $sharedMailboxDisabled.count $null = Get-MMCloudUser -SharedUser -DisabledUser -CountVariable sharedMailboxDisabledCount #; $sharedMailboxDisabledCount $__MMTSession.CountCache.Add("Shared Mailboxes which are Disabled", $sharedMailboxDisabledCount) #$users | Group SKUAssigned #$sharedMailboxLicensed = ($sharedMailboxes | Where-Object {$_.SKUAssigned -ne $true}) #$sharedMailboxLicensedCount = $sharedMailboxLicensed.count #$sharedMailboxLicensed = Get-MMCloudUser -SharedUser -UnlicensedUser -CountVariable sharedMailboxLicensedCount #; $sharedMailboxLicensedCount $null = Get-MMCloudUser -SharedUser -LicensedUser -CountVariable sharedMailboxLicensedCount #; $sharedMailboxLicensedCount $__MMTSession.CountCache.Add("Shared Mailboxes which are Licensed", $sharedMailboxLicensedCount) # ToBeQuestioned (Litigation Hold Enabled?) $null = Get-MMCloudMailbox -HoldEnabled -CountVariable mailboxesLitigationHoldCount #;$mailboxesLitigationHoldCount $__MMTSession.CountCache.Add("Mailboxes with LitigationHold", $mailboxesLitigationHoldCount) $mailboxesAuditDisabled = Get-MMCloudMailbox -AuditDisabled -CountVariable mailboxesAuditDisabledCount #;$mailboxesAuditDisabledCount #$mailboxesAuditDisabled | fl *type* $__MMTSession.CountCache.Add("Mailboxes with Disabled AuditLog", $mailboxesAuditDisabledCount) $__MMTSession.TableCache.Add("Mailboxes with Disabled AuditLog", ($mailboxesAuditDisabled | Select-Object DisplayName, UserPrincipalName, RecipientTypeDetails, Name, Alias)) # Empty DL $distributionListExternal = Get-MMCloudDistributionGroup -ExternalAllowed distributionListExternalCount $__MMTSession.CountCache.Add("Distribution List with External Sender Allowed", $distributionListExternalCount) $__MMTSession.TableCache.Add("Distribution List with External Sender Allowed", ($distributionListExternal | Select-Object DisplayName, PrimarySmtpAddress, RecipientTypeDetails, Name, Alias)) # GroupType # Room without Delegation # User with Manager? function Build-HybridReport { param( [Parameter(Mandatory=$false)] [string]$OutputPath ) # Path to the HTML template $templatePath = "$PSScriptRoot\..\Private\HybridReportTemplate.html" # Read the HTML template $htmlTemplate = Get-Content -Path $templatePath -Raw # Generate dynamic rows for grouped data $dynamicRows = "" $chartLabels = @() $chartData = @() foreach ($group in $usersGrouped) { $dynamicRows += @" <tr class="border-b border-gray-300"> <td class="px-4 py-2 border-b border-r">$($group.Name)</td> <td class="px-4 py-2 border-b">$($group.Count)</td> </tr> "@ $chartLabels += "'$($group.Name)'" $chartData += $group.Count } # Convert arrays to JavaScript-compatible strings $chartLabelsString = "[" + ($chartLabels -join ", ") + "]" $chartDataString = "[" + ($chartData -join ", ") + "]" # Generate header details $reportName = "Exchange Hybrid Report" $date = Get-Date #$organizationName = "Your Organization Name" # Replace with actual organization name $headerContent = @" <div class="header bg-orange-500 text-white p-6 rounded-lg shadow-md"> <h1 class="text-3xl font-bold">$reportName</h1> <!--<p class="text-lg">Organization: $organizationName</p>--> <p class="text-lg mt-4">Tenant ID: $tenantID</p> <p class="text-lg">Generated by: $userPrincipalName</p> <p class="text-lg">Generated on: $date</p> <div class="mt-4"> <a href="https://alweys.ch" target="_blank" class="bg-blue-500 hover:bg-blue-700 text-white text-xs font-semibold py-1 px-2 rounded"> Website (alweys.ch) </a> <a href="https://exchangepermissions.alweys.ch/modernmailtools/Invoke-ModernMailHybridReport" target="_blank" class="bg-blue-500 hover:bg-blue-700 text-white text-xs font-semibold py-1 px-2 rounded ml-2"> Docs (*.alweys.ch) </a> <a href="https://www.linkedin.com/in/stefanwey" target="_blank" class="bg-blue-500 hover:bg-blue-700 text-white text-xs font-semibold py-1 px-2 rounded ml-2"> LinkedIn (Stefan Wey) </a> </div> </div> "@ # Generate stat boxes from CountCache $statsBoxes = "" foreach ($key in $__MMTSession.CountCache.Keys) { $statsBoxes += @" <div class="stat-box bg-white p-4 rounded-lg shadow-md"> <h3 class="text-lg font-semibold">$key</h3> <p class="text-2xl font-bold text-orange-500">$($__MMTSession.CountCache[$key])</p> </div> "@ } $statsContent = @" <div class="stats-container grid grid-cols-2 gap-4 mt-4"> $statsBoxes </div> "@ # tableContent # Generate TableCache HTML $tableContent = "" foreach ($key in $__MMTSession.TableCache.Keys) { $items = $__MMTSession.TableCache[$key] if ($items -and $items.Count -gt 0) { # Get property names for table headers $headers = $items[0].PSObject.Properties.Name $headerRow = ($headers | ForEach-Object { "<th class='px-4 py-2 border-b border-r border-gray-300'>$_</th>" }) -join "" $rows = foreach ($item in $items) { "<tr>" + ($headers | ForEach-Object { "<td class='px-4 py-2 border-b border-r border-gray-200'>$($item.$_)</td>" }) -join "" + "</tr>" } $rowsString = $rows -join "`n" $tableContent += @" <div class="mt-10"></div> <!--<h2 class="text-xl font-bold mb-2">$key</h2>--> <!--<h2 class="text-xl font-bold mb-2 ml-2 mt-2">$key</h2>--> <h2 class="text-xl font-bold mb-2 ml-2 mt-4">$key</h2> <div class="stat-box bg-white rounded-lg shadow-md mt-2 mb-4"> <div class="overflow-x-auto rounded-lg"> <table class="table-auto border-collapse w-full mb-2"> <thead> <tr class="bg-gray-200"> $headerRow </tr> </thead> <tbody> $rowsString </tbody> </table> </div> </div> "@ } } # Get the module version $module = Get-Module -Name ModernMailTools $moduleVersion = if ($module) { $module.Version } else { "unknown" } # Footer content with module version $footerContent = @" <footer class="w-full text-center text-xs text-gray-500 mt-8 mb-2 opacity-80"> Report generated by ModernMailTools v$moduleVersion © $(Get-Date -Format yyyy) </footer> "@ # Replace placeholders in the template $htmlContent = $htmlTemplate -replace "{{HeaderContent}}", $headerContent $htmlContent = $htmlContent -replace "{{StatsContent}}", $statsContent $htmlContent = $htmlContent -replace "{{DynamicRows}}", $dynamicRows $htmlContent = $htmlContent -replace "{{ChartLabels}}", $chartLabelsString $htmlContent = $htmlContent -replace "{{ChartData}}", $chartDataString $htmlContent = $htmlContent -replace "{{TableContent}}", $tableContent $htmlContent = $htmlContent -replace "{{FooterContent}}", $footerContent # Generate the Path if (Test-Path $OutPath -PathType Container) { # If $OutPath is a directory, append the default file name $OutputPath = Join-Path -Path $OutPath -ChildPath "ExchangeHybridReport.html" } else { # If $OutPath is a file, use it directly $OutputPath = $OutPath } # Output HTML report #$html | Out-File -FilePath $OutputPath -Encoding UTF8 $htmlContent | Out-File -FilePath $OutputPath -Encoding UTF8 Write-output "HTML report generated at $OutputPath" # Open the report in the default browser Start-Process $OutputPath } Build-HybridReport -OutputPath $OutPath } |