diff --git a/code-tests/commands/Add-ZtDeviceOverview.Tests.ps1 b/code-tests/commands/Add-ZtDeviceOverview.Tests.ps1 index 4752f7decc..fc1dfee0d5 100644 --- a/code-tests/commands/Add-ZtDeviceOverview.Tests.ps1 +++ b/code-tests/commands/Add-ZtDeviceOverview.Tests.ps1 @@ -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) } } @@ -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 { @( diff --git a/src/powershell/assets/ReportTemplate.classic.html b/src/powershell/assets/ReportTemplate.classic.html index 23d0c98471..da312d90a4 100644 --- a/src/powershell/assets/ReportTemplate.classic.html +++ b/src/powershell/assets/ReportTemplate.classic.html @@ -1,7 +1,7 @@
- + @@ -69,44 +69,44 @@ A${r},${r},0,1,1,${2*i},${n} H${Ib}M${2*i},${n} A${r},${r},0,1,1,${i},${n}`,className:`recharts-legend-icon`});if(e.type===`rect`)return _.createElement(`path`,{stroke:`none`,fill:a,d:`M0,${Ib/8}h${Ib}v${Ib*3/4}h${-Ib}z`,className:`recharts-legend-icon`});if(_.isValidElement(e.legendIcon)){var o=Sb({},e);return delete o.legendIcon,_.cloneElement(e.legendIcon,o)}return _.createElement(vb,{fill:a,cx:n,cy:n,size:Ib,sizeType:`diameter`,type:e.type})}},{key:`renderItems`,value:function(){var e=this,t=this.props,n=t.payload,r=t.iconSize,i=t.layout,a=t.formatter,o=t.inactiveColor,s={x:0,y:0,width:Ib,height:Ib},c={display:i===`horizontal`?`inline-block`:`block`,marginRight:10},l={display:`inline-block`,verticalAlign:`middle`,marginRight:4};return n.map(function(t,n){var i=t.formatter||a,u=dr(Nb(Nb({"recharts-legend-item":!0},`legend-item-${n}`,!0),`inactive`,t.inactive));if(t.type===`none`)return null;var d=(0,L_.default)(t.value)?null:t.value;(0,L_.default)(t.value);var f=t.inactive?o:t.color;return _.createElement(`li`,bb({className:u,style:c,key:`legend-item-${n}`},I_(e.props,t,n)),_.createElement(rv,{width:r,height:r,viewBox:s,style:l},e.renderIcon(t)),_.createElement(`span`,{className:`recharts-legend-item-text`,style:{color:f}},i?i(d,t,n):d))})}},{key:`render`,value:function(){var e=this.props,t=e.payload,n=e.layout,r=e.align;if(!t||!t.length)return null;var i={padding:0,margin:0,textAlign:n===`horizontal`?r:`left`};return _.createElement(`ul`,{className:`recharts-default-legend`,style:i},this.renderItems())}}])}(_.PureComponent);Nb(Lb,`displayName`,`Legend`),Nb(Lb,`defaultProps`,{iconSize:14,layout:`horizontal`,align:`center`,verticalAlign:`middle`,inactiveColor:`#ccc`});var Rb=o(((e,t)=>{var n=e_();function r(){this.__data__=new n,this.size=0}t.exports=r})),zb=o(((e,t)=>{function n(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}t.exports=n})),Bb=o(((e,t)=>{function n(e){return this.__data__.get(e)}t.exports=n})),Vb=o(((e,t)=>{function n(e){return this.__data__.has(e)}t.exports=n})),Hb=o(((e,t)=>{var n=e_(),r=t_(),i=l_(),a=200;function o(e,t){var o=this.__data__;if(o instanceof n){var s=o.__data__;if(!r||s.length{if(e??=10,n??=r===10?`s`:`,`,typeof n!=`function`&&(!(r%1)&&(n=VE(n)).precision==null&&(n.trim=!0),n=$E(n)),e===1/0)return n;let o=Math.max(1,r*e/t.ticks().length);return e=>{let t=e/a(Math.round(i(e)));return t*r {if(e??=10,n??=r===10?`s`:`,`,typeof n!=`function`&&(!(r%1)&&(n=BE(n)).precision==null&&(n.trim=!0),n=QE(n)),e===1/0)return n;let o=Math.max(1,r*e/t.ticks().length);return e=>{let t=e/a(Math.round(i(e)));return t*r =2?S_(i[1].coordinate-i[0].coordinate):1,_=eV(a,g,p);return c===`equidistantPreserveStart`?rV(g,_,h,i,o):(f=c===`preserveStart`||c===`preserveStartEnd`?dV(g,_,h,i,o,c===`preserveStartEnd`):uV(g,_,h,i,o),f.filter(function(e){return e.isShow}))}var pV=[`viewBox`],mV=[`viewBox`],hV=[`ticks`];function gV(e){"@babel/helpers - typeof";return gV=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},gV(e)}function _V(){return _V=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);nc)return!1;if(a===0)return!0;var l=NN(t),u=l.startAngle,d=l.endAngle,f=o,p;if(u<=d){for(;f>d;)f-=360;for(;f=u&&f<=d}else{for(;f>u;)f-=360;for(;f0;)--s}i(e[n],a)===0?Bw(e,n,s):(++s,Bw(e,s,r)),s<=t&&(n=s+1),t<=s&&(r=s-1)}return e}function Bw(e,t,n){let r=e[t];e[t]=e[n],e[n]=r}function Vw(e,t,n){if(e=Float64Array.from(yne(e,n)),r=e.length){if((t=+t)<=0||r<2)return Rw(e);if(t>=1)return Lw(e);var r,i=(r-1)*t,a=Math.floor(i),o=Lw(zw(e,a).subarray(0,a+1));return o+(Rw(e.subarray(a+1))-o)*(i-a)}}function Hw(e,t,n=Ew){if(r=e.length){if((t=+t)<=0||r<2)return+n(e[0],0,e);if(t>=1)return+n(e[r-1],r-1,e);var r,i=(r-1)*t,a=Math.floor(i),o=+n(e[a],a,e);return o+(+n(e[a+1],a+1,e)-o)*(i-a)}}function Uw(e,t,n){e=+e,t=+t,n=(i=arguments.length)<2?(t=e,e=0,1):i<3?1:+n;for(var r=-1,i=Math.max(0,Math.ceil((t-e)/n))|0,a=Array(i);++r>8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):n===8?bT(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):n===4?bT(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=sT.exec(e))?new CT(t[1],t[2],t[3],1):(t=cT.exec(e))?new CT(t[1]*255/100,t[2]*255/100,t[3]*255/100,1):(t=lT.exec(e))?bT(t[1],t[2],t[3],t[4]):(t=uT.exec(e))?bT(t[1]*255/100,t[2]*255/100,t[3]*255/100,t[4]):(t=dT.exec(e))?AT(t[1],t[2]/100,t[3]/100,1):(t=fT.exec(e))?AT(t[1],t[2]/100,t[3]/100,t[4]):pT.hasOwnProperty(e)?yT(pT[e]):e===`transparent`?new CT(NaN,NaN,NaN,0):null}function yT(e){return new CT(e>>16&255,e>>8&255,e&255,1)}function bT(e,t,n,r){return r<=0&&(e=t=n=NaN),new CT(e,t,n,r)}function xT(e){return e instanceof eT||(e=vT(e)),e?(e=e.rgb(),new CT(e.r,e.g,e.b,e.opacity)):new CT}function ST(e,t,n,r){return arguments.length===1?xT(e):new CT(e,t,n,r??1)}function CT(e,t,n,r){this.r=+e,this.g=+t,this.b=+n,this.opacity=+r}Qw(CT,ST,$w(eT,{brighter(e){return e=e==null?nT:nT**+e,new CT(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?tT:tT**+e,new CT(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new CT(OT(this.r),OT(this.g),OT(this.b),DT(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:wT,formatHex:wT,formatHex8:TT,formatRgb:ET,toString:ET}));function wT(){return`#${kT(this.r)}${kT(this.g)}${kT(this.b)}`}function TT(){return`#${kT(this.r)}${kT(this.g)}${kT(this.b)}${kT((isNaN(this.opacity)?1:this.opacity)*255)}`}function ET(){let e=DT(this.opacity);return`${e===1?`rgb(`:`rgba(`}${OT(this.r)}, ${OT(this.g)}, ${OT(this.b)}${e===1?`)`:`, ${e})`}`}function DT(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function OT(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function kT(e){return e=OT(e),(e<16?`0`:``)+e.toString(16)}function AT(e,t,n,r){return r<=0?e=t=n=NaN:n<=0||n>=1?e=t=NaN:t<=0&&(e=NaN),new NT(e,t,n,r)}function jT(e){if(e instanceof NT)return new NT(e.h,e.s,e.l,e.opacity);if(e instanceof eT||(e=vT(e)),!e)return new NT;if(e instanceof NT)return e;e=e.rgb();var t=e.r/255,n=e.g/255,r=e.b/255,i=Math.min(t,n,r),a=Math.max(t,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=t===a?(n-r)/s+(ne.length)&&(t=e.length);for(var n=0,r=Array(t);nc)return!1;if(a===0)return!0;var l=MN(t),u=l.startAngle,d=l.endAngle,f=o,p;if(u<=d){for(;f>d;)f-=360;for(;f=u&&f<=d}else{for(;f>u;)f-=360;for(;f