>, settings_table?: array, warnings: array} */ public function normalize(?array $snapshot, string $policyType, ?string $platform = null): array { $snapshot = $snapshot ?? []; $normalized = $this->defaultNormalizer->normalize($snapshot, $policyType, $platform); if ($snapshot === []) { return $normalized; } $normalized['settings'] = array_values(array_filter( $normalized['settings'], fn (array $block) => strtolower((string) ($block['title'] ?? '')) !== 'general' )); foreach ($this->buildComplianceBlocks($snapshot) as $block) { $normalized['settings'][] = $block; } return $normalized; } /** * @return array */ public function flattenForDiff(?array $snapshot, string $policyType, ?string $platform = null): array { return $this->defaultNormalizer->flattenForDiff($snapshot, $policyType, $platform); } /** * @return array> */ private function buildComplianceBlocks(array $snapshot): array { $blocks = []; $groups = $this->groupedFields(); $usedKeys = []; foreach ($groups as $title => $group) { $rows = $this->buildRows($snapshot, $group['keys'], $group['labels'] ?? []); if ($rows === []) { continue; } $blocks[] = [ 'type' => 'table', 'title' => $title, 'rows' => $rows, ]; $usedKeys = array_merge($usedKeys, $group['keys']); } $additionalRows = $this->buildAdditionalRows($snapshot, $usedKeys); if ($additionalRows !== []) { $blocks[] = [ 'type' => 'table', 'title' => 'Additional Settings', 'rows' => $additionalRows, ]; } return $blocks; } /** * @return array{keys: array, labels?: array} */ private function groupedFields(): array { return [ 'Password & Access' => [ 'keys' => [ 'passwordRequired', 'passwordRequiredType', 'passwordBlockSimple', 'passwordMinimumLength', 'passwordMinimumCharacterSetCount', 'passwordExpirationDays', 'passwordMinutesOfInactivityBeforeLock', 'passwordPreviousPasswordBlockCount', 'passwordRequiredToUnlockFromIdle', ], 'labels' => [ 'passwordRequired' => 'Password required', 'passwordRequiredType' => 'Password required type', 'passwordBlockSimple' => 'Block simple passwords', 'passwordMinimumLength' => 'Password minimum length', 'passwordMinimumCharacterSetCount' => 'Password minimum character set count', 'passwordExpirationDays' => 'Password expiration days', 'passwordMinutesOfInactivityBeforeLock' => 'Password idle lock (minutes)', 'passwordPreviousPasswordBlockCount' => 'Password history count', 'passwordRequiredToUnlockFromIdle' => 'Password required to unlock from idle', ], ], 'Defender & Threat Protection' => [ 'keys' => [ 'defenderEnabled', 'defenderVersion', 'antivirusRequired', 'antiSpywareRequired', 'rtpEnabled', 'signatureOutOfDate', 'deviceThreatProtectionEnabled', 'deviceThreatProtectionRequiredSecurityLevel', 'requireHealthyDeviceReport', ], 'labels' => [ 'defenderEnabled' => 'Microsoft Defender enabled', 'defenderVersion' => 'Defender version', 'antivirusRequired' => 'Antivirus required', 'antiSpywareRequired' => 'Anti-spyware required', 'rtpEnabled' => 'Real-time protection enabled', 'signatureOutOfDate' => 'Signature out of date (days)', 'deviceThreatProtectionEnabled' => 'Device threat protection enabled', 'deviceThreatProtectionRequiredSecurityLevel' => 'Threat protection required level', 'requireHealthyDeviceReport' => 'Require healthy device report', ], ], 'Encryption & Integrity' => [ 'keys' => [ 'bitLockerEnabled', 'storageRequireEncryption', 'tpmRequired', 'secureBootEnabled', 'codeIntegrityEnabled', 'memoryIntegrityEnabled', 'kernelDmaProtectionEnabled', 'firmwareProtectionEnabled', 'virtualizationBasedSecurityEnabled', 'earlyLaunchAntiMalwareDriverEnabled', ], 'labels' => [ 'bitLockerEnabled' => 'BitLocker required', 'storageRequireEncryption' => 'Storage encryption required', 'tpmRequired' => 'TPM required', 'secureBootEnabled' => 'Secure boot required', 'codeIntegrityEnabled' => 'Code integrity required', 'memoryIntegrityEnabled' => 'Memory integrity required', 'kernelDmaProtectionEnabled' => 'Kernel DMA protection required', 'firmwareProtectionEnabled' => 'Firmware protection required', 'virtualizationBasedSecurityEnabled' => 'Virtualization-based security required', 'earlyLaunchAntiMalwareDriverEnabled' => 'Early launch anti-malware required', ], ], 'Operating System' => [ 'keys' => [ 'osMinimumVersion', 'osMaximumVersion', 'mobileOsMinimumVersion', 'mobileOsMaximumVersion', 'validOperatingSystemBuildRanges', 'wslDistributions', ], 'labels' => [ 'osMinimumVersion' => 'OS minimum version', 'osMaximumVersion' => 'OS maximum version', 'mobileOsMinimumVersion' => 'Mobile OS minimum version', 'mobileOsMaximumVersion' => 'Mobile OS maximum version', 'validOperatingSystemBuildRanges' => 'Valid OS build ranges', 'wslDistributions' => 'Allowed WSL distributions', ], ], 'Firewall' => [ 'keys' => [ 'activeFirewallRequired', ], 'labels' => [ 'activeFirewallRequired' => 'Active firewall required', ], ], 'Compliance Signals' => [ 'keys' => [ 'configurationManagerComplianceRequired', 'deviceCompliancePolicyScript', ], 'labels' => [ 'configurationManagerComplianceRequired' => 'ConfigMgr compliance required', 'deviceCompliancePolicyScript' => 'Compliance policy script', ], ], ]; } /** * @param array $labels * @return array> */ private function buildRows(array $snapshot, array $keys, array $labels = []): array { $rows = []; foreach ($keys as $key) { if (! array_key_exists($key, $snapshot)) { continue; } $rows[] = [ 'label' => $labels[$key] ?? Str::headline($key), 'value' => $this->formatValue($snapshot[$key]), ]; } return $rows; } /** * @param array $usedKeys * @return array> */ private function buildAdditionalRows(array $snapshot, array $usedKeys): array { $ignoredKeys = array_merge($this->ignoredKeys(), $usedKeys); $rows = []; foreach ($snapshot as $key => $value) { if (! is_string($key)) { continue; } if (in_array($key, $ignoredKeys, true)) { continue; } $rows[] = [ 'label' => Str::headline($key), 'value' => $this->formatValue($value), ]; } return $rows; } /** * @return array */ private function ignoredKeys(): array { return [ '@odata.context', '@odata.type', 'id', 'version', 'createdDateTime', 'lastModifiedDateTime', 'supportsScopeTags', 'roleScopeTagIds', 'assignments', 'createdBy', 'lastModifiedBy', 'omaSettings', 'settings', 'settingsDelta', 'displayName', 'description', 'name', 'platform', 'platforms', 'technologies', 'settingCount', 'settingsCount', 'templateReference', ]; } private function formatValue(mixed $value): mixed { if (is_array($value)) { return json_encode($value, JSON_PRETTY_PRINT); } return $value; } }