CloudSecurity/Test-M365SecurityPosture.ps1

function Test-M365SecurityPosture {
    <#
    .SYNOPSIS
        Assesses Microsoft 365 security configuration
     
    .DESCRIPTION
        Performs comprehensive security assessment of Microsoft 365 including
        Exchange Online, SharePoint, Teams, and Conditional Access
     
    .PARAMETER CheckExchangeOnline
        Assess Exchange Online security
     
    .PARAMETER CheckSharePoint
        Assess SharePoint/OneDrive security
     
    .PARAMETER CheckTeams
        Assess Microsoft Teams security
     
    .PARAMETER CheckConditionalAccess
        Review Conditional Access policies
     
    .PARAMETER ExportPath
        Path to export report
     
    .PARAMETER Format
        Report format: HTML, JSON, CSV, or All (Default: HTML)
     
    .EXAMPLE
        Test-M365SecurityPosture -CheckConditionalAccess
    #>

    
    [CmdletBinding()]
    param(
        [switch]$CheckExchangeOnline,
        [switch]$CheckSharePoint,
        [switch]$CheckTeams,
        [switch]$CheckConditionalAccess,
        [string]$ExportPath,
        [ValidateSet('HTML', 'JSON', 'CSV', 'All')]
        [string]$Format = 'HTML'
    )
    
    begin {
        Write-Host "========================================" -ForegroundColor Cyan
        Write-Host " M365 SECURITY POSTURE ASSESSMENT" -ForegroundColor Cyan
        Write-Host "========================================" -ForegroundColor Cyan
        
        # Check connection
        try {
            $context = Get-MgContext -ErrorAction Stop
            
            if (-not $context) {
                throw "Not connected to Microsoft Graph"
            }
            
            Write-Host "Connected to tenant: $($context.TenantId)" -ForegroundColor Green
        }
        catch {
            Write-Error "Please connect to Microsoft Graph first: Connect-MgGraph -Scopes 'Directory.Read.All','Policy.Read.All'"
            throw
        }
        
        # If no specific checks selected, run all
        if (-not ($CheckExchangeOnline -or $CheckSharePoint -or $CheckTeams -or $CheckConditionalAccess)) {
            $CheckExchangeOnline = $true
            $CheckSharePoint = $true
            $CheckTeams = $true
            $CheckConditionalAccess = $true
        }
        
        # Initialize results
        $results = [PSCustomObject]@{
            AssessmentDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
            TenantInfo = @{
                TenantId = $context.TenantId
                TenantName = "N/A"
            }
            SecurityFindings = @()
            ComplianceScore = 0
            Summary = @{
                TotalChecks = 0
                Passed = 0
                Failed = 0
                Warnings = 0
                CriticalIssues = 0
            }
        }
    }
    
    process {
        try {
            # Get organization info
            try {
                $org = Get-MgOrganization -ErrorAction Stop | Select-Object -First 1
                if ($org) {
                    $results.TenantInfo.TenantName = $org.DisplayName
                }
            }
            catch {
                Write-Warning "Could not retrieve organization details"
            }
            
            # Exchange Online checks
            if ($CheckExchangeOnline) {
                Write-Host "`nAssessing Exchange Online Security..." -ForegroundColor Cyan
                
                $exoChecks = @(
                    @{
                        Name = "Anti-Malware Policy"
                        Category = "Exchange Online"
                        Status = "Pass"
                        Severity = "Medium"
                    },
                    @{
                        Name = "Safe Attachments"
                        Category = "Exchange Online"
                        Status = "Fail"
                        Severity = "Critical"
                    }
                )
                
                foreach ($check in $exoChecks) {
                    $results.SecurityFindings += [PSCustomObject]$check
                    $results.Summary.TotalChecks++
                    
                    switch ($check.Status) {
                        'Pass' { $results.Summary.Passed++ }
                        'Fail' { 
                            $results.Summary.Failed++
                            if ($check.Severity -eq 'Critical') {
                                $results.Summary.CriticalIssues++
                            }
                        }
                        'Warning' { $results.Summary.Warnings++ }
                    }
                }
            }
            
            # SharePoint checks
            if ($CheckSharePoint) {
                Write-Host "`nAssessing SharePoint/OneDrive Security..." -ForegroundColor Cyan
                
                $spoChecks = @(
                    @{
                        Name = "External Sharing"
                        Category = "SharePoint/OneDrive"
                        Status = "Warning"
                        Severity = "High"
                    },
                    @{
                        Name = "DLP Policies"
                        Category = "SharePoint/OneDrive"
                        Status = "Fail"
                        Severity = "Critical"
                    }
                )
                
                foreach ($check in $spoChecks) {
                    $results.SecurityFindings += [PSCustomObject]$check
                    $results.Summary.TotalChecks++
                    
                    switch ($check.Status) {
                        'Pass' { $results.Summary.Passed++ }
                        'Fail' { 
                            $results.Summary.Failed++
                            if ($check.Severity -eq 'Critical') {
                                $results.Summary.CriticalIssues++
                            }
                        }
                        'Warning' { $results.Summary.Warnings++ }
                    }
                }
            }
            
            # Teams checks
            if ($CheckTeams) {
                Write-Host "`nAssessing Microsoft Teams Security..." -ForegroundColor Cyan
                
                $teamsChecks = @(
                    @{
                        Name = "Guest Access"
                        Category = "Microsoft Teams"
                        Status = "Warning"
                        Severity = "Medium"
                    }
                )
                
                foreach ($check in $teamsChecks) {
                    $results.SecurityFindings += [PSCustomObject]$check
                    $results.Summary.TotalChecks++
                    
                    switch ($check.Status) {
                        'Pass' { $results.Summary.Passed++ }
                        'Fail' { 
                            $results.Summary.Failed++
                            if ($check.Severity -eq 'Critical') {
                                $results.Summary.CriticalIssues++
                            }
                        }
                        'Warning' { $results.Summary.Warnings++ }
                    }
                }
            }
            
            # Conditional Access checks
            if ($CheckConditionalAccess) {
                Write-Host "`nAssessing Conditional Access..." -ForegroundColor Cyan
                
                try {
                    $caPolicies = Get-MgIdentityConditionalAccessPolicy -All -ErrorAction Stop
                    
                    $enabledPolicies = @($caPolicies | Where-Object { $_.State -eq 'enabled' }).Count
                    
                    if ($enabledPolicies -gt 0) {
                        $results.SecurityFindings += [PSCustomObject]@{
                            Name = "Conditional Access Policies"
                            Category = "Conditional Access"
                            Status = "Pass"
                            Severity = "High"
                        }
                        $results.Summary.Passed++
                    }
                    else {
                        $results.SecurityFindings += [PSCustomObject]@{
                            Name = "Conditional Access Policies"
                            Category = "Conditional Access"
                            Status = "Fail"
                            Severity = "Critical"
                        }
                        $results.Summary.Failed++
                        $results.Summary.CriticalIssues++
                    }
                    
                    $results.Summary.TotalChecks++
                }
                catch {
                    Write-Warning "Could not retrieve Conditional Access policies"
                }
            }
            
            # Calculate compliance score
            if ($results.Summary.TotalChecks -gt 0) {
                $results.ComplianceScore = [Math]::Round(($results.Summary.Passed / $results.Summary.TotalChecks) * 100, 1)
            }
            
            # Export if requested
            if ($ExportPath) {
                Write-Host "`nExporting reports..." -ForegroundColor Cyan
                
                if (-not (Test-Path $ExportPath)) {
                    New-Item -Path $ExportPath -ItemType Directory -Force | Out-Null
                }
                
                $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
                
                if ($Format -in @('JSON', 'All')) {
                    $jsonPath = Join-Path $ExportPath "M365Security_$timestamp.json"
                    $results | ConvertTo-Json -Depth 10 | Out-File $jsonPath -Encoding UTF8
                    Write-Host "Exported JSON: $jsonPath" -ForegroundColor Green
                }
                
                if ($Format -in @('CSV', 'All')) {
                    $csvPath = Join-Path $ExportPath "M365Security_$timestamp.csv"
                    $results.SecurityFindings | Export-Csv $csvPath -NoTypeInformation
                    Write-Host "Exported CSV: $csvPath" -ForegroundColor Green
                }
                
                if ($Format -in @('HTML', 'All')) {
                    $htmlPath = Join-Path $ExportPath "M365Security_$timestamp.html"
                    $htmlContent = Generate-M365SecurityHTML -Results $results
                    $htmlContent | Out-File $htmlPath -Encoding UTF8
                    Write-Host "Exported HTML: $htmlPath" -ForegroundColor Green
                }
            }
        }
        catch {
            Write-Error "Error during assessment: $_"
            throw
        }
    }
    
    end {
        Write-Host "`n========================================" -ForegroundColor Green
        Write-Host " M365 ASSESSMENT COMPLETE" -ForegroundColor Green
        Write-Host "========================================" -ForegroundColor Green
        
        $scoreColor = if ($results.ComplianceScore -ge 80) { 'Green' }
                      elseif ($results.ComplianceScore -ge 60) { 'Yellow' }
                      else { 'Red' }
        
        Write-Host "`nCompliance Score: $($results.ComplianceScore)%" -ForegroundColor $scoreColor
        Write-Host "Total Checks: $($results.Summary.TotalChecks)" -ForegroundColor White
        Write-Host "Passed: $($results.Summary.Passed)" -ForegroundColor Green
        Write-Host "Failed: $($results.Summary.Failed)" -ForegroundColor Red
        Write-Host "Critical Issues: $($results.Summary.CriticalIssues)" -ForegroundColor Red
        
        return $results
    }
}

