Private/Entra/Checks/Invoke-IntuneChecks.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Invoke-IntuneChecks { [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable]$AuditData ) $checkDefs = Get-AuditCategoryDefinitions -Category 'IntuneChecks' $findings = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($check in $checkDefs.checks) { $funcName = "Test-Infiltration$($check.id -replace '-', '')" if (Get-Command $funcName -ErrorAction SilentlyContinue) { try { $finding = & $funcName -AuditData $AuditData -CheckDefinition $check if ($finding) { $findings.Add($finding) } } catch { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'ERROR' ` -CurrentValue "Check failed: $_")) } } else { $findings.Add((New-AuditFinding -CheckDefinition $check -Status 'SKIP' ` -CurrentValue 'Check not yet implemented')) } } return @($findings) } # ── INTUNE-001: Compliance Policy Inventory ────────────────────────────── function Test-InfiltrationINTUNE001 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $policies = $intune.CompliancePolicies if (-not $policies -or $policies.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue 'No Intune compliance policies found' ` -Details @{ PolicyCount = 0 } } # Group by platform/type $byType = @{} foreach ($p in $policies) { $type = $p.'@odata.type' ?? 'Unknown' if (-not $byType.ContainsKey($type)) { $byType[$type] = 0 } $byType[$type]++ } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($policies.Count) compliance policies configured" ` -Details @{ PolicyCount = $policies.Count ByType = @($byType.GetEnumerator() | ForEach-Object { @{ Type = $_.Key; Count = $_.Value } }) Policies = @($policies | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName Type = $_.'@odata.type' CreatedDateTime = $_.createdDateTime } }) } } # ── INTUNE-002: Compliance Summary ─────────────────────────────────────── function Test-InfiltrationINTUNE002 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.ComplianceSummary) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune compliance summary not available' } $summary = $intune.ComplianceSummary $compliant = $summary.compliantDeviceCount ?? 0 $nonCompliant = $summary.nonCompliantDeviceCount ?? 0 $inGracePeriod = $summary.inGracePeriodCount ?? 0 $notEvaluated = $summary.notEvaluatedDeviceCount ?? 0 $error = $summary.errorDeviceCount ?? 0 $conflict = $summary.conflictDeviceCount ?? 0 $total = $compliant + $nonCompliant + $inGracePeriod + $notEvaluated + $error + $conflict $nonCompliantPct = if ($total -gt 0) { [Math]::Round(($nonCompliant / $total) * 100, 1) } else { 0 } $status = if ($nonCompliant -eq 0) { 'PASS' } elseif ($nonCompliantPct -le 10) { 'WARN' } else { 'FAIL' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "Compliance: $compliant compliant, $nonCompliant non-compliant ($nonCompliantPct%), $inGracePeriod grace period, $error errors" ` -Details @{ Compliant = $compliant NonCompliant = $nonCompliant InGracePeriod = $inGracePeriod NotEvaluated = $notEvaluated Error = $error Conflict = $conflict Total = $total NonCompliantPercentage = $nonCompliantPct } } # ── INTUNE-003: Non-Compliant Devices ─────────────────────────────────── function Test-InfiltrationINTUNE003 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.ManagedDevices) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Managed device data not available' } $devices = $intune.ManagedDevices $nonCompliant = @($devices | Where-Object { $_.complianceState -ne 'compliant' }) $total = $devices.Count $pct = if ($total -gt 0) { [Math]::Round(($nonCompliant.Count / $total) * 100, 1) } else { 0 } $status = if ($nonCompliant.Count -eq 0) { 'PASS' } elseif ($pct -le 10) { 'WARN' } else { 'FAIL' } # Group non-compliant by compliance state $byState = @{} foreach ($d in $nonCompliant) { $state = $d.complianceState ?? 'unknown' if (-not $byState.ContainsKey($state)) { $byState[$state] = 0 } $byState[$state]++ } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "$($nonCompliant.Count) of $total devices non-compliant ($pct%)" ` -Details @{ NonCompliantCount = $nonCompliant.Count TotalDevices = $total Percentage = $pct ByState = @($byState.GetEnumerator() | ForEach-Object { @{ State = $_.Key; Count = $_.Value } }) Devices = @($nonCompliant | Select-Object -First 50 | ForEach-Object { @{ DeviceName = $_.deviceName OS = $_.operatingSystem ComplianceState = $_.complianceState LastSync = $_.lastSyncDateTime } }) } } # ── INTUNE-004: Configuration Profile Inventory ───────────────────────── function Test-InfiltrationINTUNE004 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $configs = $intune.DeviceConfigurations if (-not $configs -or $configs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No device configuration profiles found' ` -Details @{ ProfileCount = 0 } } $byType = @{} foreach ($c in $configs) { $type = $c.'@odata.type' ?? 'Unknown' if (-not $byType.ContainsKey($type)) { $byType[$type] = 0 } $byType[$type]++ } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($configs.Count) device configuration profiles" ` -Details @{ ProfileCount = $configs.Count ByType = @($byType.GetEnumerator() | ForEach-Object { @{ Type = $_.Key; Count = $_.Value } }) Profiles = @($configs | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName Type = $_.'@odata.type' CreatedDateTime = $_.createdDateTime } }) } } # ── INTUNE-005: Configuration Profile Assignment Analysis ─────────────── function Test-InfiltrationINTUNE005 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.DeviceConfigurations) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Device configuration data not available' } $configs = $intune.DeviceConfigurations # Check for unassigned profiles (no assignments property or empty) $unassigned = @($configs | Where-Object { -not $_.assignments -or $_.assignments.Count -eq 0 }) $status = if ($unassigned.Count -eq 0) { 'PASS' } elseif ($unassigned.Count -le 3) { 'WARN' } else { 'FAIL' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "$($unassigned.Count) of $($configs.Count) configuration profiles appear unassigned" ` -Details @{ TotalProfiles = $configs.Count UnassignedCount = $unassigned.Count Unassigned = @($unassigned | ForEach-Object { @{ Id = $_.id; DisplayName = $_.displayName; Type = $_.'@odata.type' } }) } } # ── INTUNE-006: Windows Update Rings ──────────────────────────────────── function Test-InfiltrationINTUNE006 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.DeviceConfigurations) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Device configuration data not available' } $updateConfigs = @($intune.DeviceConfigurations | Where-Object { $_.'@odata.type' -match 'windowsUpdateForBusiness' -or $_.'@odata.type' -match 'Update' -or $_.displayName -match 'update ring|windows update' }) if ($updateConfigs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No Windows Update ring configurations found' ` -Details @{ UpdateRingCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($updateConfigs.Count) Windows Update ring configurations found" ` -Details @{ UpdateRingCount = $updateConfigs.Count Rings = @($updateConfigs | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName Type = $_.'@odata.type' } }) } } # ── INTUNE-007: BitLocker / Encryption Configuration ─────────────────── function Test-InfiltrationINTUNE007 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.DeviceConfigurations) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Device configuration data not available' } $encryptionConfigs = @($intune.DeviceConfigurations | Where-Object { $_.'@odata.type' -match 'bitLocker|encryption' -or $_.displayName -match 'BitLocker|encryption|disk encrypt' }) if ($encryptionConfigs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue 'No BitLocker/encryption configuration profiles found' ` -Details @{ EncryptionConfigCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($encryptionConfigs.Count) encryption configuration profile(s) found" ` -Details @{ EncryptionConfigCount = $encryptionConfigs.Count Configs = @($encryptionConfigs | ForEach-Object { @{ Id = $_.id; DisplayName = $_.displayName; Type = $_.'@odata.type' } }) } } # ── INTUNE-008: Defender / Antivirus Configuration ───────────────────── function Test-InfiltrationINTUNE008 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.DeviceConfigurations) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Device configuration data not available' } $defenderConfigs = @($intune.DeviceConfigurations | Where-Object { $_.'@odata.type' -match 'defender|antivirus|endpointProtection' -or $_.displayName -match 'Defender|antivirus|endpoint protection|AV policy' }) if ($defenderConfigs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue 'No Defender/antivirus configuration profiles found' ` -Details @{ DefenderConfigCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($defenderConfigs.Count) Defender/antivirus configuration profile(s) found" ` -Details @{ DefenderConfigCount = $defenderConfigs.Count Configs = @($defenderConfigs | ForEach-Object { @{ Id = $_.id; DisplayName = $_.displayName; Type = $_.'@odata.type' } }) } } # ── INTUNE-009: Attack Surface Reduction (ASR) ───────────────────────── function Test-InfiltrationINTUNE009 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.DeviceConfigurations) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Device configuration data not available' } $asrConfigs = @($intune.DeviceConfigurations | Where-Object { $_.'@odata.type' -match 'attackSurfaceReduction|asr' -or $_.displayName -match 'ASR|attack surface reduction' }) if ($asrConfigs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No Attack Surface Reduction configuration profiles found' ` -Details @{ ASRConfigCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($asrConfigs.Count) ASR configuration profile(s) found" ` -Details @{ ASRConfigCount = $asrConfigs.Count Configs = @($asrConfigs | ForEach-Object { @{ Id = $_.id; DisplayName = $_.displayName; Type = $_.'@odata.type' } }) } } # ── INTUNE-010: Endpoint Detection and Response (EDR) ────────────────── function Test-InfiltrationINTUNE010 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.DeviceConfigurations) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Device configuration data not available' } $edrConfigs = @($intune.DeviceConfigurations | Where-Object { $_.'@odata.type' -match 'endpointDetection|edr' -or $_.displayName -match 'EDR|endpoint detection|MDE|Microsoft Defender for Endpoint' }) if ($edrConfigs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No EDR configuration profiles found — verify MDE onboarding via another method' ` -Details @{ EDRConfigCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($edrConfigs.Count) EDR configuration profile(s) found" ` -Details @{ EDRConfigCount = $edrConfigs.Count Configs = @($edrConfigs | ForEach-Object { @{ Id = $_.id; DisplayName = $_.displayName; Type = $_.'@odata.type' } }) } } # ── INTUNE-011: App Protection Policies ───────────────────────────────── function Test-InfiltrationINTUNE011 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $appPolicies = $intune.AppProtectionPolicies if (-not $appPolicies -or $appPolicies.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No app protection (MAM) policies found' ` -Details @{ PolicyCount = 0 } } $byType = @{} foreach ($p in $appPolicies) { $type = $p.'@odata.type' ?? 'Unknown' if (-not $byType.ContainsKey($type)) { $byType[$type] = 0 } $byType[$type]++ } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($appPolicies.Count) app protection policies configured" ` -Details @{ PolicyCount = $appPolicies.Count ByType = @($byType.GetEnumerator() | ForEach-Object { @{ Type = $_.Key; Count = $_.Value } }) Policies = @($appPolicies | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName Type = $_.'@odata.type' } }) } } # ── INTUNE-012: Conditional Launch Settings ───────────────────────────── function Test-InfiltrationINTUNE012 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $appPolicies = $intune.AppProtectionPolicies if (-not $appPolicies -or $appPolicies.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No app protection policies — conditional launch not configured' ` -Details @{ PolicyCount = 0 } } # Check for policies with conditional launch actions (jailbreak, min OS, etc.) $withConditionalLaunch = @($appPolicies | Where-Object { $_.minimumRequiredOsVersion -or $_.minimumRequiredAppVersion -or $_.maximumRequiredOsVersion -or $_.'@odata.type' -match 'managedAppProtection' }) $status = if ($withConditionalLaunch.Count -gt 0) { 'PASS' } else { 'WARN' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "$($withConditionalLaunch.Count) app protection policies with conditional launch settings" ` -Details @{ TotalPolicies = $appPolicies.Count WithConditionalLaunch = $withConditionalLaunch.Count } } # ── INTUNE-013: Enrollment Restrictions ───────────────────────────────── function Test-InfiltrationINTUNE013 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $enrollConfigs = $intune.EnrollmentConfigurations if (-not $enrollConfigs -or $enrollConfigs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No enrollment configurations found' ` -Details @{ ConfigCount = 0 } } # Look for platform restrictions $platformRestrictions = @($enrollConfigs | Where-Object { $_.'@odata.type' -match 'deviceEnrollmentPlatformRestrictions' -or $_.'@odata.type' -match 'PlatformRestriction' }) # Look for enrollment limit configurations $limitConfigs = @($enrollConfigs | Where-Object { $_.'@odata.type' -match 'deviceEnrollmentLimitConfiguration' }) return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($enrollConfigs.Count) enrollment configs ($($platformRestrictions.Count) platform restrictions, $($limitConfigs.Count) limit configs)" ` -Details @{ TotalConfigs = $enrollConfigs.Count PlatformRestrictions = $platformRestrictions.Count LimitConfigs = $limitConfigs.Count Configs = @($enrollConfigs | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName Type = $_.'@odata.type' Priority = $_.priority } }) } } # ── INTUNE-014: Autopilot Configuration ───────────────────────────────── function Test-InfiltrationINTUNE014 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $profiles = $intune.AutopilotProfiles if (-not $profiles -or $profiles.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No Windows Autopilot deployment profiles found' ` -Details @{ ProfileCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($profiles.Count) Autopilot deployment profile(s) configured" ` -Details @{ ProfileCount = $profiles.Count Profiles = @($profiles | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName DeviceNameTemplate = $_.deviceNameTemplate Language = $_.language ExtractHardwareHash = $_.extractHardwareHash } }) } } # ── INTUNE-015: Disk Encryption Status ────────────────────────────────── function Test-InfiltrationINTUNE015 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.ManagedDevices) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Managed device data not available' } $devices = $intune.ManagedDevices $encrypted = @($devices | Where-Object { $_.isEncrypted -eq $true }) $notEncrypted = @($devices | Where-Object { $_.isEncrypted -eq $false }) $unknown = @($devices | Where-Object { $null -eq $_.isEncrypted }) $total = $devices.Count $encryptedPct = if ($total -gt 0) { [Math]::Round(($encrypted.Count / $total) * 100, 1) } else { 0 } $status = if ($notEncrypted.Count -eq 0 -and $unknown.Count -eq 0) { 'PASS' } elseif ($encryptedPct -ge 90) { 'WARN' } else { 'FAIL' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "$($encrypted.Count) of $total devices encrypted ($encryptedPct%), $($notEncrypted.Count) not encrypted, $($unknown.Count) unknown" ` -Details @{ Encrypted = $encrypted.Count NotEncrypted = $notEncrypted.Count Unknown = $unknown.Count Total = $total EncryptedPercentage = $encryptedPct NotEncryptedDevices = @($notEncrypted | Select-Object -First 50 | ForEach-Object { @{ DeviceName = $_.deviceName; OS = $_.operatingSystem; IsEncrypted = $_.isEncrypted } }) } } # ── INTUNE-016: Firewall Policy ───────────────────────────────────────── function Test-InfiltrationINTUNE016 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune -or -not $intune.DeviceConfigurations) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Device configuration data not available' } $firewallConfigs = @($intune.DeviceConfigurations | Where-Object { $_.'@odata.type' -match 'firewall' -or $_.displayName -match 'firewall|windows firewall' }) if ($firewallConfigs.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No firewall configuration profiles found in Intune' ` -Details @{ FirewallConfigCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($firewallConfigs.Count) firewall configuration profile(s) found" ` -Details @{ FirewallConfigCount = $firewallConfigs.Count Configs = @($firewallConfigs | ForEach-Object { @{ Id = $_.id; DisplayName = $_.displayName; Type = $_.'@odata.type' } }) } } # ── INTUNE-017: Security Baselines ────────────────────────────────────── function Test-InfiltrationINTUNE017 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $baselines = $intune.SecurityBaselines if (-not $baselines -or $baselines.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue 'No security baseline templates found' ` -Details @{ BaselineCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($baselines.Count) security baseline template(s) available" ` -Details @{ BaselineCount = $baselines.Count Baselines = @($baselines | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName TemplateType = $_.templateType PublishedDateTime = $_.publishedDateTime } }) } } # ── INTUNE-018: PowerShell Scripts Deployed ───────────────────────────── function Test-InfiltrationINTUNE018 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $scripts = $intune.DeviceManagementScripts if (-not $scripts -or $scripts.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'No PowerShell scripts deployed via Intune' ` -Details @{ ScriptCount = 0 } } # PowerShell scripts deployed via Intune can be a security risk - review recommended $runAsSystem = @($scripts | Where-Object { $_.runAsAccount -eq 'system' }) $unsignedScripts = @($scripts | Where-Object { $_.enforceSignatureCheck -ne $true }) $status = if ($runAsSystem.Count -gt 0 -or $unsignedScripts.Count -gt 0) { 'WARN' } else { 'PASS' } return New-AuditFinding -CheckDefinition $CheckDefinition -Status $status ` -CurrentValue "$($scripts.Count) PowerShell scripts deployed via Intune ($($runAsSystem.Count) run as SYSTEM, $($unsignedScripts.Count) unsigned)" ` -Details @{ ScriptCount = $scripts.Count RunAsSystemCount = $runAsSystem.Count Scripts = @($scripts | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName FileName = $_.fileName RunAsAccount = $_.runAsAccount EnforceSignatureCheck = $_.enforceSignatureCheck CreatedDateTime = $_.createdDateTime } }) } } # ── INTUNE-019: Win32 App Inventory ───────────────────────────────────── function Test-InfiltrationINTUNE019 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $apps = $intune.MobileApps $win32Apps = @($apps | Where-Object { $_.'@odata.type' -eq '#microsoft.graph.win32LobApp' }) if ($win32Apps.Count -eq 0 -and $apps.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'No Win32 apps found in Intune' ` -Details @{ Win32AppCount = 0; TotalAppCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($win32Apps.Count) Win32 app(s) deployed (of $($apps.Count) total mobile apps)" ` -Details @{ Win32AppCount = $win32Apps.Count TotalAppCount = $apps.Count Win32Apps = @($win32Apps | Select-Object -First 50 | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName Publisher = $_.publisher FileName = $_.fileName } }) } } # ── INTUNE-020: Device Categories ─────────────────────────────────────── function Test-InfiltrationINTUNE020 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } $categories = $intune.DeviceCategories if (-not $categories -or $categories.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue 'No device categories configured' ` -Details @{ CategoryCount = 0 } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($categories.Count) device categor(ies) configured" ` -Details @{ CategoryCount = $categories.Count Categories = @($categories | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName Description = $_.description } }) } } # ── INTUNE-021: Remote Actions Audit ──────────────────────────────────── function Test-InfiltrationINTUNE021 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Remote actions audit requires Intune audit log data which is not collected in the current data set' } # ── INTUNE-022: OneDrive Known Folder Move / Sync ────────────────────── function Test-InfiltrationINTUNE022 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'OneDrive sync configuration check requires OneDrive admin settings or device-level registry data not available via Graph' } # ── INTUNE-023: Multi-Admin Approval for Destructive Actions ───────── function Test-InfiltrationINTUNE023 { [CmdletBinding()] param([hashtable]$AuditData, [hashtable]$CheckDefinition) $intune = $AuditData.Intune if (-not $intune) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue 'Intune data not available' } # Check if the API returned data (requires beta endpoint) if ($intune.Errors -and $intune.Errors['OperationApprovalPolicies']) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'SKIP' ` -CurrentValue "Unable to query approval policies: $($intune.Errors['OperationApprovalPolicies'])" } $policies = $intune.OperationApprovalPolicies if (-not $policies -or $policies.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue 'No multi-admin approval policies configured — destructive actions (bulk wipe, retire) can be executed by a single admin' ` -Details @{ PolicyCount = 0 Risk = 'A compromised admin account can trigger mass device wipes without secondary approval' RecommendedActions = @('Wipe', 'Retire', 'Delete', 'ScriptDeployment') } } # Analyze what operation types are covered $coveredTypes = @($policies | ForEach-Object { if ($_.operationApprovalPolicyType) { $_.operationApprovalPolicyType } } | Select-Object -Unique) $policyDetails = @($policies | ForEach-Object { @{ Id = $_.id DisplayName = $_.displayName PolicyType = $_.operationApprovalPolicyType ApproverGroupIds = $_.approverGroupIds } }) # Critical destructive types we want to see covered $destructiveTypes = @('deviceWipe', 'deviceRetire', 'deviceDelete') $coveredDestructive = @($coveredTypes | Where-Object { $_ -in $destructiveTypes }) $missingDestructive = @($destructiveTypes | Where-Object { $_ -notin $coveredTypes }) if ($coveredDestructive.Count -eq 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'FAIL' ` -CurrentValue "$($policies.Count) approval policies found but none cover destructive device actions (wipe/retire/delete)" ` -Details @{ PolicyCount = $policies.Count CoveredTypes = $coveredTypes MissingCritical = $missingDestructive Policies = $policyDetails } } if ($missingDestructive.Count -gt 0) { return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'WARN' ` -CurrentValue "$($policies.Count) approval policies configured, but missing coverage for: $($missingDestructive -join ', ')" ` -Details @{ PolicyCount = $policies.Count CoveredTypes = $coveredTypes MissingCritical = $missingDestructive Policies = $policyDetails } } return New-AuditFinding -CheckDefinition $CheckDefinition -Status 'PASS' ` -CurrentValue "$($policies.Count) multi-admin approval policies active, covering $($coveredTypes -join ', ')" ` -Details @{ PolicyCount = $policies.Count CoveredTypes = $coveredTypes Policies = $policyDetails } } |