Quản lý thiết bị Non-Compliance trên Intune: phân loại nguyên nhân, tự động xử lý và chặn truy cập
Mở báo cáo Compliance trong Intune, mình thấy hàng trăm thiết bị Not compliant: laptop Windows, MacBook, điện thoại Android và iPhone. Nhìn qua thì tưởng đều là “máy có vấn đề bảo mật”. Nhưng khi đào sâu, phần lớn lại là máy đã thanh lý, máy nằm trong kho, máy enroll trùng serial, hoặc máy của nhân viên đã nghỉ việc. Chỉ một phần nhỏ thật sự là máy đang được dùng nhưng không tuân thủ policy. Với môi trường khoảng 2.000 máy tính cộng thêm thiết bị di động, xử lý từng máy bằng tay là không khả thi. Bài này chia sẻ cách mình phân loại tự động và xử lý theo từng nguyên nhân.
Tóm tắt nhanh: Power BI gộp dữ liệu từ Intune Data Warehouse, SentinelOne, Jira Asset và AD, rồi dùng DAX gán cho mỗi máy một Reason và Action Require. Power Automate lấy nhóm Delete / Backup&Delete / Wipe ra CSV để chạy Graph API, còn các nhóm còn lại được thêm vào một group bị chặn truy cập tài nguyên công ty, kèm email báo cho user.
Bối cảnh: vì sao không thể tin hoàn toàn vào Intune
Danh sách Non-Compliance của Intune có ba vấn đề:
- Last check-in chưa chính xác. Có máy vẫn đang online nhưng Intune ghi nhận check-in từ nhiều tuần trước.
- Không biết trạng thái thực tế của máy. Intune không biết máy đang ở kho, đang chuẩn bị bàn giao hay đã thanh lý.
- Không biết trạng thái tài khoản. Primary user có thể đã bị disable trên AD từ lâu.
Vì vậy mình bổ sung thêm ba nguồn dữ liệu để có bức tranh đầy đủ.
| Nguồn | Dùng để làm gì |
|---|---|
| Intune Data Warehouse | Danh sách thiết bị, trạng thái compliance, serial, primary user, last check-in |
| SentinelOne | Last seen từ agent EDR, dùng để đối chiếu với last check-in của Intune |
| Jira Asset | Trạng thái tài sản: In Use, In Stock, In Prepare, Liquidation, user đang được gán |
| AD Computer / User object | Tài khoản user và máy đang Enabled hay Disabled |
Kiến trúc tổng thể
| Bước | Thành phần | Việc làm |
|---|---|---|
| 1 | Power BI | Lấy dữ liệu từ 4 nguồn, dùng DAX tạo bảng NonCompliance |
| 2 | DAX | Gán Reason và Action Require cho từng máy |
| 3 | Power Automate (Flow 1) | Lấy máy có Action = Delete, Backup&Delete, Wipe, xuất CSV |
| 4 | Graph API | Backup khóa (nếu cần), rồi delete hoặc wipe thiết bị |
| 5 | Power Automate (Flow 2) | Lấy các Action còn lại, thêm thiết bị vào group chặn truy cập |
| 6 | Intune | Group đã được assign sẵn cho app/policy thực thi chặn truy cập |
| 7 | Power Automate (Flow 3) | Gửi email cho user, hướng dẫn cách khắc phục |
Hình 1: luồng xử lý từ Power BI đến Intune.
Bảng phân loại Reason và Action Require
Đây là phần quan trọng nhất của cả quy trình. Mỗi máy chỉ nhận một Reason, theo thứ tự ưu tiên từ trên xuống: điều kiện nào khớp trước thì dừng lại.
| Thứ tự | Reason | Action Require |
|---|---|---|
| 1 | Duplicate serial number (older check-in) | Delete |
| 2 | Liquidation | Delete |
| 3 | Device already in wipe state | Delete |
| 4 | In Stock and Primary User has a new device | Delete |
| 5 | In use and inactive for over 90 days | Backup&Delete |
| 6 | In stock and inactive for over 30 days | Wipe |
| 7 | No Serial Number | Re-check |
| 8 | No Primary User | Re-enroll |
| 9 | Primary user account disabled | Re-enroll |
| 10 | In use and inactive for over 30 days | Contact User |
| 11 | Other reasons | Other |
Vì sao thứ tự quan trọng? Một máy vừa trùng serial vừa inactive 90 ngày phải được xếp vào nhóm Delete (bản ghi cũ, an toàn để xóa), chứ không phải Backup&Delete. Tương tự, điều kiện “inactive 90 ngày” phải đứng trước “inactive 30 ngày”.
Bước 1-2: Tạo bảng phân loại bằng DAX
Lấy last check-in gần nhất
Ý tưởng là so sánh hai mốc thời gian và lấy mốc mới hơn:
LastActivity =
VAR _Intune = Intune[lastSyncDateTime]
VAR _S1 =
LOOKUPVALUE ( S1[Last Active Date], S1[Serial Number], Intune[serialNumber] )
RETURN
IF ( ISBLANK ( _S1 ) || _Intune >= _S1, _Intune, _S1 )
Bảng NonCompliance hoàn chỉnh
Dưới đây là khung DAX mình dùng tạo bảng mơi trên Power BI. Tên bảng và cột là ví dụ, bạn đổi theo dataset thật:
Non-Compliance =
VAR ExtendedIntune =
ADDCOLUMNS (
FILTER (
Intune,
Intune[isDeleted] = FALSE &&
Intune[operatingSystem] = "Windows" &&
LEFT(Intune[deviceName], 3) = "VN-" &&
NOT ISBLANK(Intune[azureADRegistered]) &&
(Intune[deviceRegistrationStateKey] = 5172 || Intune[deviceRegistrationStateKey] = 5226)
),
"ComplianceStatus", LOOKUPVALUE (
State[complianceStatus],
State[complianceStateKey], Intune[complianceStateKey]
),
"JiraStatus", CALCULATE (
MAX ( Jira[Status] ),
FILTER ( Jira, Jira[Serial Number] = Intune[serialNumber] )
),
"JiraStock", CALCULATE (
MAX ( Jira[Stock] ),
FILTER ( Jira, Jira[Serial Number] = Intune[serialNumber] )
),
"OfficeLocation", CALCULATE (
MAX ( UsersAD[physicalDeliveryOfficeName] ),
FILTER ( UsersAD, UsersAD[employeeNumber] = Intune[primaryUser] )
),
"UserAccountControl", CALCULATE (
MAX ( UsersAD[userAccountControl] ),
FILTER ( UsersAD, UsersAD[employeeNumber] = Intune[primaryUser] )
),
"DeviceAccountControl", CALCULATE (
MAX ( DevicesAD[userAccountControl] ),
FILTER ( DevicesAD, DevicesAD[serialNumber] = Intune[serialNumber] )
),
"LastActiveDate", CALCULATE (
MAX ( Agents[lastActiveDt] ),
FILTER ( Agents, Agents[serialNumber] = Intune[serialNumber] )
),
"LastRebootDate", CALCULATE (
MAX ( Agents[lastRebootDt] ),
FILTER ( Agents, Agents[serialNumber] = Intune[serialNumber] )
),
"WipeState", CALCULATE (
MAX ( Wipe[managementStateName] ),
FILTER ( Wipe, Wipe[managementStateKey] = Intune[managementStateKey] )
),
"DuplicateSerialMaxCheckin", CALCULATE (
MAX ( Intune[lastSyncDateTime] ),
ALLEXCEPT ( Intune, Intune[serialNumber] )
),
"DuplicateSerialCount", CALCULATE (
COUNTROWS ( Intune ),
ALLEXCEPT ( Intune, Intune[serialNumber] )
),
"PrimaryUserDeviceCount", CALCULATE (
COUNTROWS ( Intune ),
FILTER ( Intune, Intune[primaryUser] = EARLIER(Intune[primaryUser]) )
)
)
VAR FilteredIntune =
FILTER (
ExtendedIntune,
[ComplianceStatus] = "Noncompliant"
)
VAR FinalIntune =
ADDCOLUMNS (
FilteredIntune,
"Reason", SWITCH (
TRUE(),
TRIM(Intune[serialNumber]) = "", "No Serial Number",
ISBLANK([JiraStatus]) || ISBLANK([JiraStock]), "Liquidation",
[DuplicateSerialCount] > 1 && Intune[lastSyncDateTime] < [DuplicateSerialMaxCheckin], "Duplicate serial number (older check-in)",
[WipeState] IN { "RetirePending", "WipeFailed", "WipePending", "RetireIssued", "WipeIssued" }, "Device already in wipe state",
[JiraStatus] = "IN STOCK" &&
Intune[primaryUser] <> "00000000-0000-0000-0000-000000000000" &&
Intune[lastSyncDateTime] < TODAY() - 30 &&
(ISBLANK([LastActiveDate]) || [LastActiveDate] < TODAY() - 30) &&
[PrimaryUserDeviceCount] > 1, "In Stock and Primary User has a new device",
[JiraStatus] = "IN STOCK" &&
Intune[lastSyncDateTime] < TODAY() - 30 &&
(ISBLANK([LastActiveDate]) || [LastActiveDate] < TODAY() - 30), "In stock and inactive for over 30 days",
[JiraStatus] = "IN USE" &&
(Intune[primaryUser] = "00000000-0000-0000-0000-000000000000" ||
CALCULATE (
COUNTROWS (
FILTER (
User,
User[userId] = SELECTEDVALUE(Intune[primaryUser]) &&
(User[isDeleted] = TRUE || ISBLANK(User[userEmail]))
)
)
) > 0), "No Primary User",
[JiraStatus] = "IN USE" &&
Intune[lastSyncDateTime] < TODAY() - 30 &&
Intune[primaryUser] <> "00000000-0000-0000-0000-000000000000" &&
(ISBLANK([LastActiveDate]) || [LastActiveDate] < TODAY() - 30), "In use and inactive for over 30 days",
CALCULATE (
COUNTROWS (
FILTER (
User,
User[userId] = SELECTEDVALUE(Intune[primaryUser]) &&
(User[intuneLicensed] = FALSE)
)
)
) > 0, "Primary User account Disabled",
"Other reasons"
),
"ActionRequire", SWITCH (
TRUE(),
TRIM(Intune[serialNumber]) = "", "Re-check",
ISBLANK([JiraStatus]) || ISBLANK([JiraStock]), "Delete",
[DuplicateSerialCount] > 1 && Intune[lastSyncDateTime] < [DuplicateSerialMaxCheckin], "Delete",
[WipeState] IN { "RetirePending", "WipeFailed", "WipePending", "RetireIssued", "WipeIssued" }, "Delete",
[JiraStatus] = "IN STOCK" &&
Intune[primaryUser] <> "00000000-0000-0000-0000-000000000000" &&
Intune[lastSyncDateTime] < TODAY() - 30 &&
(ISBLANK([LastActiveDate]) || [LastActiveDate] < TODAY() - 30) &&
[PrimaryUserDeviceCount] > 1, "Delete",
[JiraStatus] = "IN STOCK" &&
Intune[lastSyncDateTime] < TODAY() - 30 &&
(ISBLANK([LastActiveDate]) || [LastActiveDate] < TODAY() - 30), "Wipe",
[JiraStatus] = "IN USE" &&
(Intune[primaryUser] = "00000000-0000-0000-0000-000000000000" ||
CALCULATE (
COUNTROWS (
FILTER (
User,
User[userId] = SELECTEDVALUE(Intune[primaryUser]) &&
(User[isDeleted] = TRUE || ISBLANK(User[userEmail]))
)
)
) > 0), "Re-enroll",
[JiraStatus] = "IN USE" &&
Intune[primaryUser] <> "00000000-0000-0000-0000-000000000000" &&
Intune[lastSyncDateTime] < TODAY() - 30 &&
(ISBLANK([LastActiveDate]) || [LastActiveDate] < TODAY() - 30), "Contact User",
CALCULATE (
COUNTROWS (
FILTER (
User,
User[userId] = SELECTEDVALUE(Intune[primaryUser]) &&
(User[intuneLicensed] = FALSE)
)
)
) > 0, "Re-enroll",
"Other"
)
)
RETURN
SELECTCOLUMNS (
FinalIntune,
"Reason", [Reason],
"Action Require", [ActionRequire],
"Device ID", Intune[deviceId],
"Device Name", Intune[deviceName],
"Last Check-in", Intune[lastSyncDateTime],
"Last Reboot", [LastRebootDate],
"AD State", [DeviceAccountControl],
"Device State", [ComplianceStatus],
"Serial Number", Intune[serialNumber],
"Primary User UPN", Intune[primaryUser],
"Office Location", [OfficeLocation],
"Jira Status", [JiraStatus],
"Jira Stock", [JiraStock]
)
Khi viết DAX cho bài toán này, chú ý:
LOOKUPVALUEsẽ báo lỗi nếu khóa tra cứu bị trùng (ví dụ một serial có hai dòng trong Jira). Hãy lọc trùng ở Power Query trước, hoặc dùngMAXX(FILTER(...)).- Ghi chú thêm cột
Reasondạng chuỗi rõ ràng để Power Automate lọc và để License/Helpdesk team đọc hiểu ngay. - Với máy bị loại khỏi kiểm tra (ví dụ máy không thuộc phạm vi), vẫn giữ trong bảng nhưng đặt Action =
Otherkèm lý do, để báo cáo không bị “mất” thiết bị.
Bước 3-4: Xóa và wipe thiết bị bằng Power Automate và Graph API
Flow 1: xuất danh sách ra CSV
- Recurrence chạy theo lịch, ví dụ mỗi tuần.
- Run a query against a dataset lấy các dòng có Action là
Delete,Backup&DeletehoặcWipe:
EVALUATE
FILTER (
NonCompliance,
NonCompliance[Action Require] IN { "Delete", "Backup&Delete", "Wipe" }
)
- Create CSV table rồi Create file lưu vào SharePoint, đặt tên kèm ngày chạy để làm bằng chứng.
Gọi Graph API
Một PowerShell script (chạy trên server hoặc Azure Automation bằng App Registration có quyền DeviceManagementManagedDevices.ReadWrite.All và DeviceManagementManagedDevices.PrivilegedOperations.All) đọc CSV và xử lý theo Action:
| Action | Graph API |
|---|---|
| Delete | DELETE /deviceManagement/managedDevices/{id} |
| Wipe | POST /deviceManagement/managedDevices/{id}/wipe |
| Backup&Delete | Backup BitLocker key (/informationProtection/bitlocker/recoveryKeys) và LAPS (/directory/deviceLocalCredentials), lưu lại, rồi mới DELETE |
# Ví dụ khung xử lý - đọc CSV và gọi Graph theo Action
#Delete devices
$GetDevices = Import-csv "C:\Temp\DevicesToRemove.csv" #Intune ID
$serverUrl = "https://graph.microsoft.com"
$userToken = Get-Content -Path 'C:\Temp\IntuneToken.txt'
$apiRequest = '/beta/deviceManagement/managedDevices/executeAction' ## Change to Desired Get Method API Call
Start-Transcript "C:\Temp\IntuneCleanup.txt" -Force
foreach($Device in $GetDevices){
$BodyJSON = @{
"actionName" = "delete"
"deviceIds" = @(
"$($Device."DeviceID")"
)
} | ConvertTo-JSON -Depth 20
Write-Host "Deleting Tired devices($($Device."Device Name") - User: $($Device."Primary user UPN"))" -ForegroundColor Yellow
$Params = @{ Uri = ($serverUrl + $apiRequest)
Headers = @{'Authorization' = "Bearer $($userToken)"}
Method = 'POST'
BODY = $BodyJSON
ContentType = "application/json" }
Invoke-RestMethod @Params
}
Stop-Transcript
#Backup LAPS, Bitlocker trước
# ============ INPUT ============
$CsvInput = "C:\Temp\DevicesToRemove.csv" #Entra ID
$TokenFile = "C:\Temp\IntuneToken.txt" #Get from Intune Web
$CsvOutput = "C:\Temp\LAPS_BitLocker_$(Get-Date -Format 'yyyyMMdd_HHmm').csv"
# ============ GRAPH ============
$GraphBeta = "https://graph.microsoft.com/beta"
$GraphV1 = "https://graph.microsoft.com/v1.0"
# ============ AUTH ============
$Token = (Get-Content -Path $TokenFile -Raw) -replace '\s',''
$Headers = @{
Authorization = "Bearer $Token"
"Content-Type" = "application/json"
}
# ============ HELPERS ============
function Invoke-Graph {
param($Uri)
return Invoke-RestMethod -Uri $Uri -Headers $Headers -Method GET -ErrorAction Stop
}
function Invoke-GraphRetry {
param($Uri)
for ($i=1; $i -le 3; $i++) {
try {
return Invoke-Graph $Uri
}
catch {
Start-Sleep -Seconds (2 * $i)
}
}
throw
}
# ============ MAIN ============
$Devices = Import-Csv $CsvInput
$Report = @()
$i = 0
$total = $Devices.Count
foreach ($row in $Devices) {
$i++
$DeviceId = ($row.DeviceID -replace '\s','')
Write-Host "`n[$i/$total] Processing $DeviceId" -ForegroundColor Cyan
# ================= DEVICE NAME =================
$DeviceName = ""
$AzureAdDeviceId = ""
try {
$dev = Invoke-GraphRetry "$GraphV1/devices/$DeviceId`?`$select=id,deviceId,displayName"
$DeviceName = $dev.displayName
$AzureAdDeviceId = $dev.deviceId
Write-Host " 🖥️ $DeviceName" -ForegroundColor Green
}
catch {
Write-Host " ⚠️ Device lookup failed" -ForegroundColor Yellow
}
# ================= LAPS =================
$AccountName=""
$Password=""
$BackupDateTime=""
$LAPSStatus=""
try {
$lapsUri = "$GraphBeta/directory/deviceLocalCredentials/$DeviceId`?`$select=credentials"
$res = Invoke-GraphRetry $lapsUri
if ($res.credentials) {
$latest = $res.credentials |
Sort-Object {[datetime]$_.backupDateTime} -Descending |
Select-Object -First 1
$Password = [Text.Encoding]::UTF8.GetString(
[Convert]::FromBase64String($latest.passwordBase64)
)
$AccountName = $latest.accountName
$BackupDateTime = ([datetime]$latest.backupDateTime).ToString("yyyy-MM-dd HH:mm:ss")
$LAPSStatus = "OK"
Write-Host " ✅ LAPS OK" -ForegroundColor Green
}
else {
$LAPSStatus = "No LAPS"
}
}
catch {
$LAPSStatus = "ERROR"
}
Start-Sleep -Milliseconds 200
# ================= BITLOCKER =================
$BitLockerKeyId=""
$BitLockerKey=""
$BitLockerDate=""
$BitLockerStatus=""
$candidateIds = @($DeviceId)
if ($AzureAdDeviceId) { $candidateIds += $AzureAdDeviceId }
foreach ($cid in $candidateIds) {
try {
Write-Host " 🔎 Try BitLocker deviceId: $cid" -ForegroundColor DarkCyan
$filter = [uri]::EscapeDataString("deviceId eq '$cid'")
$uri = "$GraphV1/informationProtection/bitlocker/recoveryKeys?`$filter=$filter"
$list = Invoke-GraphRetry $uri
if (-not $list.value) { continue }
$latest = $list.value |
Sort-Object {[datetime]$_.createdDateTime} -Descending |
Select-Object -First 1
$keyUri = "$GraphV1/informationProtection/bitlocker/recoveryKeys/$($latest.id)`?`$select=key"
$keyRes = Invoke-GraphRetry $keyUri
$BitLockerKeyId = $latest.id
$BitLockerKey = $keyRes.key
$BitLockerDate = ([datetime]$latest.createdDateTime).ToString("yyyy-MM-dd HH:mm:ss")
$BitLockerStatus = "OK"
Write-Host " ✅ BitLocker OK" -ForegroundColor Green
break
}
catch {
continue
}
}
if (-not $BitLockerStatus) {
$BitLockerStatus = "No BitLocker"
Write-Host " ⚠️ No BitLocker" -ForegroundColor Yellow
}
# ================= OUTPUT =================
$Report += [PSCustomObject]@{
DeviceId = $DeviceId
DeviceName = $DeviceName
AccountName = $AccountName
Password = $Password
BackupDateTime = $BackupDateTime
BitLockerKeyId = $BitLockerKeyId
BitLockerRecoveryKey = $BitLockerKey
BitLockerCreatedDateTime = $BitLockerDate
LAPSStatus = $LAPSStatus
BitLockerStatus = $BitLockerStatus
}
Start-Sleep -Milliseconds 200
}
# ============ EXPORT ============
$Report | Export-Csv $CsvOutput -NoTypeInformation -Encoding UTF8
Write-Host "`n✅ DONE → $CsvOutput" -ForegroundColor Green
Backup&Delete, chỉ xóa khi đã lưu thành công khóa BitLocker và LAPS. Nên chạy chế độ dry-run và gửi danh sách cho người duyệt trước mỗi lần chạy thật.Bước 5-6: Chặn truy cập tài nguyên công ty bằng Group
Các máy thuộc nhóm Re-enroll, Re-check, Contact User, Other vẫn đang thuộc về user, nên không xóa mà chặn truy cập cho tới khi máy Compliance trở lại.
Flow 2: thêm thiết bị vào group
- Run a query against a dataset lấy các dòng có Action không thuộc nhóm Delete / Backup&Delete / Wipe.
- Với mỗi thiết bị, gọi Graph API thêm vào group đã được assign sẵn trong Intune:
POST https://graph.microsoft.com/v1.0/groups/{group-id}/members/$ref
Content-Type: application/json
{
"@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/{entra-device-object-id}"
}
Lưu ý: {entra-device-object-id} là Object ID của device trong Entra ID, khác với azureADDeviceId lấy từ Intune. Cần tra trước bằng GET /devices?$filter=deviceId eq '{azureADDeviceId}'.
- Khi máy Compliance trở lại, flow chạy lần sau sẽ không còn thấy máy đó trong bảng. Thêm một bước so sánh thành viên group với bảng hiện tại để gỡ máy ra khỏi group (
DELETE /groups/{group-id}/members/{id}/$ref).
Group được dùng để làm gì
Group này được assign sẵn cho một Intune app/policy thực thi việc chặn truy cập, ví dụ gỡ chứng chỉ dùng cho Wi-Fi/VPN nội bộ, nên máy không kết nối được vào mạng công ty. Khi máy ra khỏi group, chứng chỉ được cấp lại.
# Block_Access.ps1 - Intune app assigned to Non-Compliance group
Bước 7: Gửi email cho user
Flow 3 gom theo Primary User, mỗi user chỉ nhận một email liệt kê các thiết bị và việc họ cần làm. Nội dung email thay đổi theo Action:
| Action | User cần làm |
|---|---|
| Contact User | Bật máy, kết nối mạng và mở Company Portal để đồng bộ |
| Re-enroll | Liên hệ Helpdesk để enroll lại thiết bị |
| Re-check | Liên hệ Helpdesk để kiểm tra lại thông tin thiết bị |
| Other | Đọc lý do Non-Compliance trong Company Portal và khắc phục |
Subject: [IT Notice] Thiết bị {Device Name} không tuân thủ chính sách bảo mật
Chào anh/chị {User Name},
Thiết bị {Device Name} (Serial: {Serial Number}) đang ở trạng thái
Non-Compliance. Lý do: {Reason}.
Để tiếp tục truy cập tài nguyên công ty, vui lòng:
{Hướng dẫn theo Action}
Nếu không khắc phục trước ngày {Deadline}, thiết bị sẽ bị chặn truy cập
mạng nội bộ cho đến khi tuân thủ chính sách.
Trân trọng,
IT Team
Các lệnh kiểm tra trên thiết bị
Windows
# Ép đồng bộ với Intune
Get-ScheduledTask | Where-Object TaskName -eq 'PushLaunch' | Start-ScheduledTask
# Trạng thái join Entra ID / MDM
dsregcmd /status | Select-String 'AzureAdJoined|DomainJoined|DeviceId|MdmUrl'
# Log của Intune Management Extension
Get-Content 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log' -Tail 50
# Serial number
(Get-CimInstance Win32_BIOS).SerialNumber
macOS
# Profile MDM đã cài
sudo profiles show -type enrollment
profiles list -all | grep -i -E "mdm|intune"
# Serial number
system_profiler SPHardwareDataType | grep Serial
Kiểm tra từ Intune bằng Graph
# Danh sách máy Non-Compliance kèm last sync
Get-MgDeviceManagementManagedDevice -Filter "complianceState eq 'noncompliant'" -All |
Select-Object DeviceName, SerialNumber, OperatingSystem, UserPrincipalName, LastSyncDateTime
Những lỗi mình đã gặp
| Lỗi | Triệu chứng | Nguyên nhân gốc | Cách xử lý |
|---|---|---|---|
| Xếp nhầm máy đang dùng vào nhóm Wipe | Máy vẫn online nhưng bị đánh dấu inactive | Chỉ dùng last check-in của Intune | Lấy mốc gần nhất giữa Intune và SentinelOne |
LOOKUPVALUE báo lỗi |
Bảng DAX không load được | Serial bị trùng trong Jira hoặc S1 | Lọc trùng trước, hoặc dùng MAXX(FILTER(...)) |
| Một máy rơi vào nhiều nhóm | Báo cáo đếm sai | Thứ tự điều kiện trong SWITCH không đúng |
Sắp xếp theo mức ưu tiên: Delete trước, inactive 90 trước 30 |
| Thêm device vào group thất bại | Graph trả lỗi 404 | Dùng azureADDeviceId thay cho Object ID |
Tra Object ID bằng /devices?$filter=deviceId eq '...' |
Bảng thiếu máy Jira In Stock không có user |
Máy không hiện trong danh sách cần xử lý | Lọc ISBLANK loại luôn các dòng đó |
Giữ lại, gán Reason No Assigned User hoặc Other |
| Flow Power Automate chạy quá lâu | Timeout hoặc throttling | Một flow lớn xử lý tất cả | Tách thành nhiều flow nhỏ theo Action, thêm delay giữa các request Graph |
Kết luận
Danh sách Non-Compliance trong Intune chỉ là điểm bắt đầu. Khi gộp thêm SentinelOne, Jira Asset và AD, bạn biết được máy nào cần xóa, máy nào cần wipe, và máy nào chỉ cần nhắc user. Hãy bắt đầu bằng bảng phân loại trong Power BI và để nó chạy vài tuần ở chế độ chỉ báo cáo, trước khi bật Delete, Wipe và chặn truy cập.
Bạn đang xử lý thiết bị Non-Compliance theo cách nào: chặn bằng Conditional Access, gỡ chứng chỉ, hay dọn dẹp định kỳ bằng tay?
Bình luận