Scripts/Get-SecureScore.ps1

Function Get-SecureScore {
<#
    .SYNOPSIS
    Retrieves Microsoft Secure Score recommendations and current status.
 
    .DESCRIPTION
    Retrieves Secure Score control profiles, current scores, and derives statuses for recommendations.
 
    .PARAMETER OutputDir
    OutputDir is the parameter specifying the output directory.
    Default: Output\SecureScore
 
    .PARAMETER Encoding
    Encoding is the parameter specifying the encoding of the CSV output file.
    Default: UTF8
 
    .PARAMETER LogLevel
    Specifies the level of logging:
    None: No logging
    Minimal: Critical errors only
    Standard: Normal operational logging
    Debug: Verbose logging for debugging purposes
    Default: Standard
 
    .PARAMETER Category
    Category is the parameter specifying a specific control category to filter results.
    Default: All categories will be included if not specified.
 
    .PARAMETER Service
    Service is the parameter specifying a specific service to filter results (e.g., "Exchange", "SharePoint").
    Default: All services will be included if not specified.
 
    .PARAMETER StatusFilter
    StatusFilter is the parameter specifying which statuses to include in the output.
    Valid values: AtRisk, Partial, MeetsStandard, NotApplicable
    Default: All statuses will be included if not specified.
     
    .EXAMPLE
    Get-SecureScore
    Retrieves Secure Score recommendations and statuses.
     
    .EXAMPLE
    Get-SecureScore -OutputDir C:\Windows\Temp
    Retrieves Secure Score data and saves output to C:\Windows\Temp folder.
 
    .EXAMPLE
    Get-SecureScore -Category "Identity"
    Retrieves Secure Score recommendations filtered to the Identity category.
 
    .EXAMPLE
    Get-SecureScore -Service "Exchange"
    Retrieves Secure Score recommendations for Exchange only.
 
    .EXAMPLE
    Get-SecureScore -StatusFilter AtRisk
    Retrieves only the at-risk Secure Score recommendations.
#>


    [CmdletBinding()]
    param(
        [string]$OutputDir,
        [string]$Encoding = "UTF8",
        [string]$Category,
        [string]$Service,
        [ValidateSet('AtRisk', 'Partial', 'MeetsStandard', 'NotApplicable')]
        [string]$StatusFilter,
        [ValidateSet('None', 'Minimal', 'Standard', 'Debug')]
        [string]$LogLevel = 'Standard'
    )

    Init-Logging
    Init-OutputDir -Component "SecureScore" -FilePostfix "SecureScore" -CustomOutputDir $OutputDir

    Write-LogFile -Message "=== Starting Secure Score Collection ===" -Color "Cyan" -Level Standard

    $requiredScopes = @("SecurityEvents.Read.All")
    $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes

    if ($isDebugEnabled) {
        Write-LogFile -Message "[DEBUG] Graph authentication details:" -Level Debug
        Write-LogFile -Message "[DEBUG] Required scopes: $($requiredScopes -join ', ')" -Level Debug
        Write-LogFile -Message "[DEBUG] Authentication type: $($graphAuth.AuthType)" -Level Debug
        Write-LogFile -Message "[DEBUG] Current scopes: $($graphAuth.Scopes -join ', ')" -Level Debug
        if ($graphAuth.MissingScopes.Count -gt 0) {
            Write-LogFile -Message "[DEBUG] Missing scopes: $($graphAuth.MissingScopes -join ', ')" -Level Debug
        } else {
            Write-LogFile -Message "[DEBUG] Missing scopes: None" -Level Debug
        }
    }

    try {
        Write-LogFile -Message "[INFO] Retrieving Secure Score data..." -Level Standard

        if ($Category) {
            Write-LogFile -Message "[INFO] Filtering results for category: $Category" -Level Standard
        }
        if ($Service) {
            Write-LogFile -Message "[INFO] Filtering results for service: $Service" -Level Standard
        }
        if ($StatusFilter) {
            Write-LogFile -Message "[INFO] Filtering results for status: $StatusFilter" -Level Standard
        }
        
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Fetching all Secure Score control profiles..." -Level Debug
            $profilePerformance = Measure-Command {
                $profiles = Get-MgSecuritySecureScoreControlProfile -All
            }
            Write-LogFile -Message "[DEBUG] Profile retrieval took $([math]::round($profilePerformance.TotalSeconds, 2)) seconds" -Level Debug
            Write-LogFile -Message "[DEBUG] Found $($profiles.Count) control profiles" -Level Debug
        } else {
            $profiles = Get-MgSecuritySecureScoreControlProfile -All
        }

        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Fetching latest Secure Score..." -Level Debug
            $scorePerformance = Measure-Command {
                $latestScore = Get-MgSecuritySecureScore -Top 1
            }
            Write-LogFile -Message "[DEBUG] Score retrieval took $([math]::round($scorePerformance.TotalSeconds, 2)) seconds" -Level Debug
        } else {
            $latestScore = Get-MgSecuritySecureScore -Top 1
        }

        Write-LogFile -Message "[INFO] Found $($profiles.Count) control profiles" -Level Standard

        $controlScoresHash = @{}
        $latestScore.ControlScores | ForEach-Object {
            $controlScoresHash[$_.ControlName] = $_.Score
        }

        $results = [System.Collections.Generic.List[object]]::new()
        $atRiskCount = 0
        $meetsStandardCount = 0
        $partialCount = 0
        $notApplicableCount = 0

        foreach ($profile in $profiles) {
            if ($isDebugEnabled) {
                Write-LogFile -Message "[DEBUG] Processing profile: $($profile.Title)" -Level Debug
                Write-LogFile -Message "[DEBUG] Profile ID: $($profile.Id)" -Level Debug
            }

            if ($Category -and $profile.ControlCategory -notlike "*$Category*") {
                if ($isDebugEnabled) {
                    Write-LogFile -Message "[DEBUG] Skipping profile (category filter): $($profile.ControlCategory)" -Level Debug
                }
                continue
            }

            if ($Service -and $profile.Service -notlike "*$Service*") {
                if ($isDebugEnabled) {
                    Write-LogFile -Message "[DEBUG] Skipping profile (service filter): $($profile.Service)" -Level Debug
                }
                continue
            }

            $state = $profile.ControlStateUpdates | Select-Object -Last 1 -ExpandProperty State
            if (-not $state) { $state = "Default" }

            $currentScore = $controlScoresHash[$profile.Id]
            if ($null -eq $currentScore) { $currentScore = 0 }

            if ($null -eq $controlScoresHash[$profile.Id] -and $state -eq "Default") {
                $status = "Not applicable"
                $notApplicableCount++
            } elseif ($state -in @("Ignored", "ThirdParty", "Reviewed")) {
                $status = "Not applicable (overridden: $state)"
                $notApplicableCount++
            } elseif ($currentScore -eq $profile.MaxScore) {
                $status = "Meets standard"
                $meetsStandardCount++
            } elseif ($currentScore -gt 0) {
                $status = "Partial"
                $partialCount++
            } else {
                $status = "At risk"
                $atRiskCount++
            }

            if ($StatusFilter) {
                $includeResult = $false
                switch ($StatusFilter) {
                    "AtRisk" { if ($status -eq "At risk") { $includeResult = $true } }
                    "Partial" { if ($status -eq "Partial") { $includeResult = $true } }
                    "MeetsStandard" { if ($status -eq "Meets standard") { $includeResult = $true } }
                    "NotApplicable" { if ($status -like "Not applicable*") { $includeResult = $true } }
                }
                if (-not $includeResult) { continue }
            }

            $scoreGap = $profile.MaxScore - $currentScore
            $results.Add([PSCustomObject]@{
                Category          = $profile.ControlCategory
                Title             = $profile.Title
                Service           = $profile.Service
                Status            = $status
                CurrentScore      = $currentScore
                MaxScore          = $profile.MaxScore
                ScoreGap          = $scoreGap
                State             = $state
                ActionType        = $profile.ActionType
                ActionUrl         = $profile.ActionUrl
                ImplementationCost = $profile.ImplementationCost
                UserImpact        = $profile.UserImpact
                Tier              = $profile.Tier
                Rank              = $profile.Rank
                Deprecated        = $profile.Deprecated
                Threats           = ($profile.Threats -join "; ")
                Remediation       = $profile.Remediation
                RemediationImpact = $profile.RemediationImpact
                LastModifiedDateTime = $profile.LastModifiedDateTime
            })
        }

        if ($results.Count -gt 0) {
            $results | Export-Csv -Path $script:outputFile -NoTypeInformation -Encoding $Encoding
            Write-LogFile -Message "[INFO] Exported $($results.Count) recommendations to $($script:outputFile)" -Level Standard
        } else {
            Write-LogFile -Message "[WARNING] No Secure Score data found matching the specified criteria" -Color "Yellow" -Level Standard
        }

        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Score analysis completed:" -Level Debug
            Write-LogFile -Message "[DEBUG] Total profiles processed: $($profiles.Count)" -Level Debug
            Write-LogFile -Message "[DEBUG] Results after filtering: $($results.Count)" -Level Debug
        }

        $summary = [ordered]@{
            "Secure Score Overview" = [ordered]@{
                "Current Score" = $latestScore.CurrentScore
                "Maximum Score" = $latestScore.MaxScore
                "Percentage" = if ($latestScore.MaxScore -gt 0) { [math]::Round(($latestScore.CurrentScore / $latestScore.MaxScore) * 100, 2) } else { 0 }
                "As of Date" = $latestScore.CreatedDateTime
            }
            "Recommendations Summary" = [ordered]@{
                "Total Recommendations" = $profiles.Count
                "At Risk" = $atRiskCount
                "Partial" = $partialCount
                "Meets Standard" = $meetsStandardCount
                "Not Applicable" = $notApplicableCount
            }
        }

        Write-Summary -Summary $summary -Title "Secure Score Summary"
    }
    catch {
        Write-LogFile -Message "[ERROR] An error occurred: $($_.Exception.Message)" -Color "Red" -Level Minimal
        if ($isDebugEnabled) {
            Write-LogFile -Message "[DEBUG] Error details:" -Level Debug
            Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug
            Write-LogFile -Message "[DEBUG] Error message: $($_.Exception.Message)" -Level Debug
            Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug
        }
        throw
    }
}