Skip to content
This repository was archived by the owner on Sep 1, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion code-tests/commands/Add-ZtDeviceOverview.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Describe "Add-ZtDeviceOverview" {

if (-not (Get-Command Invoke-ZtGraphRequest -ErrorAction SilentlyContinue)) {
function global:Invoke-ZtGraphRequest {
param($RelativeUri, $ApiVersion)
param($RelativeUri, $ApiVersion, $Method, $Body, $ErrorAction)
}
}

Expand Down Expand Up @@ -99,6 +99,106 @@ Describe "Add-ZtDeviceOverview" {
$script:tenantInfo.Value.DeviceSummary.totalDevices | Should -Be 16
}

It "Should add MDE sensor coverage from one Advanced Hunting aggregate" {
Mock Invoke-DatabaseQuery -ParameterFilter { $Sql -match 'group by operatingSystem' -and $Sql -notmatch 'group by operatingSystem, trustType' -and $Sql -notmatch 'group by operatingSystem, isCompliant' } -MockWith {
@(
[pscustomobject]@{ operatingSystem = 'Windows'; count = 10 }
[pscustomobject]@{ operatingSystem = 'macOS'; count = 5 }
[pscustomobject]@{ operatingSystem = 'iOS'; count = 4 }
[pscustomobject]@{ operatingSystem = 'Android'; count = 3 }
[pscustomobject]@{ operatingSystem = 'Linux'; count = 2 }
)
}
Mock Invoke-ZtGraphRequest -ParameterFilter { $RelativeUri -eq 'security/runHuntingQuery' } -MockWith {
[pscustomobject]@{
results = @(
[pscustomobject]@{ Platform = 'Windows'; MdeSensorInstalledCount = 8 }
[pscustomobject]@{ Platform = 'macOS'; MdeSensorInstalledCount = 4 }
[pscustomobject]@{ Platform = 'iOS/iPadOS'; MdeSensorInstalledCount = 2 }
[pscustomobject]@{ Platform = 'Android'; MdeSensorInstalledCount = 1 }
[pscustomobject]@{ Platform = 'Linux'; MdeSensorInstalledCount = 0 }
)
}
}

Add-ZtDeviceOverview -Database 'test'

$coverage = $script:tenantInfo.Value.DeviceSummary.mdeSensorInstalledOperatingSystemSummary
$coverage.windowsCount | Should -Be 8
$coverage.macOSCount | Should -Be 4
$coverage.iosCount | Should -Be 2
$coverage.androidCount | Should -Be 1
$coverage.linuxCount | Should -Be 0
Should -Invoke Invoke-ZtGraphRequest -Exactly 1 -ParameterFilter {
$RelativeUri -eq 'security/runHuntingQuery' -and
$ApiVersion -eq 'v1.0' -and
$Method -eq 'POST' -and
($Body | ConvertFrom-Json).Timespan -eq 'P30D'
}
}

It "Should omit MDE coverage when Advanced Hunting returns no result set" {
Mock Invoke-ZtGraphRequest -ParameterFilter { $RelativeUri -eq 'security/runHuntingQuery' } -MockWith {
[pscustomobject]@{ results = $null }
}

Add-ZtDeviceOverview -Database 'test'

$script:tenantInfo.Value.DeviceSummary.PSObject.Properties.Name | Should -Not -Contain 'mdeSensorInstalledOperatingSystemSummary'
Should -Invoke Write-PSFMessage -Exactly 1 -ParameterFilter {
$Level -eq 'Warning' -and $Message -eq 'Advanced hunting returned no result set for MDE sensor coverage.'
}
}

It "Should omit MDE coverage when Advanced Hunting returns an empty result set" {
Mock Invoke-ZtGraphRequest -ParameterFilter { $RelativeUri -eq 'security/runHuntingQuery' } -MockWith {
[pscustomobject]@{ results = @() }
}

Add-ZtDeviceOverview -Database 'test'

$script:tenantInfo.Value.DeviceSummary.PSObject.Properties.Name | Should -Not -Contain 'mdeSensorInstalledOperatingSystemSummary'
Should -Invoke Write-PSFMessage -Exactly 1 -ParameterFilter {
$Level -eq 'Warning' -and $Message -eq 'Advanced hunting returned no result set for MDE sensor coverage.'
}
}

It "Should continue without MDE coverage when Advanced Hunting fails" {
Mock Invoke-ZtGraphRequest -ParameterFilter { $RelativeUri -eq 'security/runHuntingQuery' } -MockWith {
throw 'Advanced Hunting request failed'
}

{ Add-ZtDeviceOverview -Database 'test' } | Should -Not -Throw

$script:tenantInfo.Value.DeviceSummary.PSObject.Properties.Name | Should -Not -Contain 'mdeSensorInstalledOperatingSystemSummary'
Should -Invoke Write-PSFMessage -Exactly 1 -ParameterFilter {
$Level -eq 'Warning' -and $Message -like 'Failed to retrieve MDE sensor coverage from advanced hunting:*'
}
}

It "Should warn only when MDE coverage exceeds the platform total" {
Mock Invoke-ZtGraphRequest -ParameterFilter { $RelativeUri -eq 'security/runHuntingQuery' } -MockWith {
[pscustomobject]@{
results = @(
[pscustomobject]@{ Platform = 'Windows'; MdeSensorInstalledCount = 1 }
[pscustomobject]@{ Platform = 'macOS'; MdeSensorInstalledCount = 0 }
[pscustomobject]@{ Platform = 'iOS/iPadOS'; MdeSensorInstalledCount = 0 }
[pscustomobject]@{ Platform = 'Android'; MdeSensorInstalledCount = 0 }
[pscustomobject]@{ Platform = 'Linux'; MdeSensorInstalledCount = 0 }
)
}
}

Add-ZtDeviceOverview -Database 'test'

Should -Invoke Write-PSFMessage -Exactly 1 -ParameterFilter {
$Level -eq 'Warning' -and $Message -like 'MDE sensor coverage is inconsistent for Windows:*'
}
Should -Invoke Write-PSFMessage -Exactly 0 -ParameterFilter {
$Level -eq 'Warning' -and $Message -like 'MDE sensor coverage is inconsistent for macOS:*'
}
}

It "Should sum Windows desktop devices across trust types and compliance, deriving unmanaged via subtraction" {
Mock Invoke-DatabaseQuery -ParameterFilter { $Sql -match 'group by operatingSystem, trustType' } -MockWith {
@(
Expand Down
118 changes: 59 additions & 59 deletions src/powershell/assets/ReportTemplate.classic.html

Large diffs are not rendered by default.

122 changes: 61 additions & 61 deletions src/powershell/assets/ReportTemplate.html

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,46 @@ order by operatingSystem
if ($null -eq $linuxCount) { $linuxCount = 0 }
if ($null -eq $discoveredDeviceTotal) { $discoveredDeviceTotal = 0 }

$mdeSensorInstalledOperatingSystemSummary = $null
$mdeCoverageQuery = "let Platforms = datatable(Platform:string, PlatformOrder:int) ['Windows', 1, 'macOS', 2, 'iOS/iPadOS', 3, 'Android', 4, 'Linux', 5]; let Coverage = DeviceInfo | where Timestamp > ago(30d) | summarize arg_max(Timestamp, *) by DeviceId | where isempty(MergedToDeviceId) | where OnboardingStatus =~ 'Onboarded' and SensorHealthState =~ 'Active' and isnotempty(AadDeviceId) | extend NormalizedOS=tolower(OSPlatform) | extend Platform=case(NormalizedOS startswith 'windows', 'Windows', NormalizedOS startswith 'mac', 'macOS', NormalizedOS startswith 'ios' or NormalizedOS startswith 'ipados', 'iOS/iPadOS', NormalizedOS startswith 'android', 'Android', NormalizedOS startswith 'linux', 'Linux', 'Other') | where Platform != 'Other' | summarize arg_max(Timestamp, Platform) by AadDeviceId=tolower(AadDeviceId) | summarize MdeSensorInstalledCount=count() by Platform; Platforms | join kind=leftouter Coverage on Platform | project Platform, MdeSensorInstalledCount=coalesce(MdeSensorInstalledCount, 0), PlatformOrder | order by PlatformOrder asc | project-away PlatformOrder"
Comment thread
praneeth-0000 marked this conversation as resolved.
try {
$requestBody = @{ Query = $mdeCoverageQuery; Timespan = 'P30D' } | ConvertTo-Json -Compress
$mdeCoverageResponse = Invoke-ZtGraphRequest -RelativeUri 'security/runHuntingQuery' -ApiVersion 'v1.0' -Method POST -Body $requestBody -ErrorAction Stop
$mdeCoverageRows = @()
if ($null -ne $mdeCoverageResponse -and $null -ne $mdeCoverageResponse.results) {
$mdeCoverageRows = @($mdeCoverageResponse.results)
}
if ($mdeCoverageRows.Count -gt 0) {
$mdeSensorInstalledOperatingSystemSummary = [PSCustomObject]@{
windowsCount = [long](($mdeCoverageRows | Where-Object Platform -eq 'Windows' | Select-Object -First 1).MdeSensorInstalledCount)
macOSCount = [long](($mdeCoverageRows | Where-Object Platform -eq 'macOS' | Select-Object -First 1).MdeSensorInstalledCount)
iosCount = [long](($mdeCoverageRows | Where-Object Platform -eq 'iOS/iPadOS' | Select-Object -First 1).MdeSensorInstalledCount)
androidCount = [long](($mdeCoverageRows | Where-Object Platform -eq 'Android' | Select-Object -First 1).MdeSensorInstalledCount)
linuxCount = [long](($mdeCoverageRows | Where-Object Platform -eq 'Linux' | Select-Object -First 1).MdeSensorInstalledCount)
}

foreach ($platformCoverage in @(
@{ Platform = 'Windows'; Installed = $mdeSensorInstalledOperatingSystemSummary.windowsCount; Total = $windowsCount },
@{ Platform = 'macOS'; Installed = $mdeSensorInstalledOperatingSystemSummary.macOSCount; Total = $macOSCount },
@{ Platform = 'iOS/iPadOS'; Installed = $mdeSensorInstalledOperatingSystemSummary.iosCount; Total = $iosCount },
@{ Platform = 'Android'; Installed = $mdeSensorInstalledOperatingSystemSummary.androidCount; Total = $androidCount },
@{ Platform = 'Linux'; Installed = $mdeSensorInstalledOperatingSystemSummary.linuxCount; Total = $linuxCount }
)) {
if ([long] $platformCoverage.Installed -gt [long] $platformCoverage.Total) {
Write-PSFMessage "MDE sensor coverage is inconsistent for $($platformCoverage.Platform): installed count $($platformCoverage.Installed) exceeds total device count $($platformCoverage.Total)." -Level Warning -Tag TenantInfo
}
}
}
else {
Write-PSFMessage 'Advanced hunting returned no result set for MDE sensor coverage.' -Level Warning -Tag TenantInfo
}
}
catch {
Write-PSFMessage "Failed to retrieve MDE sensor coverage from advanced hunting: $_" -Level Warning -Tag TenantInfo
Comment thread
praneeth-0000 marked this conversation as resolved.
}

$deviceSummary = [PSCustomObject]@{
description = 'All devices by operating system.'
description = 'Total devices and Microsoft Defender for Endpoint sensor coverage by OS.'
deviceOperatingSystemSummary = [PSCustomObject]@{
windowsCount = $windowsCount
macOSCount = $macOSCount
Expand All @@ -42,6 +80,9 @@ order by operatingSystem
}
totalDevices = $discoveredDeviceTotal
}
if ($null -ne $mdeSensorInstalledOperatingSystemSummary) {
$deviceSummary | Add-Member -MemberType NoteProperty -Name mdeSensorInstalledOperatingSystemSummary -Value $mdeSensorInstalledOperatingSystemSummary
}

$desktopRows = Invoke-DatabaseQuery -Database $Database -Sql @"
select operatingSystem, trustType, isCompliant, count(*) count
Expand Down
19 changes: 18 additions & 1 deletion src/report/demo-report-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,24 @@
}
],
"DeviceOverview": {
"DeviceSummary": null,
"DeviceSummary": {
"description": "Total devices and Microsoft Defender for Endpoint sensor coverage by OS.",
"totalDevices": 780,
"deviceOperatingSystemSummary": {
"windowsCount": 525,
"macOSCount": 75,
"iosCount": 75,
"androidCount": 105,
"linuxCount": 0
},
"mdeSensorInstalledOperatingSystemSummary": {
"windowsCount": 500,
"macOSCount": 60,
"iosCount": 2,
"androidCount": 10,
"linuxCount": 0
}
},
"DesktopDevicesSummary": {
"nodes": [
{
Expand Down
52 changes: 10 additions & 42 deletions src/report/src-curent/components/DevicesInsightsAccordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/
import { Separator } from "@/components/ui/separator";
import { reportData } from "@/config/report-data";
import { formatNumber } from "@/lib/format-utils";
import { buildDeviceCoverageRows } from "@/lib/device-coverage";

type SankeyLink = { source: string; target: string; value: number | null };

Expand All @@ -48,6 +49,10 @@ export default function DevicesInsightsAccordion() {
const desktopNodes: SankeyLink[] = deviceOverview?.DesktopDevicesSummary?.nodes || [];
const mobileNodes: SankeyLink[] = deviceOverview?.MobileSummary?.nodes || [];
const osSummary: any = deviceOverview?.DeviceSummary?.deviceOperatingSystemSummary || deviceOverview?.ManagedDevices?.deviceOperatingSystemSummary;
const deviceCoverageRows = buildDeviceCoverageRows(
deviceOverview?.DeviceSummary?.deviceOperatingSystemSummary,
deviceOverview?.DeviceSummary?.mdeSensorInstalledOperatingSystemSummary,
);

const windowsDeviceCount = Number(osSummary?.windowsCount) || getFlow(desktopNodes, (s) => s === "Desktop devices", (t) => t === "Windows");
const macOSDeviceCount = Number(osSummary?.macOSCount) || getFlow(desktopNodes, (s) => s === "Desktop devices", (t) => t === "macOS");
Expand Down Expand Up @@ -138,6 +143,8 @@ export default function DevicesInsightsAccordion() {
<CardTitle className="text-2xl tabular-nums">Device summary</CardTitle>
</CardHeader>
<CardContent className="flex flex-1 min-h-0 flex-col pb-2 pt-0">
{deviceCoverageRows ? (
<>
<ChartContainer
config={{
covered: { label: "MDE sensor installed", color: "hsl(240, 40%, 45%)" },
Expand All @@ -147,48 +154,7 @@ export default function DevicesInsightsAccordion() {
>
<BarChart
margin={{ left: 64, right: 64, top: 0, bottom: 0 }}
data={(() => {
const finalizeCoverage = (total: number, coveredRaw: number, notCoveredRaw: number) => {
const safeTotal = Math.max(0, total || 0);
const safeCovered = Math.max(0, coveredRaw || 0);
const safeNotCovered = Math.max(0, notCoveredRaw || 0);
const adjustedNotCovered = safeCovered + safeNotCovered < safeTotal
? safeNotCovered + (safeTotal - (safeCovered + safeNotCovered))
: safeNotCovered;
const percent = safeTotal > 0 ? Math.round((safeCovered / safeTotal) * 100) : 0;
return { covered: safeCovered, notCovered: Math.max(0, safeTotal - safeCovered), percent, adjustedNotCovered };
};

const windows = finalizeCoverage(
windowsDeviceCount,
getFlow(desktopNodes, (s) => ["Entra joined", "Entra hybrid joined", "Entra registered"].includes(s), (t) => t === "Compliant"),
getFlow(desktopNodes, (s) => ["Entra joined", "Entra hybrid joined", "Entra registered"].includes(s), (t) => t === "Non-compliant" || t === "Unmanaged"),
);
const mac = finalizeCoverage(
macOSDeviceCount,
getFlow(desktopNodes, (s) => s === "macOS", (t) => t === "Compliant"),
getFlow(desktopNodes, (s) => s === "macOS", (t) => t === "Non-compliant" || t === "Unmanaged"),
);
const ios = finalizeCoverage(
iosDeviceCount,
getFlow(mobileNodes, (s) => s.includes("iOS"), (t) => t === "Compliant"),
getFlow(mobileNodes, (s) => s.includes("iOS"), (t) => t === "Non-compliant"),
);
const android = finalizeCoverage(
androidDeviceCount,
getFlow(mobileNodes, (s) => s.includes("Android"), (t) => t === "Compliant"),
getFlow(mobileNodes, (s) => s.includes("Android"), (t) => t === "Non-compliant"),
);
const linux = finalizeCoverage(linuxDeviceCount, 0, linuxDeviceCount);

return [
{ os: "Windows", total: windowsDeviceCount, covered: windows.covered, notCovered: windows.notCovered, label: `${formatNumber(windows.covered)}/${formatNumber(windowsDeviceCount)} (${windows.percent}%)` },
{ os: "macOS", total: macOSDeviceCount, covered: mac.covered, notCovered: mac.notCovered, label: `${formatNumber(mac.covered)}/${formatNumber(macOSDeviceCount)} (${mac.percent}%)` },
{ os: "iOS", total: iosDeviceCount, covered: ios.covered, notCovered: ios.notCovered, label: `${formatNumber(ios.covered)}/${formatNumber(iosDeviceCount)} (${ios.percent}%)` },
{ os: "Android", total: androidDeviceCount, covered: android.covered, notCovered: android.notCovered, label: `${formatNumber(android.covered)}/${formatNumber(androidDeviceCount)} (${android.percent}%)` },
{ os: "Linux", total: linuxDeviceCount, covered: linux.covered, notCovered: linux.notCovered, label: `${formatNumber(linux.covered)}/${formatNumber(linuxDeviceCount)} (${linux.percent}%)` },
];
})()}
data={deviceCoverageRows}
layout="vertical"
barSize={32}
barGap={36}
Expand All @@ -212,6 +178,8 @@ export default function DevicesInsightsAccordion() {
Not covered
</div>
</div>
</>
) : renderNoData("No MDE coverage data available.")}
</CardContent>
<CardFooter className="flex flex-row items-center border-t p-4">
<div className="flex w-full items-center gap-2">
Expand Down
1 change: 1 addition & 0 deletions src/report/src-curent/config/report-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export interface DeviceSummary {
description: string;
totalDevices: number | null;
deviceOperatingSystemSummary: DeviceOperatingSystemSummary;
mdeSensorInstalledOperatingSystemSummary?: DeviceOperatingSystemSummary | null;
}

export interface DeviceOwnership {
Expand Down
Loading