MCSAssessment.psm1


function Invoke-MCSAssessment {
    Param(
    [Alias("Tenant","Tenants")]
    [string]$TenantID,
    [Alias("Subscription","Subscriptions","Subs")]
    [String[]]$SubscriptionID,
    [Alias("Workload","Workloads")]
    [String]$WorkloadName = "Default_Workload",
    [switch]$SkipCompress,
    [switch]$SkipARI,
    [switch]$SkipWARA,
    [switch]$SkipMDC,
    [switch]$SkipMCSB
    )

    # WARA Files
    $RecommendationResourceTypesUri = 'https://azure.github.io/WARA-Build/objects/WARAinScopeResTypes.csv'
    $RecommendationDataUri = 'https://azure.github.io/WARA-Build/objects/recommendations.json'

    Write-Host "Setting Variables.."
    $workingDirectory = (Get-Location).Path
    Write-Host "Working Directory: $workingDirectory"
    if ($workingDirectory -eq "C:\") {
        Write-Host "Error: Working Directory cannot be the root of C:\" -ForegroundColor Red
        exit 1
    }

    Write-Host "Validating Parameters..."
    if (-Not $TenantID) {
        Write-Host "Error: No TenantID" -ForegroundColor Red
        exit 1
    }

    if (-Not $SubscriptionID) {
        Write-Host "Error: No SubscriptionID" -ForegroundColor Red
        exit 1
    }

    <#
    Write-Host "Creating Folders..."
    $subjson = "$workingDirectory/JSON"
    $subcsv = "$workingDirectory/CSV"
    if (-Not (Test-Path $subjson)) {
        New-Item -ItemType Directory -Force -Path $subjson | Out-Null
    }
    if (-Not (Test-Path $subcsv)) {
        New-Item -ItemType Directory -Force -Path $subcsv | Out-Null
    }
    #>


    if (!$SkipARI.IsPresent) {
        Write-Host "Starting ARI Data Collection for Workload: $WorkloadName"
        Invoke-ARI -ReportDir $workingDirectory -ReportName $WorkloadName -NoAutoUpdate -DiagramFullEnvironment -TenantId $TenantID -SubscriptionId $SubscriptionID -Debug -IncludeCosts -Lite
    }

    if (!$SkipWARA.IsPresent) {
        Write-Host "Starting WARA Data Collection for Workload: $WorkloadName"
        Start-WARACollector -tenantid $TenantID -subscriptionid @($SubscriptionID) -Debug

        $WARAFile = Get-ChildItem -Path $workingDirectory -Filter "WARA-File*.json"

        $JSONResources = Get-Item -Path $WARAFile
        $JSONResources = $JSONResources.FullName
        $JSONContent = Get-Content -Path $JSONResources | ConvertFrom-Json

        $RootTypes = Invoke-RestMethod $RecommendationResourceTypesUri | ConvertFrom-Csv
        $RootTypes = $RootTypes | Where-Object { $_.InAprlAndOrAdvisor -eq 'yes' }

        $RecommendationObject = Invoke-RestMethod $RecommendationDataUri

        Write-Host "Count of WARA Recommendations: $($RecommendationObject.Count)"

        $ResourceRecommendations = $RecommendationObject | Where-Object { [string]::IsNullOrEmpty($_.tags) }

        $ResourceCollection = @()

        # First loop through the recommendations to get the impacted resources
        foreach ($Recom in $ResourceRecommendations) {
            $Resources = $JSONContent.ImpactedResources| Where-Object { ($_.recommendationId -eq $Recom.aprlGuid) }

            # If the recommendation is not a Custom Recommendation, we need to validate if the resources are not already in the tmp array (from a previous loop of a Custom Recommendation)
            if ([string]::IsNullOrEmpty($Resources) -and $Recom.aprlGuid -notin $tmp.Guid -and -not $Recom.checkName) {
                $Resources = $JSONContent.ImpactedResources | Where-Object { ($_.recommendationId -eq $Recom.aprlGuid) }
            }

            foreach ($Resource in $Resources) {
                $ResObj = [PSCustomObject]@{
                        'Recommendation Guid'        = $Recom.aprlGuid
                        'Recommendation Title'       = $Recom.description
                        'Description'                = $Recom.longDescription
                        'Priority'                   = $Recom.recommendationImpact
                        'Customer-facing annotation' = ""
                        'Internal-facing notes'      = ($Recom.learnMoreLink.url -join " `n")
                        'Potential Benefit'          = $Recommendation.'Potential Benefit'
                        'Resource Type'              = $Resource.type
                        'Resource ID'                = $Resource.id
                    }

                $ResourceCollection += $ResObj
            }
        }
    }

    Write-Host "Gethering Resource IDs"
    $ResourceList = @()
    $ResourceIDsQuery = "resources | project id"
    foreach ($Subscription in $SubscriptionID) {
        Write-Host "ResourceID loops: $Subscription"
        try
            {
                $QueryResult = Search-AzGraph -Query $ResourceIDsQuery -first 1000 -Subscription $Subscription -Debug:$false
            }
        catch
            {
                $QueryResult = Search-AzGraph -Query $ResourceIDsQuery -first 200 -Subscription $Subscription -Debug:$false
            }

        $ResourceList += $QueryResult
        while ($QueryResult.SkipToken) {
            try
                {
                    $QueryResult = Search-AzGraph -Query $ResourceIDsQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 1000 -Debug:$false
                }
            catch
                {
                    $QueryResult = Search-AzGraph -Query $ResourceIDsQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 200 -Debug:$false
                }
            $ResourceList += $QueryResult
        }
    }

    Write-Host "Starting Security Data Collection for Workload: $WorkloadName"
    $MDCQuery = Get-Content -Path ("$PSScriptRoot\MDC.kql") -Raw -Encoding UTF8
    $MCSBQuery = Get-Content -Path ("$PSScriptRoot\MCSB.kql") -Raw -Encoding UTF8


    if (!$SkipMDC.IsPresent) {
        $MDCLocalResults = @()
        foreach ($Subscription in $SubscriptionID) {
            Write-Host "Processing Security loops: $Subscription"
            try
                {
                    $QueryResult = Search-AzGraph -Query $MDCQuery -first 1000 -Subscription $Subscription -Debug:$false
                }
            catch
                {
                    $QueryResult = Search-AzGraph -Query $MDCQuery -first 200 -Subscription $Subscription -Debug:$false
                }

            $MDCLocalResults += $QueryResult
            while ($QueryResult.SkipToken) {
                try
                    {
                        $QueryResult = Search-AzGraph -Query $MDCQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 1000 -Debug:$false
                    }
                catch
                    {
                        $QueryResult = Search-AzGraph -Query $MDCQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 200 -Debug:$false
                    }
                $MDCLocalResults += $QueryResult
            }
        }

        Write-Host "Exporting Defender for Cloud Recommendations to CSV"
        $MDCLocalResults | Export-Csv -Path ($workingDirectory + "\DefenderForCloudRecommendations.csv") -NoTypeInformation -Encoding UTF8

        Foreach ($MDC in $MDCLocalResults) {
            if ($MDC.state -eq 'Unhealthy') {
                $MDCResourceType = $MDC.resourceId.Split('/')[6]+ '/' + $MDC.resourceId.Split('/')[7]
                $Resourceid = $ResourceList | where-object { $_.id -eq $MDC.resourceId }
                if ([string]::IsNullOrEmpty($Resourceid.id) -eq $false) {
                    $ResObj = [PSCustomObject]@{
                                'Recommendation Guid'        = $MDC.recommendationId
                                'Recommendation Title'       = $MDC.recommendationDisplayName
                                'Description'                = $MDC.description
                                'Priority'                   = $MDC.severity
                                'Customer-facing annotation' = ""
                                'Internal-facing notes'      = $MDC.azurePortalRecommendationLink
                                'Potential Benefit'          = ""
                                'Resource Type'              = $MDCResourceType
                                'Resource ID'                = $Resourceid.id
                            }

                        $ResourceCollection += $ResObj
                }
            }
        }
    }

    if (!$SkipMCSB.IsPresent) 
        {
        $MCSBLocalResults = @()
        foreach ($Subscription in $SubscriptionID) {
            Write-Host "Processing Security loops: $Subscription"
            try
                {
                    $QueryResult = Search-AzGraph -Query $MCSBQuery -first 1000 -Subscription $Subscription -Debug:$false
                }
            catch
                {
                    $QueryResult = Search-AzGraph -Query $MCSBQuery -first 200 -Subscription $Subscription -Debug:$false
                }

            $MCSBLocalResults += $QueryResult
            while ($QueryResult.SkipToken) {
                try
                    {
                        $QueryResult = Search-AzGraph -Query $MCSBQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 1000 -Debug:$false
                    }
                catch
                    {
                        $QueryResult = Search-AzGraph -Query $MCSBQuery -SkipToken $QueryResult.SkipToken -Subscription $Subscription -first 200 -Debug:$false
                    }
                $MCSBLocalResults += $QueryResult
            }
        }

        Write-Host "Exporting Security Benchmark Recommendations to CSV"
        $MCSBLocalResults | Export-Csv -Path ($workingDirectory + "\SecurityBenchmarkRecommendations.csv") -NoTypeInformation -Encoding UTF8

        Foreach ($MCSB in $MCSBLocalResults) {
            if ($MCSB.state -eq 'Unhealthy' -and $MCSB.recommendationMetadataState -eq 'failed') {
            $MCSBResourceType = $MCSB.resourceId.Split('/')[6]+ '/' + $MCSB.resourceId.Split('/')[7]
            $Resourceid = $ResourceList | where-object { $_.id -eq $MCSB.resourceId }
            if ([string]::IsNullOrEmpty($Resourceid.id) -eq $false) {
                $ResObj = [PSCustomObject]@{
                            'Recommendation Guid'        = $MCSB.recommendationId
                            'Recommendation Title'       = $MCSB.recommendationDisplayName
                            'Description'                = $MCSB.description
                            'Priority'                   = $MCSB.severity
                            'Customer-facing annotation' = ""
                            'Internal-facing notes'      = $MCSB.azurePortalRecommendationLink
                            'Potential Benefit'          = ""
                            'Resource Type'              = $MCSBResourceType
                            'Resource ID'                = $Resourceid.id
                        }

                    $ResourceCollection += $ResObj
                }
            }
        }
    }

    Write-Host "Total Recommendation Lines to Export: $($ResourceCollection.Count)"
    if ($ResourceCollection.Count -gt 500) {
        $Loop = $ResourceCollection.Count / 500
        $Loop = [math]::ceiling($Loop)
        $Looper = 0
        $Limit = 0
        while ($Looper -lt $Loop) {
            $Looper ++
            Write-Host "Exporting Partial CSV File: $Looper"
            $ResourceCollection[($Limit - 500)..($Limit - 1)] | Export-Csv -Path "$workingDirectory\Consolidated_Assessment_${WorkloadName}_Part${Looper}.csv" -NoTypeInformation -Encoding UTF8
            $Limit += 500
        }
    }
    else {
        Write-Host "Exporting Complete CSV File"
        $ResourceCollection | Export-Csv -Path "$workingDirectory\Consolidated_Assessment_$WorkloadName.csv" -NoTypeInformation -Encoding UTF8
    }

    <#
    Write-Host "Starting PSRule Data Collection for Workload: $WorkloadName"
    foreach ($Subscription in $SubscriptionID) {
        # PSRule Export
        Write-Host " - Exporting rule data..." -ForegroundColor Yellow
        Export-AzRuleData -OutputPath "$subjson/$Subscription" -Subscription $Subscription
 
        Write-Host " - Generating CSV results..." -ForegroundColor Yellow
        invoke-psrule -inputPath "$subjson/$Subscription/$Subscription.json" -Outcome Fail -Module PSRule.Rules.Azure -OutputPath "$subcsv/$Subscription.csv" -OutputFormat Csv
    }
    #>

    if ($SkipCompress) {
        Write-Host "Skipping compression as per user request." -ForegroundColor Yellow
    } else {
        try
            {
                # Create a temporary folder to gather all contents
                $tempFolder = "$workingDirectory\TempForZip"
                New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null

                $FilesToMove = Get-ChildItem -Path $workingDirectory -Include ("*.json","*.csv") -Recurse
                # Move all files into the temp folder
                foreach ($file in $FilesToMove) {
                    Move-Item -Path $file.FullName -Destination $tempFolder
                }

                # Compress the temp folder into a ZIP file
                Compress-Archive -Path "$tempFolder\*" -DestinationPath ("$workingDirectory\Consolidated_Assessment.zip") -Force
            }
        catch
            {
                Write-Host "Error during compression: " + $_.Exception.Message -ForegroundColor Red
                exit 1
            }
        # Clean up the temporary folder
        Remove-Item -Path $tempFolder -Recurse -Force

        # Output path of the created ZIP file
        Write-Host "The Assessment Data Collection ZIP file is stored at the location : $zipFilePath" -foregroundColor Green
        # End of the script
    }

    # CleanUp
    <#
    Write-Host "Cleaning up WARA and ARI generated files..."
    if (Test-Path -PathType Leaf -Path $WARAFile) {
        Remove-Item $WARAFile -Force
    }
    #>

    if (Test-Path -PathType Container -Path "$workingDirectory\DiagramCache") {
        Remove-Item "$workingDirectory\DiagramCache" -Force -Recurse
    }
    if (Test-Path -PathType Container -Path "$workingDirectory\ReportCache") {
        Remove-Item "$workingDirectory\ReportCache" -Force -Recurse
    }
    if (Test-Path -PathType Leaf -Path "$workingDirectory\DiagramLogFile.log") {
        Remove-Item "$workingDirectory\DiagramLogFile.log" -Force
    }
}