Private/25-GetAtlasDevicesAndAuthentication.ps1
|
function Get-AtlasDeviceAndAuthentication { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $TenantId, [Parameter(Mandatory)] [AllowEmptyCollection()] [AtlasNode[]] $KnownNode, [switch] $SkipDeviceOwners, [switch] $SkipAuthenticationMethods, [ValidateRange(1, 20)] [int] $BatchSize = 10 ) $result = [AtlasCollectionResult]::new() $keyById = @{} foreach ($node in $KnownNode) { if (-not $keyById.ContainsKey($node.Id) -or $node.Status -eq 'complete') { $keyById[$node.Id] = $node.Key } } $devicesEndpoint = '/v1.0/devices?$select=id,displayName,deviceId,operatingSystem,operatingSystemVersion,trustType,isCompliant,isManaged,accountEnabled,approximateLastSignInDateTime' $deviceResponse = $null try { $deviceResponse = Invoke-AtlasGraphRequest -Uri $devicesEndpoint } catch { $result.Status = 'partial' $result.Warnings.Add("Devices could not be collected: $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") $deviceResponse = [pscustomobject] @{ Items = @() Metrics = @{ requestCount = 0; retryCount = 0 } } } $deviceKeyById = @{} if ($SkipDeviceOwners) { $result.Status = 'partial' $result.Warnings.Add('Device owner collection was skipped by request.') } $deviceIndex = 0 foreach ($device in $deviceResponse.Items) { $deviceIndex++ $deviceId = Get-AtlasResponseProperty -InputObject $device -Name 'id' if (-not $deviceId) { continue } $displayName = Get-AtlasResponseProperty -InputObject $device -Name 'displayName' if (-not $displayName) { $displayName = $deviceId } $deviceNode = New-AtlasNode -TenantId $TenantId -Id $deviceId -Kind 'device' -DisplayName $displayName -Properties @{ deviceId = Get-AtlasResponseProperty -InputObject $device -Name 'deviceId' operatingSystem = Get-AtlasResponseProperty -InputObject $device -Name 'operatingSystem' operatingSystemVersion = Get-AtlasResponseProperty -InputObject $device -Name 'operatingSystemVersion' trustType = Get-AtlasResponseProperty -InputObject $device -Name 'trustType' isCompliant = Get-AtlasResponseProperty -InputObject $device -Name 'isCompliant' isManaged = Get-AtlasResponseProperty -InputObject $device -Name 'isManaged' accountEnabled = Get-AtlasResponseProperty -InputObject $device -Name 'accountEnabled' approximateLastSignInDateTime = Get-AtlasResponseProperty -InputObject $device -Name 'approximateLastSignInDateTime' } -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = '#microsoft.graph.device' resourcePath = "/devices/$deviceId" collector = 'devices' } $result.Nodes.Add($deviceNode) $deviceKeyById[$deviceId] = $deviceNode.Key if ($SkipDeviceOwners) { Update-AtlasProgressItem ` -CurrentItem $deviceIndex ` -TotalItems $deviceResponse.Items.Count ` -Status 'Device owners skipped' continue } $ownersEndpoint = "/v1.0/devices/$deviceId/registeredOwners?`$select=id,displayName" try { $ownersResponse = Invoke-AtlasGraphRequest -Uri $ownersEndpoint $deviceResponse.Metrics.requestCount += $ownersResponse.Metrics.requestCount $deviceResponse.Metrics.retryCount += $ownersResponse.Metrics.retryCount } catch { $result.Status = 'partial' $result.Warnings.Add("Registered owners could not be collected for device '$displayName': $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") Update-AtlasProgressItem ` -CurrentItem $deviceIndex ` -TotalItems $deviceResponse.Items.Count ` -Status 'Device owner request failed' continue } foreach ($owner in $ownersResponse.Items) { $ownerId = Get-AtlasResponseProperty -InputObject $owner -Name 'id' if (-not $ownerId) { continue } if ($keyById.ContainsKey($ownerId)) { $ownerKey = $keyById[$ownerId] } else { $odataType = Get-AtlasResponseProperty -InputObject $owner -Name '@odata.type' $ownerKind = switch ($odataType) { '#microsoft.graph.user' { 'user' } '#microsoft.graph.group' { 'group' } '#microsoft.graph.servicePrincipal' { 'servicePrincipal' } default { 'directoryObject' } } $ownerName = Get-AtlasResponseProperty -InputObject $owner -Name 'displayName' if (-not $ownerName) { $ownerName = $ownerId } $ownerNode = New-AtlasNode -TenantId $TenantId -Id $ownerId -Kind $ownerKind -DisplayName $ownerName -Status 'unresolved' -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' resourcePath = "/directoryObjects/$ownerId" collector = 'devices' } $result.Nodes.Add($ownerNode) $ownerKey = $ownerNode.Key } $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'devices' -Endpoint $ownersEndpoint -SourceObjectId "$ownerId|registeredOwner|$deviceId" -Fields @{ ownerId = $ownerId deviceId = $deviceId } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $ownerKey -To $deviceNode.Key -Relationship 'registeredDevice' -State @{ assignment = 'registeredOwner' } -EvidenceIds @($evidence.Key) -Source @{ collector = 'devices' }) ) } Update-AtlasProgressItem ` -CurrentItem $deviceIndex ` -TotalItems $deviceResponse.Items.Count ` -Status 'Device owners' } $authMethodCount = 0 $authenticationLogicalRequestCount = 0 $userNode = @($KnownNode | Where-Object { $_.Kind -in @('user', 'guestUser') }) if ($SkipAuthenticationMethods) { $result.Status = 'partial' $result.Warnings.Add('Authentication method collection was skipped by request.') Update-AtlasProgressItem ` -CurrentItem $userNode.Count ` -TotalItems $userNode.Count ` -Status 'Authentication methods skipped' } elseif ($userNode.Count -gt 0) { $nodeByBatchId = @{} $batchRequest = @( for ($index = 0; $index -lt $userNode.Count; $index++) { $batchId = "auth-$index" $nodeByBatchId[$batchId] = $userNode[$index] [pscustomobject] @{ Id = $batchId Uri = "/v1.0/users/$($userNode[$index].Id)/authentication/methods" } } ) $methodsBatch = Invoke-AtlasGraphBatchGet ` -Request $batchRequest ` -BatchSize $BatchSize ` -ProgressStatus 'Authentication methods' $deviceResponse.Metrics.requestCount += $methodsBatch.Metrics.requestCount $deviceResponse.Metrics.retryCount += $methodsBatch.Metrics.retryCount $authenticationLogicalRequestCount = $methodsBatch.Metrics.logicalRequestCount $processedUserCount = 0 foreach ($methodsResponse in $methodsBatch.Responses) { $processedUserCount++ $node = $nodeByBatchId[$methodsResponse.Id] $methodsEndpoint = "/v1.0/users/$($node.Id)/authentication/methods" if ($methodsResponse.StatusCode -lt 200 -or $methodsResponse.StatusCode -ge 300) { $result.Status = 'partial' $result.Warnings.Add( "Authentication methods could not be collected for user '$($node.DisplayName)': $($methodsResponse.ErrorMessage)" ) Update-AtlasProgressItem ` -CurrentItem $processedUserCount ` -TotalItems $userNode.Count ` -Status 'Authentication methods' continue } foreach ($method in $methodsResponse.Items) { $methodId = Get-AtlasResponseProperty -InputObject $method -Name 'id' if (-not $methodId) { continue } $odataType = Get-AtlasResponseProperty -InputObject $method -Name '@odata.type' $methodKind = if ($odataType) { $odataType.Replace('#microsoft.graph.', '') } else { 'authenticationMethod' } $methodNode = New-AtlasNode -TenantId $TenantId -Id "$($node.Id):authenticationMethod:$methodId" -Kind 'authenticationMethod' -DisplayName $methodKind -Properties @{ methodId = $methodId methodType = $methodKind odataType = $odataType } -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = $odataType resourcePath = "/users/$($node.Id)/authentication/methods/$methodId" collector = 'authenticationMethods' } $result.Nodes.Add($methodNode) $authMethodCount++ $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'authenticationMethods' -Endpoint $methodsEndpoint -SourceObjectId "$($node.Id)|authenticationMethod|$methodId" -Fields @{ userId = $node.Id methodId = $methodId methodType = $methodKind } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $node.Key -To $methodNode.Key -Relationship 'hasAuthenticationMethod' -State @{ methodType = $methodKind } -EvidenceIds @($evidence.Key) -Source @{ collector = 'authenticationMethods' }) ) } Update-AtlasProgressItem ` -CurrentItem $processedUserCount ` -TotalItems $userNode.Count ` -Status 'Authentication methods' } } $result.Metrics = @{ deviceCount = $deviceResponse.Items.Count authenticationMethodCount = $authMethodCount authenticationLogicalRequestCount = $authenticationLogicalRequestCount deviceOwnersSkipped = [bool] $SkipDeviceOwners authenticationMethodsSkipped = [bool] $SkipAuthenticationMethods batchSize = $BatchSize requestCount = $deviceResponse.Metrics.requestCount retryCount = $deviceResponse.Metrics.retryCount } return $result } |