Public/Invoke-M365SecurityAudit.ps1

function Invoke-M365SecurityAudit {
    <#
    .SYNOPSIS
        Runs a comprehensive Microsoft 365 tenant security audit.
 
    .DESCRIPTION
        Connects to Microsoft 365 and runs all security baseline checks:
        1. MFA enrollment and enforcement status
        2. Conditional Access policy review
        3. Mailbox forwarding rule audit (data exfiltration detection)
        4. Guest/external user access review
        5. Shared mailbox license optimization
 
        Generates an HTML dashboard report with findings and recommendations.
 
        Requires the Microsoft Graph PowerShell SDK and Exchange Online module.
 
    .PARAMETER OutputPath
        Directory to save the HTML report. Defaults to .\Reports.
 
    .PARAMETER SkipConnect
        Skip the Connect-MgGraph and Connect-ExchangeOnline calls (use if already connected).
 
    .PARAMETER Scopes
        Microsoft Graph scopes to request. Defaults to the required set for all checks.
 
    .EXAMPLE
        Invoke-M365SecurityAudit
 
        Runs all checks, prompts for authentication, saves report to .\Reports.
 
    .EXAMPLE
        Invoke-M365SecurityAudit -SkipConnect -OutputPath "C:\Audits"
 
        Runs all checks using existing connections.
 
    .NOTES
        Required modules:
        - Microsoft.Graph.Authentication
        - Microsoft.Graph.Users
        - Microsoft.Graph.Identity.SignIns
        - ExchangeOnlineManagement
    #>

    [CmdletBinding()]
    param(
        [string]$OutputPath = '.\Reports',

        [switch]$SkipConnect,

        [string]$LogPath = '.\Logs'
    )

    begin {
        foreach ($dir in @($OutputPath, $LogPath)) {
            if (-not (Test-Path $dir)) {
                New-Item -Path $dir -ItemType Directory -Force | Out-Null
            }
        }

        $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
        Start-Transcript -Path (Join-Path $LogPath "M365Audit-$timestamp.log") -Append

        # Connect to services
        if (-not $SkipConnect) {
            Write-Verbose "Connecting to Microsoft Graph..."
            Connect-MgGraph -Scopes @(
                'User.Read.All',
                'UserAuthenticationMethod.Read.All',
                'Policy.Read.All',
                'Directory.Read.All'
            ) -ErrorAction Stop

            Write-Verbose "Connecting to Exchange Online..."
            Connect-ExchangeOnline -ShowBanner:$false -ErrorAction Stop
        }

        $auditResults = @{}
    }

    process {
        $tenantName = (Get-MgOrganization).DisplayName

        # 1. MFA Status
        Write-Verbose "Checking MFA enrollment..."
        $auditResults['MFA'] = @(Get-MFAStatus)

        # 2. Conditional Access
        Write-Verbose "Reviewing Conditional Access policies..."
        $auditResults['ConditionalAccess'] = @(Get-ConditionalAccessReview)

        # 3. Mailbox Forwarding
        Write-Verbose "Auditing mailbox forwarding rules..."
        $auditResults['Forwarding'] = @(Get-MailboxForwardingRules)

        # 4. Guest Access
        Write-Verbose "Reviewing guest accounts..."
        $auditResults['Guests'] = @(Get-GuestAccessReport)

        # Generate HTML
        $htmlFile = Join-Path $OutputPath "M365-SecurityAudit-$timestamp.html"
        $html = _New-M365AuditHtml -AuditResults $auditResults -TenantName $tenantName
        $html | Out-File -FilePath $htmlFile -Encoding UTF8

        Write-Verbose "Report saved: $htmlFile"

        # Summary
        $mfaNotEnrolled = @($auditResults['MFA'] | Where-Object MFAEnabled -eq $false).Count
        $forwardingRules = $auditResults['Forwarding'].Count

        [PSCustomObject]@{
            Tenant              = $tenantName
            AuditDate           = Get-Date -Format 'yyyy-MM-dd HH:mm'
            UsersWithoutMFA     = $mfaNotEnrolled
            TotalUsers          = $auditResults['MFA'].Count
            CAPolicies          = $auditResults['ConditionalAccess'].Count
            ForwardingRules     = $forwardingRules
            GuestAccounts       = $auditResults['Guests'].Count
            ReportPath          = $htmlFile
        }
    }

    end {
        Stop-Transcript
    }
}