diff --git a/src/Api.ps1 b/src/Api.ps1 index 31289c4..b6f7581 100644 --- a/src/Api.ps1 +++ b/src/Api.ps1 @@ -1710,7 +1710,7 @@ function Get-GroupDevicesExportEndpoint { return @{ ok = $true; groupId = $GroupId; groupName = $groupName; userCount = 0; items = @(); count = 0; resolvedMs = [int]$sw.ElapsedMilliseconds } } - $select = 'id,deviceName,userDisplayName,userPrincipalName,operatingSystem,osVersion,complianceState,serialNumber,model,manufacturer,lastSyncDateTime,enrolledDateTime' + $select = 'id,deviceName,userDisplayName,userPrincipalName,operatingSystem,osVersion,complianceState,serialNumber,model,manufacturer,lastSyncDateTime,enrolledDateTime,managementAgent' $batchSize = 20 $devSeen = @{} $items = [System.Collections.Generic.List[object]]::new() @@ -1757,6 +1757,8 @@ function Get-GroupDevicesExportEndpoint { OS = [string]$d.operatingSystem OSVersion = [string]$d.osVersion ComplianceState = [string]$d.complianceState + ManagementAgent = [string]$d.managementAgent + ManagementType = Get-ManagementType ([string]$d.managementAgent) SerialNumber = [string]$d.serialNumber Model = [string]$d.model Manufacturer = [string]$d.manufacturer @@ -1840,6 +1842,15 @@ function Get-UserMemberOfEndpoint { # Devices # ============================================================ +# Verwaltungsart aus dem Graph-Feld 'managementAgent' ableiten. +# 'configurationManagerClientMdm' / 'configurationManagerClientMdmEas' -> Co-Managed +# (ConfigMgr + Intune), alles andere -> reines Intune (MDM). +function Get-ManagementType { + param([string]$Agent) + if ($Agent -match 'configurationManager') { return 'Co-Managed' } + return 'Intune' +} + function Search-DevicesEndpoint { param($Query) $err = Test-Connected @@ -1852,7 +1863,7 @@ function Search-DevicesEndpoint { # HINWEIS: 'managementState' ist KEIN gueltiges $select-Feld auf managedDevices # -> fuehrt zu 400 BadRequest. Bewusst weggelassen. - $select = 'id,deviceName,userDisplayName,userPrincipalName,operatingSystem,osVersion,complianceState,lastSyncDateTime,serialNumber,model,manufacturer,enrolledDateTime' + $select = 'id,deviceName,userDisplayName,userPrincipalName,operatingSystem,osVersion,complianceState,lastSyncDateTime,serialNumber,model,manufacturer,enrolledDateTime,managementAgent' # Ohne Suchbegriff: alle Geräte (erste Seite) # Mit Suchbegriff: Graph unterstuetzt startswith nur auf deviceName/userDisplayName/userPrincipalName. @@ -1924,6 +1935,8 @@ function Search-DevicesEndpoint { ComplianceState = $d.complianceState LastSync = ConvertTo-IsoDate $d.lastSyncDateTime ManagementState = $d.managementState + ManagementAgent = [string]$d.managementAgent + ManagementType = Get-ManagementType ([string]$d.managementAgent) SerialNumber = $d.serialNumber Model = $d.model Manufacturer = $d.manufacturer @@ -1939,12 +1952,37 @@ function Get-DeviceEndpoint { if ($err) { return $err } # 'managementState' entfernt: kein gueltiges $select-Feld auf managedDevices (400). - $select = 'id,deviceName,userDisplayName,userPrincipalName,operatingSystem,osVersion,complianceState,lastSyncDateTime,serialNumber,model,manufacturer,enrolledDateTime,imei,wiFiMacAddress,azureADDeviceId,joinType,deviceEnrollmentType,managedDeviceOwnerType,totalStorageSpaceInBytes,freeStorageSpaceInBytes' + $select = 'id,deviceName,userDisplayName,userPrincipalName,operatingSystem,osVersion,complianceState,lastSyncDateTime,serialNumber,model,manufacturer,enrolledDateTime,imei,wiFiMacAddress,azureADDeviceId,joinType,deviceEnrollmentType,managedDeviceOwnerType,managementAgent,totalStorageSpaceInBytes,freeStorageSpaceInBytes' $d = Invoke-MgGraphRequestRetry -Uri "https://graph.microsoft.com/beta/deviceManagement/managedDevices/$DeviceId`?`$select=$select" -Method GET $totalGB = if ($d.totalStorageSpaceInBytes) { [math]::Round($d.totalStorageSpaceInBytes / 1GB, 1) } else { $null } $freeGB = if ($d.freeStorageSpaceInBytes) { [math]::Round($d.freeStorageSpaceInBytes / 1GB, 1) } else { $null } + # Autopilot-Identity per Seriennummer nachladen (nur Windows, fehlertolerant). + # HINWEIS: Ein echtes "Import-/Registrierungsdatum" liefert Graph nicht; verfuegbar + # sind nur Profil-Zuweisung (deploymentProfileAssignedDateTime) und letzter Kontakt. + $inAutopilot = $false + $apProfileAssign = $null + $apLastContact = $null + $sn = [string]$d.serialNumber + if ($sn -and ([string]$d.operatingSystem).ToLower() -eq 'windows') { + try { + $snEsc = $sn -replace "'", "''" + $apFilt = [Uri]::EscapeDataString("contains(serialNumber,'$snEsc')") + $apSel = 'id,serialNumber,deploymentProfileAssignedDateTime,lastContactedDateTime,enrollmentState' + $apUri = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities?`$filter=$apFilt&`$select=$apSel&`$top=1" + $apResp = Invoke-MgGraphRequestRetry -Uri $apUri -Method GET + $autop = @($apResp.value)[0] + if ($autop) { + $inAutopilot = $true + $apProfileAssign = ConvertTo-IsoDate $autop.deploymentProfileAssignedDateTime + $apLastContact = ConvertTo-IsoDate $autop.lastContactedDateTime + } + } catch { + Write-Host " [DEVICE] Autopilot-Lookup (SN=$sn): $($_.Exception.Message)" -ForegroundColor DarkYellow + } + } + return @{ Id = $d.id DeviceName = $d.deviceName @@ -1955,6 +1993,8 @@ function Get-DeviceEndpoint { ComplianceState = $d.complianceState LastSync = ConvertTo-IsoDate $d.lastSyncDateTime ManagementState = $d.managementState + ManagementAgent = [string]$d.managementAgent + ManagementType = Get-ManagementType ([string]$d.managementAgent) SerialNumber = $d.serialNumber Model = $d.model Manufacturer = $d.manufacturer @@ -1967,6 +2007,9 @@ function Get-DeviceEndpoint { OwnerType = $d.managedDeviceOwnerType TotalStorageGB = $totalGB FreeStorageGB = $freeGB + InAutopilot = $inAutopilot + AutopilotProfileAssigned = $apProfileAssign + AutopilotLastContacted = $apLastContact } } diff --git a/www/app.js b/www/app.js index 63946e7..69f7c28 100644 --- a/www/app.js +++ b/www/app.js @@ -5872,12 +5872,20 @@ const DEV_COL_MAP = { deviceName: 'DeviceName', userDisplayName: 'UserDisplayName', operatingSystem: 'OS', + managementType: 'ManagementType', complianceState: 'ComplianceState', lastSyncDateTime: 'LastSync', enrolledDateTime: 'EnrolledDateTime', }; const DEV_DATE_COLS = new Set(['LastSync', 'EnrolledDateTime']); +// Verwaltungsart als Badge: Co-Managed (ConfigMgr + Intune) vs. reines Intune. +function devMgmtBadge(type) { + if (type === 'Co-Managed') return 'Co-Managed'; + if (type === 'Intune') return 'Intune'; + return '—'; +} + function devFmtDate(iso) { if (!iso) return '—'; const d = new Date(iso); @@ -5941,6 +5949,7 @@ function devRender() { ${escapeHtml(d.DeviceName || '—')} ${escapeHtml(d.UserDisplayName || '—')} ${escapeHtml(d.OS || '—')} + ${devMgmtBadge(d.ManagementType)} ${escapeHtml(d.ComplianceState || '—')} ${sync} `; @@ -6018,12 +6027,16 @@ function devRenderDetail(d) { ['Benutzer', d.UserDisplayName], ['UPN', d.UserPrincipalName], ['OS', d.OS ? `${d.OS} ${d.OSVersion || ''}`.trim() : null], + ['Verwaltung', d.ManagementType], ['Compliance', d.ComplianceState], ['Seriennummer', d.SerialNumber], ['IMEI', d.Imei], ['Wi-Fi MAC', d.WiFiMac], ['Speicher', storage], ['Eingeschrieben', enrolled], + ['Autopilot', d.InAutopilot ? 'Registriert' : (d.InAutopilot === false ? 'Nicht in Autopilot' : null)], + ['Autopilot-Profil zugewiesen', d.AutopilotProfileAssigned ? devFmtDate(d.AutopilotProfileAssigned) : null], + ['Autopilot letzter Kontakt', d.AutopilotLastContacted ? devFmtDate(d.AutopilotLastContacted) : null], ['Letzter Sync', sync], ['Join-Typ', d.JoinType], ['Besitzertyp', d.OwnerType], @@ -6222,12 +6235,12 @@ document.addEventListener('click', e => { function devExportCsv() { const items = DevState.filtered || []; if (items.length === 0) { toast('Keine Geräte zum Exportieren.', 'warn'); return; } - const header = ['DeviceName','UserDisplayName','UserPrincipalName','OS','OSVersion','ComplianceState','SerialNumber','Model','Manufacturer','LastSync','EnrolledDateTime']; + const header = ['DeviceName','UserDisplayName','UserPrincipalName','OS','OSVersion','ManagementType','ComplianceState','SerialNumber','Model','Manufacturer','LastSync','EnrolledDateTime']; const rows = [header]; for (const d of items) { rows.push([ d.DeviceName || '', d.UserDisplayName || '', d.UserPrincipalName || '', - d.OS || '', d.OSVersion || '', d.ComplianceState || '', + d.OS || '', d.OSVersion || '', d.ManagementType || '', d.ComplianceState || '', d.SerialNumber || '', d.Model || '', d.Manufacturer || '', d.LastSync ? devFmtDate(d.LastSync) : '', d.EnrolledDateTime ? devFmtDate(d.EnrolledDateTime) : '', ]); diff --git a/www/index.html b/www/index.html index 7c062a9..e527d0c 100644 --- a/www/index.html +++ b/www/index.html @@ -527,6 +527,7 @@ Gerät Benutzer OS + Verwaltung Compliance Letzter Sync diff --git a/www/styles.css b/www/styles.css index 3df22ce..6931824 100644 --- a/www/styles.css +++ b/www/styles.css @@ -5427,6 +5427,8 @@ body.read-only .app-row { .dev-badge-ok { background: rgba(74,222,128,.15); color: #4ade80; } .dev-badge-err { background: rgba(248,113,113,.15); color: #f87171; } .dev-badge-unk { background: var(--hairline-soft); color: var(--muted); } +.dev-badge-intune { background: rgba(96,165,250,.15); color: #60a5fa; } +.dev-badge-comgmt { background: rgba(251,191,36,.15); color: #fbbf24; } /* Detail-Panel */ .dev-detail {