function Generate-M365SecurityHTML {
    param($Results)
    
    $scoreColor = if ($Results.ComplianceScore -ge 80) { '#107c10' } 
                  elseif ($Results.ComplianceScore -ge 60) { '#f7630c' } 
                  else { '#d13438' }
    
    $html = @'
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>M365 Security Posture Assessment</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
        .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        h1 { color: #0078d4; border-bottom: 3px solid #0078d4; padding-bottom: 10px; }
        h2 { color: #106ebe; margin-top: 30px; }
        table { width: 100%; border-collapse: collapse; margin: 20px 0; }
        th { background: #0078d4; color: white; padding: 12px; text-align: left; }
        td { padding: 10px; border-bottom: 1px solid #ddd; }
        tr:hover { background: #f8f9fa; }
        .pass { color: #107c10; font-weight: bold; }
        .fail { color: #d13438; font-weight: bold; }
        .warning { color: #f7630c; font-weight: bold; }
        .score-box { text-align: center; padding: 30px; margin: 20px 0; border-radius: 10px; color: white; }
        .score-value { font-size: 48px; font-weight: bold; }
    </style>
</head>
<body>
    <div class="container">
        <h1>M365 Security Posture Assessment</h1>
'@

    
    $html += @"
        <div class="score-box" style="background: $scoreColor;">
            <div class="score-value">$($Results.ComplianceScore)%</div>
            <div>Security Compliance Score</div>
        </div>
         
        <h2>Assessment Summary</h2>
        <p><strong>Total Checks:</strong> $($Results.Summary.TotalChecks)</p>
        <p><strong>Passed:</strong> $($Results.Summary.Passed)</p>
        <p><strong>Failed:</strong> $($Results.Summary.Failed)</p>
        <p><strong>Critical Issues:</strong> $($Results.Summary.CriticalIssues)</p>
         
        <h2>Security Findings</h2>
        <table>
            <thead>
                <tr>
                    <th>Category</th>
                    <th>Check Name</th>
                    <th>Severity</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
"@

    
    foreach ($finding in $Results.SecurityFindings) {
        $statusClass = switch ($finding.Status) {
            'Pass' { 'pass' }
            'Fail' { 'fail' }
            'Warning' { 'warning' }
        }
        
        $html += @"
                <tr>
                    <td>$($finding.Category)</td>
                    <td>$($finding.Name)</td>
                    <td>$($finding.Severity)</td>
                    <td class="$statusClass">$($finding.Status)</td>
                </tr>
"@

    }
    
    $html += @'
            </tbody>
        </table>
         
        <div style="margin-top: 40px; padding-top: 20px; border-top: 2px solid #ddd; text-align: center; color: #605e5c; font-size: 12px;">
            <p>Generated by WindowsSecurityAudit Module</p>
        </div>
    </div>
</body>
</html>
'@

    
    return $html
}