Src/Private/Get-AbrIntuneScripts.ps1

function Get-AbrIntuneScripts {
    <#
    .SYNOPSIS
    Documents Intune PowerShell scripts, Shell scripts and Proactive Remediations.
    .DESCRIPTION
        InfoLevel 1: Summary table per script type (name, run-as, enforcement, assignments)
        InfoLevel 2: Per-script detail section with overview + decoded script content
    .NOTES
        Version: 0.2.1
        Author: Pai Wei Sing
    #>

    [CmdletBinding()]
    param ([Parameter(Position = 0, Mandatory)][string]$TenantId)

    begin {
        Write-PScriboMessage -Message "Collecting Intune Scripts for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'Scripts'
    }

    process {
        Section -Style Heading2 'Scripts & Remediations' {
            $TotalRemediations = 0
            $UnsignedScripts   = 0
            Paragraph "The following section documents scripts and Proactive Remediations deployed via Microsoft Intune in tenant $TenantId."
            BlankLine

            #region Helper: decode Base64 script content
            # Graph returns scriptContent as Base64 -- decode to plain text for the report
            # Truncate long scripts to keep the report readable
            function Get-DecodedScriptContent {
                param([string]$Base64Content, [int]$MaxLines = 40)
                if (-not $Base64Content) { return '--' }
                try {
                    $decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Base64Content))
                    $lines   = $decoded -split "`n"
                    if ($lines.Count -gt $MaxLines) {
                        $truncated = ($lines | Select-Object -First $MaxLines) -join "`n"
                        return "$truncated`n`n... [Script truncated -- $($lines.Count) lines total. See JSON backup for full content.]"
                    }
                    return $decoded.Trim()
                } catch {
                    return '[Could not decode script content]'
                }
            }
            #endregion

            #region Windows PowerShell Scripts
            if ($script:TenantHasIntuneP1 -eq $false) {
                Write-Host ' - Skipping Windows PowerShell Scripts (Intune Plan 1 not detected).' -ForegroundColor Yellow
                Write-AbrDebugLog 'Windows PowerShell Scripts skipped -- no Intune P1 licence' 'WARN' 'SCRIPTS'
            } else {
            try {
                Write-Host " - Retrieving Windows PowerShell scripts..."
                $PSScriptsResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/beta/deviceManagement/deviceManagementScripts?`$expand=assignments" `
                    -ErrorAction SilentlyContinue
                $PSScripts = $PSScriptsResp.value

                if ($PSScripts -and @($PSScripts).Count -gt 0) {
                    $UnsignedScripts += @($PSScripts | Where-Object { -not $_.enforceSignatureCheck }).Count
                    Section -Style Heading3 'Windows PowerShell Scripts' {
                        BlankLine
                        $PSObj = [System.Collections.ArrayList]::new()
                        foreach ($Script in ($PSScripts | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $Script.assignments -CheckMemberCount:$script:CheckEmptyGroups
                            $psInObj = [ordered] @{
                                'Script Name'        = $Script.displayName
                                'Run As Account'     = if ($Script.runAsAccount) { $Script.runAsAccount } else { '--' }
                                'Run As 32-bit'      = $Script.runAs32Bit
                                'Enforce Sig. Check' = $Script.enforceSignatureCheck
                                'Retry Count'        = if ($null -ne $Script.retryCount) { $Script.retryCount } else { '--' }
                                'Included Groups'    = $assignResolved.IncludedGroups
                                'Excluded Groups'    = $assignResolved.ExcludedGroups
                                'Last Modified'      = if ($Script.lastModifiedDateTime) { ([datetime]$Script.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            }
                            $PSObj.Add([pscustomobject](ConvertTo-HashToYN $psInObj)) | Out-Null
                        }
                        $null = (& { if ($HealthCheck.Intune.EndpointSecurity) {
                            $null = ($PSObj | Where-Object { $_.'Included Groups' -eq '--' } | Set-Style -Style Warning | Out-Null)
                            $null = ($PSObj | Where-Object { $_.'Enforce Sig. Check' -eq 'No' } | Set-Style -Style Warning | Out-Null)
                        }})
                        $PSTableParams = @{ Name = "Windows PowerShell Scripts - $TenantId"; ColumnWidths = 20, 11, 9, 11, 7, 20, 14, 8 }
                        if ($Report.ShowTableCaptions) { $PSTableParams['Caption'] = "- $($PSTableParams.Name)" }
                        $PSObj | Table @PSTableParams

                        if (Get-IntuneExcelSheetEnabled -SheetKey 'PowerShellScripts') { $script:ExcelSheets['PowerShell Scripts'] = $PSObj }
                        if (Get-IntuneBackupSectionEnabled -SectionKey 'Scripts')       { $script:BackupData['PowerShellScripts'] = $PSScripts }

                        #region InfoLevel 2 -- per-script detail + content
                        if ($InfoLevel.Scripts -ge 2) {
                            foreach ($Script in ($PSScripts | Sort-Object displayName)) {
                                $assignResolved = Resolve-IntuneAssignments -Assignments $Script.assignments
                                Section -Style Heading4 $Script.displayName {
                                    BlankLine
                                    # Overview
                                    $ovObj = [System.Collections.ArrayList]::new()
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Script Name';          Value = $Script.displayName }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Description';          Value = if ($Script.description) { $Script.description } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Run As Account';       Value = if ($Script.runAsAccount) { $Script.runAsAccount } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Run As 32-bit';        Value = if ($Script.runAs32Bit) { 'Yes' } else { 'No' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Enforce Sig. Check';   Value = if ($Script.enforceSignatureCheck) { 'Yes' } else { 'No' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Retry Count';          Value = if ($null -ne $Script.retryCount) { "$($Script.retryCount)" } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Filename';             Value = if ($Script.fileName) { $Script.fileName } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Included Groups';      Value = $assignResolved.IncludedGroups }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Excluded Groups';      Value = $assignResolved.ExcludedGroups }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Scope Tags';           Value = if ($script:ResolveScopeTagNames -and $Script.roleScopeTagIds) { Get-IntuneScopeTagNames -ScopeTagIds $Script.roleScopeTagIds } else { 'Default' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Last Modified';        Value = if ($Script.lastModifiedDateTime) { ([datetime]$Script.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null
                                    $OvParams = @{ Name = "Script Detail - $($Script.displayName)"; ColumnWidths = 30, 70 }
                                    if ($Report.ShowTableCaptions) { $OvParams['Caption'] = "- $($OvParams.Name)" }
                                    $ovObj | Table @OvParams

                                    # Script content
                                    if ($Script.scriptContent) {
                                        BlankLine
                                        Paragraph 'Script Content:'
                                        BlankLine
                                        $decoded = Get-DecodedScriptContent -Base64Content $Script.scriptContent -MaxLines 40
                                        Paragraph $decoded
                                    }
                                }
                            }
                        }
                        #endregion
                    }
                }
            } catch {
                if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Windows PowerShell Scripts' -RequiredRole 'Intune Service Administrator or Global Administrator' }
                else { Write-AbrSectionError -Section 'Windows PowerShell Scripts' -Message "$($_.Exception.Message)" }
            }
            } # end licence gate
            #endregion

            #region macOS Shell Scripts
            if ($script:TenantHasIntuneP1 -eq $false) {
                Write-Host ' - Skipping macOS Shell Scripts (Intune Plan 1 not detected).' -ForegroundColor Yellow
                Write-AbrDebugLog 'macOS Shell Scripts skipped -- no Intune P1 licence' 'WARN' 'SCRIPTS'
            } else {
            try {
                Write-Host " - Retrieving macOS Shell scripts..."
                $ShellScriptsResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/beta/deviceManagement/deviceShellScripts?`$expand=assignments" `
                    -ErrorAction SilentlyContinue
                $ShellScripts = $ShellScriptsResp.value

                if ($ShellScripts -and @($ShellScripts).Count -gt 0) {
                    Section -Style Heading3 'macOS Shell Scripts' {
                        BlankLine
                        $ShellObj = [System.Collections.ArrayList]::new()
                        foreach ($Script in ($ShellScripts | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $Script.assignments -CheckMemberCount:$script:CheckEmptyGroups
                            $shellInObj = [ordered] @{
                                'Script Name'         = $Script.displayName
                                'Run As Account'      = if ($Script.runAsAccount) { $Script.runAsAccount } else { '--' }
                                'Execution Frequency' = if ($Script.executionFrequency) { $Script.executionFrequency } else { '--' }
                                'Retry Count'         = if ($null -ne $Script.retryCount) { $Script.retryCount } else { '--' }
                                'Included Groups'     = $assignResolved.IncludedGroups
                                'Excluded Groups'     = $assignResolved.ExcludedGroups
                                'Last Modified'       = if ($Script.lastModifiedDateTime) { ([datetime]$Script.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            }
                            $ShellObj.Add([pscustomobject]$shellInObj) | Out-Null
                        }
                        $ShellTableParams = @{ Name = "macOS Shell Scripts - $TenantId"; ColumnWidths = 20, 12, 14, 8, 20, 16, 10 }
                        if ($Report.ShowTableCaptions) { $ShellTableParams['Caption'] = "- $($ShellTableParams.Name)" }
                        $ShellObj | Table @ShellTableParams

                        if (Get-IntuneExcelSheetEnabled -SheetKey 'ShellScripts') { $script:ExcelSheets['Shell Scripts'] = $ShellObj }
                        if (Get-IntuneBackupSectionEnabled -SectionKey 'Scripts') {
                            if ($script:BackupData['ShellScripts']) { $script:BackupData['ShellScripts'] += $ShellScripts } else { $script:BackupData['ShellScripts'] = $ShellScripts }
                        }

                        #region InfoLevel 2
                        if ($InfoLevel.Scripts -ge 2) {
                            foreach ($Script in ($ShellScripts | Sort-Object displayName)) {
                                $assignResolved = Resolve-IntuneAssignments -Assignments $Script.assignments
                                Section -Style Heading4 $Script.displayName {
                                    BlankLine
                                    $ovObj = [System.Collections.ArrayList]::new()
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Script Name';          Value = $Script.displayName }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Description';          Value = if ($Script.description) { $Script.description } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Run As Account';       Value = if ($Script.runAsAccount) { $Script.runAsAccount } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Execution Frequency';  Value = if ($Script.executionFrequency) { $Script.executionFrequency } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Retry Count';          Value = if ($null -ne $Script.retryCount) { "$($Script.retryCount)" } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Filename';             Value = if ($Script.fileName) { $Script.fileName } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Included Groups';      Value = $assignResolved.IncludedGroups }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Excluded Groups';      Value = $assignResolved.ExcludedGroups }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Last Modified';        Value = if ($Script.lastModifiedDateTime) { ([datetime]$Script.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null
                                    $OvParams = @{ Name = "Shell Script Detail - $($Script.displayName)"; ColumnWidths = 30, 70 }
                                    if ($Report.ShowTableCaptions) { $OvParams['Caption'] = "- $($OvParams.Name)" }
                                    $ovObj | Table @OvParams

                                    if ($Script.scriptContent) {
                                        BlankLine
                                        Paragraph 'Script Content:'
                                        BlankLine
                                        $decoded = Get-DecodedScriptContent -Base64Content $Script.scriptContent -MaxLines 40
                                        Paragraph $decoded
                                    }
                                }
                            }
                        }
                        #endregion
                    }
                }
            } catch {
                if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'macOS Shell Scripts' -RequiredRole 'Intune Service Administrator or Global Administrator' }
                else { Write-AbrSectionError -Section 'macOS Shell Scripts' -Message "$($_.Exception.Message)" }
            }
            } # end licence gate
            #endregion

            #region Proactive Remediations
            if ($script:TenantHasIntuneP2 -eq $false) {
                Write-Host ' - Skipping Proactive Remediations (Intune Plan 2 / Suite not detected).' -ForegroundColor Yellow
                Write-AbrDebugLog 'Proactive Remediations skipped -- no Intune P2/Suite licence' 'WARN' 'SCRIPTS'
            } else {
            try {
                Write-Host " - Retrieving Proactive Remediations..."
                $RemediationsResp = Invoke-MgGraphRequest -Method GET `
                    -Uri "$($script:GraphEndpoint)/beta/deviceManagement/deviceHealthScripts?`$expand=assignments" `
                    -ErrorAction SilentlyContinue
                $Remediations = $RemediationsResp.value

                $null = ($TotalRemediations = @($Remediations).Count)
                if ($TotalRemediations -gt 0) {
                    Section -Style Heading3 'Proactive Remediations' {
                        BlankLine
                        $RemObj = [System.Collections.ArrayList]::new()
                        foreach ($Rem in ($Remediations | Sort-Object displayName)) {
                            $assignResolved = Resolve-IntuneAssignments -Assignments $Rem.assignments -CheckMemberCount:$script:CheckEmptyGroups
                            $remInObj = [ordered] @{
                                'Remediation Name'   = $Rem.displayName
                                'Publisher'          = if ($Rem.publisher) { $Rem.publisher } else { '--' }
                                'Run As Account'     = if ($Rem.runAsAccount) { $Rem.runAsAccount } else { '--' }
                                'Run As 32-bit'      = $Rem.runAs32Bit
                                'Enforce Sig. Check' = $Rem.enforceSignatureCheck
                                'Included Groups'    = $assignResolved.IncludedGroups
                                'Excluded Groups'    = $assignResolved.ExcludedGroups
                                'Last Modified'      = if ($Rem.lastModifiedDateTime) { ([datetime]$Rem.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' }
                            }
                            $RemObj.Add([pscustomobject](ConvertTo-HashToYN $remInObj)) | Out-Null
                        }
                        $RemTableParams = @{ Name = "Proactive Remediations - $TenantId"; ColumnWidths = 18, 12, 11, 8, 10, 18, 15, 8 }
                        if ($Report.ShowTableCaptions) { $RemTableParams['Caption'] = "- $($RemTableParams.Name)" }
                        $RemObj | Table @RemTableParams

                        if (Get-IntuneExcelSheetEnabled -SheetKey 'ProactiveRemediations') { $script:ExcelSheets['Proactive Remediations'] = $RemObj }
                        if (Get-IntuneBackupSectionEnabled -SectionKey 'Scripts')           { $script:BackupData['ProactiveRemediations'] = $Remediations }

                        #region InfoLevel 2 -- per-remediation detail + both scripts
                        if ($InfoLevel.Scripts -ge 2) {
                            foreach ($Rem in ($Remediations | Sort-Object displayName)) {
                                $assignResolved = Resolve-IntuneAssignments -Assignments $Rem.assignments
                                Section -Style Heading4 $Rem.displayName {
                                    BlankLine
                                    # Overview
                                    $ovObj = [System.Collections.ArrayList]::new()
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Remediation Name';     Value = $Rem.displayName }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Description';          Value = if ($Rem.description) { $Rem.description } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Publisher';            Value = if ($Rem.publisher) { $Rem.publisher } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Version';              Value = if ($Rem.version) { $Rem.version } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Run As Account';       Value = if ($Rem.runAsAccount) { $Rem.runAsAccount } else { '--' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Run As 32-bit';        Value = if ($Rem.runAs32Bit) { 'Yes' } else { 'No' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Enforce Sig. Check';   Value = if ($Rem.enforceSignatureCheck) { 'Yes' } else { 'No' } }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Included Groups';      Value = $assignResolved.IncludedGroups }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Excluded Groups';      Value = $assignResolved.ExcludedGroups }) | Out-Null
                                    $ovObj.Add([pscustomobject]@{ Setting = 'Last Modified';        Value = if ($Rem.lastModifiedDateTime) { ([datetime]$Rem.lastModifiedDateTime).ToString('yyyy-MM-dd') } else { '--' } }) | Out-Null
                                    $OvParams = @{ Name = "Remediation Detail - $($Rem.displayName)"; ColumnWidths = 30, 70 }
                                    if ($Report.ShowTableCaptions) { $OvParams['Caption'] = "- $($OvParams.Name)" }
                                    $ovObj | Table @OvParams

                                    # Detection script
                                    if ($Rem.detectionScriptContent) {
                                        BlankLine
                                        Paragraph 'Detection Script:'
                                        BlankLine
                                        $decoded = Get-DecodedScriptContent -Base64Content $Rem.detectionScriptContent -MaxLines 40
                                        Paragraph $decoded
                                    }

                                    # Remediation script
                                    if ($Rem.remediationScriptContent) {
                                        BlankLine
                                        Paragraph 'Remediation Script:'
                                        BlankLine
                                        $decoded = Get-DecodedScriptContent -Base64Content $Rem.remediationScriptContent -MaxLines 40
                                        Paragraph $decoded
                                    }
                                }
                            }
                        }
                        #endregion
                    }
                }
            } catch {
                if (Test-AbrGraphForbidden -ErrorRecord $_) { Write-AbrPermissionError -Section 'Proactive Remediations' -RequiredRole 'Intune Service Administrator or Global Administrator' }
                else { Write-AbrSectionError -Section 'Proactive Remediations' -Message "$($_.Exception.Message)" }
            }
            } # end licence gate
            #endregion

            #region ACSC E8 Assessment
            if ($script:IncludeACSCe8) {
                BlankLine
                Paragraph "ACSC Essential Eight Maturity Level Assessment -- Scripts and Remediations:"
                BlankLine
                try {
                    $_v = @{ TotalRemediations = $TotalRemediations; UnsignedScripts = $UnsignedScripts }
                    $E8Checks = Build-AbrIntuneComplianceChecks -Definitions (Get-AbrIntuneE8Checks -Section 'Scripts') -Framework E8 -CallerVariables $_v
                    New-AbrIntuneE8AssessmentTable -Checks $E8Checks -Name 'Scripts' -TenantId $TenantId
                    if ($E8Checks) { $null = $script:E8AllChecks.AddRange([object[]](@($E8Checks | Select-Object @{N='Section';E={'Scripts'}}, ML, Control, Status, Detail))) }
                } catch { Write-AbrSectionError -Section 'E8 Scripts Assessment' -Message "$($_.Exception.Message)" }
            }
            #endregion

            #region CIS Assessment
            if ($script:IncludeCISBaseline) {
                BlankLine
                Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- Scripts:"
                BlankLine
                try {
                    $_v = @{ UnsignedScripts = $UnsignedScripts }
                    $CISChecks = Build-AbrIntuneComplianceChecks -Definitions (Get-AbrIntuneCISChecks -Section 'Scripts') -Framework CIS -CallerVariables $_v
                    New-AbrIntuneCISAssessmentTable -Checks $CISChecks -Name 'Scripts' -TenantId $TenantId
                    if ($CISChecks) { $null = $script:CISAllChecks.AddRange([object[]](@($CISChecks | Select-Object @{N='Section';E={'Scripts'}}, CISControl, Level, Status, Detail))) }
                } catch { Write-AbrSectionError -Section 'CIS Scripts Assessment' -Message "$($_.Exception.Message)" }
            }
            #endregion
        }
    }

    end { Show-AbrDebugExecutionTime -End -TitleMessage 'Scripts' }
}