tests/Get-MsecIntuneDevice.Tests.ps1
|
#Requires -Module Pester # # Tests for Get-MsecIntuneDevice. The function GETs /deviceManagement/managedDevices # with a $select filter and projects each device row to a flat PSCustomObject. # Tests cover: # - Field projection (Graph camelCase -> output PascalCase). # - The 9999-sentinel for ComplianceGraceUntil becomes $null. # - DateTime strings are converted to [datetime] (so callers can compare with # (Get-Date).AddDays(...) without manual parsing). # - 403 is rewritten to mention DeviceManagementManagedDevices.Read.All. BeforeAll { $modulePath = Join-Path $PSScriptRoot '..' 'msec.psm1' Import-Module $modulePath -Force -ErrorAction Stop $script:TestThumbBytes = [byte[]](1..20) } AfterAll { Remove-Module Msec -Force -ErrorAction SilentlyContinue } Describe 'Get-MsecIntuneDevice' { BeforeEach { InModuleScope Msec -Parameters @{ Thumb = $script:TestThumbBytes } { param($Thumb) $script:MsecSession = @{ TenantId = 'tenant' ClientId = 'client' KeyVaultName = 'kv-test' KeyName = 'msec-app' ThumbprintBytes = $Thumb Tokens = @{} } } } It 'projects every Graph row to the documented flat shape, with DateTime conversion and grace-sentinel nullification' { $rows = InModuleScope Msec { Mock Invoke-MsecKeyVaultSign -MockWith { [byte[]](1..10) } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'oauth2/v2.0/token' } -MockWith { [pscustomobject]@{ access_token = 'mock'; expires_in = 3600 } } Mock Invoke-RestMethod -ParameterFilter { $Uri -match '/deviceManagement/managedDevices' } -MockWith { [pscustomobject]@{ value = @( [pscustomobject]@{ id = 'd-1'; deviceName = 'lap-01' userPrincipalName = 'admin@contoso.com'; userDisplayName = 'Admin' operatingSystem = 'Windows'; osVersion = '10.0.26100.5074' model = 'XPS 13'; manufacturer = 'Dell' complianceState = 'compliant' # 9999 sentinel = no grace period -> ComplianceGraceUntil must be $null. complianceGracePeriodExpirationDateTime = '9999-12-31T23:59:59.9999999Z' managementState = 'managed'; managementAgent = 'mdm' managedDeviceOwnerType = 'company' isEncrypted = $true; jailBroken = 'False'; azureADRegistered = $true enrolledDateTime = '2025-09-01T10:00:00Z' lastSyncDateTime = '2026-06-07T15:30:00Z' serialNumber = 'SN-001' } [pscustomobject]@{ id = 'd-2'; deviceName = 'phone-01' userPrincipalName = 'admin@contoso.com'; userDisplayName = 'Admin' operatingSystem = 'iOS'; osVersion = '18.5' model = 'iPhone 16 Pro'; manufacturer = 'Apple' # In-grace device: grace expiration is a REAL future date, not the sentinel. complianceState = 'inGracePeriod' complianceGracePeriodExpirationDateTime = '2026-06-15T00:00:00Z' managementState = 'managed'; managementAgent = 'mdm' managedDeviceOwnerType = 'personal' isEncrypted = $true; jailBroken = 'False'; azureADRegistered = $true enrolledDateTime = '2026-01-10T12:00:00Z' lastSyncDateTime = '2026-06-08T11:00:00Z' serialNumber = 'SN-002' } ) } } Get-MsecIntuneDevice } $rows.Count | Should -Be 2 # Row 1: Windows laptop, fully compliant, sentinel grace. $win = $rows | Where-Object Id -eq 'd-1' $win.DeviceName | Should -Be 'lap-01' $win.Os | Should -Be 'Windows' $win.ComplianceState | Should -Be 'compliant' $win.Manufacturer | Should -Be 'Dell' $win.Ownership | Should -Be 'company' $win.IsEncrypted | Should -BeTrue $win.ComplianceGraceUntil | Should -BeNullOrEmpty # 9999 sentinel -> null $win.EnrolledDateTime | Should -BeOfType [datetime] $win.LastSyncDateTime | Should -BeOfType [datetime] # Row 2: in-grace, real future grace date. $ios = $rows | Where-Object Id -eq 'd-2' $ios.Os | Should -Be 'iOS' $ios.ComplianceState | Should -Be 'inGracePeriod' $ios.Ownership | Should -Be 'personal' $ios.ComplianceGraceUntil | Should -BeOfType [datetime] $ios.ComplianceGraceUntil | Should -Be ([datetime]'2026-06-15T00:00:00Z') } It 'sends a $select trimming the request to the documented columns (so we do not pull all 80+ fields)' { InModuleScope Msec { Mock Invoke-MsecKeyVaultSign -MockWith { [byte[]](1..10) } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'oauth2/v2.0/token' } -MockWith { [pscustomobject]@{ access_token = 'mock'; expires_in = 3600 } } $script:CapturedUri = $null Mock Invoke-RestMethod -ParameterFilter { $Uri -match '/deviceManagement/managedDevices' } -MockWith { $script:CapturedUri = $Uri [pscustomobject]@{ value = @() } } Get-MsecIntuneDevice | Out-Null $script:CapturedUri | Should -Match '\$select=' # Spot-check that key documented columns are in the $select. $script:CapturedUri | Should -Match 'complianceState' $script:CapturedUri | Should -Match 'lastSyncDateTime' $script:CapturedUri | Should -Match 'azureADRegistered' } } It 'rewrites a 403 to mention the missing DeviceManagementManagedDevices.Read.All permission' { InModuleScope Msec { Mock Invoke-MsecKeyVaultSign -MockWith { [byte[]](1..10) } Mock Invoke-RestMethod -ParameterFilter { $Uri -match 'oauth2/v2.0/token' } -MockWith { [pscustomobject]@{ access_token = 'mock'; expires_in = 3600 } } Mock Invoke-RestMethod -ParameterFilter { $Uri -match '/deviceManagement/managedDevices' } -MockWith { throw 'Response status code does not indicate success: 403 (Forbidden).' } { Get-MsecIntuneDevice } | Should -Throw -ExpectedMessage '*DeviceManagementManagedDevices.Read.All*' } } } Describe 'Enrollment type decides whether management can be removed' { BeforeEach { InModuleScope Msec { $script:MsecSession = @{ TenantId = 't'; ClientId = 'c'; Tokens = @{} } } } It 'marks Apple ADE as automated and manual enrolment as not' { # An ADE-enrolled Mac has a management profile the user cannot remove; a manually # enrolled one does not, so every policy and CA decision resting on management can be # ended by whoever holds the laptop. Measured live: 9 of 19 Macs were manual. $rows = InModuleScope Msec { Mock Invoke-MsecGraphRequest -MockWith { @( [pscustomobject]@{ id='1'; deviceName='ADE Mac'; operatingSystem='macOS' deviceEnrollmentType='appleBulkWithUser'; isSupervised=$true } [pscustomobject]@{ id='2'; deviceName='Manual Mac'; operatingSystem='macOS' deviceEnrollmentType='userEnrollment'; isSupervised=$true } ) } @(Get-MsecIntuneDevice) } ($rows | Where-Object DeviceName -eq 'ADE Mac').IsAutomatedEnrollment | Should -BeTrue ($rows | Where-Object DeviceName -eq 'Manual Mac').IsAutomatedEnrollment | Should -BeFalse # Both report supervised, which is why IsSupervised cannot be used for this. ($rows | Where-Object DeviceName -eq 'Manual Mac').IsSupervised | Should -BeTrue } It 'leaves IsAutomatedEnrollment null on Windows, where the enum cannot answer' { # windowsAzureADJoin covers both Autopilot and a manual Entra join. $false would be a # claim the data does not support. $rows = InModuleScope Msec { Mock Invoke-MsecGraphRequest -MockWith { @([pscustomobject]@{ id='3'; deviceName='PC'; operatingSystem='Windows' deviceEnrollmentType='windowsAzureADJoin' }) } @(Get-MsecIntuneDevice) } $rows[0].IsAutomatedEnrollment | Should -BeNullOrEmpty $rows[0].EnrollmentType | Should -Be 'windowsAzureADJoin' } } |