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 &copy; $(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
}