共用方式為


教學課程:使用來自 Microsoft Entra 的數據,在 Azure 數據總管中自定義報表

在本教學課程中,您將瞭解如何使用來自 Microsoft Entra ID 和 Microsoft Entra ID Governance Services 的數據,在 Azure 數據總管 (ADX) 中建立自定義報表。 這個教學課程補充了其他報告選擇,例如使用 Azure Monitor 和特權管理封存 & 報告,其主要關注點在於將稽核日誌匯出至 Azure Monitor 以便保存和分析。 相較之下,將Microsoft Entra ID 數據匯出至 Azure 數據總管,可讓您彈性地在 Microsoft Entra 物件上建立自定義報表,包括歷程記錄和已刪除的物件。 此外,使用 Azure 數據總管可擴大資料匯聚能力以納入其他來源的數據,並具備大規模擴展能力、彈性架構和資料保留策略。 當您需要保留存取數據多年、執行臨機操作調查,或需要對使用者存取數據執行自定義查詢時,Azure 數據總管特別有用。

本文說明如何顯示從 Microsoft Entra 導出的設定、使用者和訪問許可權,以及從其他來源匯出的數據,例如在其自己的 SQL 資料庫中具有訪問許可權的應用程式。 然後,您可以使用 Azure 數據總管中的 Kusto 查詢語言 (KQL),根據組織的需求來建置自定義報表。

使用下列步驟來建立這些報告:

  1. 在 Azure 訂用帳戶中設定 Azure 數據總管,或建立免費的叢集。
  2. 使用 PowerShell 腳本和 Microsoft Graph 從 Microsoft Entra ID 擷取數據。
  3. 建立數據表,並將該資料從 Microsoft Entra ID 匯入 Azure 資料總管
  4. 從 Microsoft Entra ID Governance擷取數據。
  5. 建立數據表,並將該資料從 Microsoft Entra ID Governance 匯入至 Azure 數據總管
  6. 使用 Kusto 查詢語言來建置自訂查詢

在本教學課程結束時,您將能夠創建使用者訪問權限和許可權的自訂檢視。 這些檢視會使用Microsoft支援的工具跨越不同的應用程式。 您也可以從第三方資料庫或應用程式帶入數據,以報告這些數據。

必要條件

如果您不熟悉 Azure 數據總管,並想要瞭解本文所示的案例,您可以取得 免費的 Azure 數據總管叢集。 為了在生產環境中使用搭配服務等級協定的 Azure 數據總管,您需要 Azure 訂閱來託管完整的 Azure 數據總管叢集。

判斷您想要在報告中包括的資料。 本文中的腳本提供來自 Microsoft Entra 的使用者、群組和應用程式的特定數據範例。 這些範例旨在說明您可以使用此方式產生的報告類型,但您的特定報告需求可能會不同,而且需要不同或其他資料。 您可以從這些對象開始,並在一段時間內引進更多種類的Microsoft Entra 物件。

  • 本文說明如何以已登入的使用者身分,從 Microsoft Entra 擷取數據。 若要這樣做,請確定您有必要的角色指派,以便從 Microsoft Entra 取得資料。 您需要具有適當許可權的角色,才能匯出您想要處理之Microsoft Entra 數據類型。
    • 使用者資料:全域系統管理員、特殊權限角色系統管理員、使用者系統管理員
    • 群組資料:全域系統管理員、特殊權限角色系統管理員、群組系統管理員
    • 應用程式角色指派:全域系統管理員、特殊權限角色系統管理員、應用程式系統管理員、雲端應用程式系統管理員
  • Microsoft Graph PowerShell 必須同意允許透過 Microsoft Graph 擷取 Microsoft Entra 物件。 本教學課程中的範例需要委派的 User.Read.All、Group.Read.All、Application.Read.All 和 Directory.Read.All 許可權。 如果您打算在沒有登入用戶的情況下使用自動化來擷取數據,請改為同意對應的應用程式許可權。 如需其他資訊,請參閱 Microsoft Graph 權限參考。 如果您尚未同意 Microsoft Graph PowerShell 的這些許可權,您需要是全域管理員,才能執行此同意作業。
  • 本教學課程不會說明自定義安全性屬性。 根據預設,全域管理員和其他系統管理角色,不具備從 Microsoft Entra 使用者讀取自訂安全性屬性的權限。 如果您打算擷取自定義安全性屬性,可能需要更多角色和許可權。
  • 在安裝 Microsoft Graph PowerShell 的電腦上,請確定您具有文件系統目錄的寫入許可權。 這是您安裝必要的 Microsoft Graph PowerShell 模組,以及匯出Microsoft Entra 數據的儲存位置。
  • 如果您想要將該數據併入 Azure 數據總管,請確定您有權從 Microsoft Entra 以外的其他數據源擷取數據。

1:設定 Azure 數據總管

如果您先前尚未使用 Azure 數據總管,您必須先設定此專案。 您可以建立 免費叢集,而不需要 Azure 訂用帳戶或信用卡 或需要 Azure 訂用帳戶的完整叢集。 請參閱快速入門:建立 Azure 資料總管叢集和資料庫,以開始使用。

2:使用 PowerShell 擷取 Microsoft Entra ID 識別碼數據

在本節中,您會 安裝 Microsoft Graph PowerShell 模組,並在 PowerShell 中 連線到 Microsoft Graph,以擷取Microsoft Entra ID 數據。

貴組織第一次針對此案例使用這些模組時,您必須具備全域管理員角色,才能允許 Microsoft Graph PowerShell 授與同意,以在租使用者中使用。 後續的互動可以使用低權限的角色。

  1. 開啟 PowerShell。
  2. 如果您尚未安裝所有 Microsoft Graph PowerShell 模組,請安裝必要的Microsoft Graph 模組。 本教學課程的本節需要下列課程模組:Microsoft.Graph.AuthenticationMicrosoft.Graph.UsersMicrosoft.Graph.GroupsMicrosoft.Graph.ApplicationsMicrosoft.Graph.DirectoryObjects。 如果您已安裝這些模組,請繼續進行下一個步驟。
   $modules = @('Microsoft.Graph.Users', 'Microsoft.Graph.Groups', 'Microsoft.Graph.Applications', 'Microsoft.Graph.DirectoryObjects') 
   foreach ($module in $modules) { 
   Install-Module -Name $module -Scope CurrentUser -AllowClobber -Force
   } 
  1. 將模組匯入目前的 PowerShell 工作階段。
  $modules = @('Microsoft.Graph.Users', 'Microsoft.Graph.Groups', 'Microsoft.Graph.Applications', 'Microsoft.Graph.DirectoryObjects') 
  foreach ($module in $modules) { 
  Import-Module -Name $module 
  } 
  1. 聯機至 Microsoft Graph。 本教學課程的本節說明讀取使用者、群組和應用程式,因此需要 User.Read.AllGroup.Read.AllApplication.Read.AllDirectory.Read.All 許可權範圍。 如需權限的詳細資訊,請參閱 Microsoft Graph 許可權參考
  Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "Application.Read.All", "Directory.Read.All" -ContextScope Process -NoWelcome

此命令會提示您使用 Microsoft Entra 認證登入。 登入之後,如果您是第一次連線,或需要新的許可權,您可能需要同意所需的許可權。

PowerShell 查詢以擷取在 Azure Data Explorer 中建置自訂報表所需的 Microsoft Entra ID 資料

下列查詢會使用 PowerShell 從 Microsoft Graph 擷取 Microsoft Entra ID 數據,將這些數據匯出為 JSON 檔案,然後在後續的第 3 節中匯入到 Azure 數據總管中。 產生具有這類數據的報告可能有多個案例,包括:

  • 稽核者所想要看到的報告是列出 10 個依成員部門所組織的群組的群組成員。
  • 稽核者想要查看已在兩個日期之間存取應用程式的所有使用者的報告。

您也可以將數據從Microsoft Entra 以外的其他來源帶入 Azure 數據總管。 這使得能夠實現下列情境:

  • 系統管理員想要檢視從 Microsoft Entra ID 新增至應用程式的所有使用者,以及其在應用程式本身存放庫中的訪問許可權,例如 SQL 資料庫。

這些類型的報表不會內建至Microsoft Entra ID。 不過,您可以從 Entra 擷取數據,並在 Azure 數據總管中使用自定義查詢加以結合,以自行建立這些報表。 稍後會在本教學課程的 從其他來源引進數據 一節中解決。

在本教學課程中,我們會從數個區域擷取Microsoft Entra ID 數據:

  • 使用者資訊,例如顯示名稱、UPN 和工作詳細資料
  • 群組資訊,包括其成員資格
  • 應用程式和應用程式角色指派

此數據集可讓我們針對誰獲得應用程式存取權、應用程式角色資訊和相關聯的時間範圍,執行一組廣泛的查詢。 請注意,這些是範例查詢,而您的資料和特定需求可能會與此處所顯示的內容不同。

注意

較大的租戶可能會遇到由 Microsoft Graph 模組所處理的節流或 429 錯誤。 Azure 數據總管也可能限制檔案上傳大小。

在這些 PowerShell 腳本中,我們會將選取的屬性從 Microsoft Entra 物件導出至 JSON 檔案。 來自這些匯出屬性的數據可用來在 Azure 數據總管中產生自定義報告。 下列特定屬性包含在這些範例中,因為我們使用此數據來說明您可以在 Azure 數據總管中建立的報表類型。 由於您的特定報告需求可能會因所顯示的內容而有所不同,因此您應該在這些腳本中包含您想要檢視報表的特定屬性。 不過,您可以遵循顯示的相同模式來協助建置腳本。

選取快照日期

我們包含硬式編碼 快照集日期,可識別 JSON 檔案中具有特定日期的數據,並允許我們在 Azure 數據總管中追蹤類似的數據集。 快照集日期也有助於比較兩個快照日期之間的資料變更。

$SnapshotDate = Get-Date -AsUTC -Format "yyyy-MM-dd"

取得 Entra 使用者資料

此腳本會將選取的屬性從 Entra 使用者對象匯出至 JSON 檔案。 在本教學課程的後續一節中,我們會在本教學課程的後續 一節中,將其他 JSON 檔案和其他數據匯入 Azure 數據總管

  function Export-EntraUsersToJson { 

  # Define a hash table for property mappings 
   $propertyMappings = @{ 
    "Id" = "ObjectID" 
    "DisplayName" = "DisplayName" 
    "UserPrincipalName" = "UserPrincipalName" 
    "EmployeeId" = "EmployeeId" 
    "UserType" = "UserType" 
    "CreatedDateTime" = "CreatedDateTime" 
    "JobTitle" = "JobTitle" 
    "Department" = "Department" 
    "AccountEnabled" = "AccountEnabled" 

   # Add custom properties as needed 
    "custom_extension" = "CustomExtension" 
   } 
  # Retrieve users with specified properties and create custom objects directly 
   $users = Get-MgUser -Select ($propertyMappings.Keys) -All | ForEach-Object { 
      $userObject = @{} 
      foreach ($key in $propertyMappings.Keys) { 
        if ($key -eq "CreatedDateTime") { 
          # Convert date string directly to DateTime and format it 
          $date = [datetime]::Parse($_.$key) 
          $userObject[$propertyMappings[$key]] = $date.ToString("yyyy-MM-dd") 
        } else { 
          $userObject[$propertyMappings[$key]] = $_.$key 
        } 
      } 
      # Additional properties or transformations 
      $userObject["SnapshotDate"] = $SnapshotDate
      [pscustomobject]$userObject 
    } 
    # Convert the user data to JSON and save it to a file 
    $users | ConvertTo-Json -Depth 2 | Set-Content ".\EntraUsers.json" 
  } 
  # Execute the function 
  Export-EntraUsersToJson 

取得群組資料

產生 JSON 檔案,其中包含用來在 Azure 數據總管中建立自定義檢視的組名和標識碼。 此範例包含所有群組,但如有需要,可以包含其他篩選。 若要篩選成只包括特定群組,則可能會想要在指令碼中包括邏輯來檢查巢狀群組。

    # Get all groups and select Id and DisplayName 
    $groups = Get-MgGroup -All | Select-Object Id,DisplayName 
    # Export the groups to a JSON file 
    $groups | ConvertTo-Json | Set-Content ".\EntraGroups.json" 

取得群組成員資格資料

產生具有群組成員資格的 JSON 檔案,用來在 Azure 數據總管中建立自定義檢視。 此範例包含所有群組,但如有需要,可以包含其他篩選。

    # Retrieve all groups from Microsoft Entra (Azure AD) 
    $groups = Get-MgGroup -All 
    # Initialize an array to store results 
    $results = @() 
    # Iterate over each group 
    foreach ($group in $groups) { 
      # Extract the group ID 
      $groupId = $group.Id 
      # Get members of the current group and select their IDs 
      $members = Get-MgGroupMember -GroupId $groupId | Select-Object -ExpandProperty Id 
      # Add a custom object with group ID and member IDs to the results array 
      $results += [PSCustomObject]@{ 
        GroupId = $groupId 
        Members = $members 
        SnapshotDate = $SnapshotDate
      } 
      # Pause for a short time to avoid rate limits 
      Start-Sleep -Milliseconds 200 
    } 
    # Convert the results array to JSON format and save it to a file 
    $results | ConvertTo-Json | Set-Content "EntraGroupMembership.json" 

取得應用程式和服務主體資料

產生 JSON 檔案,而此檔案包含租用戶中的所有應用程式和對應的服務主體。 我們將在本教學課程的後續一節 將這項數據匯入 Azure 數據總管,以讓我們根據此數據產生與應用程式相關的自定義報告。

    # Fetch applications and their corresponding service principals, then export to JSON 
    Get-MgApplication -All | ForEach-Object { 
      $app = $_ 
      $sp = Get-MgServicePrincipal -Filter "appId eq '$($app.AppId)'" 
      [pscustomobject]@{ 
        Name        = $app.DisplayName 
        ApplicationId   = $app.AppId 
        ServicePrincipalId = $sp.Id 
        SnapshotDate = $SnapshotDate
      } 
    } | ConvertTo-Json -Depth 10 | Set-Content "Applications.json" 

取得 AppRole 資料

Microsoft Entra 中,為企業應用程式產生所有 appRoles 的 JSON 檔案。 匯入 Azure 資料總管之後,我們會使用此數據來產生涉及使用者應用程式角色指派的報告。

    # Get a list of all applications, handle pagination manually if necessary 
    $apps = Get-MgApplication -All 
    # Loop through each application to gather the desired information 
    $results = foreach ($app in $apps) { 
      # Get the service principal for the application using its appId 
      $spFilter = "appId eq '$($app.AppId)'" 
      $sp = Get-MgServicePrincipal -Filter $spFilter | Select-Object -First 1 
      # Process AppRoles, if any, for the application 
      $appRoles = if ($app.AppRoles) { 
        $app.AppRoles | Where-Object { $_.AllowedMemberTypes -contains "User" } | 
        Select-Object Id, Value, DisplayName 
      } 
      # Construct a custom object with application and service principal details 
      [PSCustomObject]@{ 
        ApplicationId    = $app.AppId 
        DisplayName     = $app.DisplayName 
        ServicePrincipalId = $sp.Id 
        AppRoles      = $appRoles 
        SnapshotDate = $SnapshotDate
      } 
    } 
    # Export the results to a JSON file 
    $results | ConvertTo-Json -Depth 4 | Out-File 'AppRoles.json' 

取得 AppRole 指派資料

產生包含租戶中使用者所有應用程式角色指派的 JSON 檔案。

    $users = Get-MgUser -All 
    $result = @() 
    foreach ($user in $users) { 
      Get-MgUserAppRoleAssignment -UserId $user.Id | ForEach-Object { 
        # Use the same date formatting approach 
        $createdDateTime = $_.CreatedDateTime -replace "\\/Date\((\d+)\)\\/", '$1' 
        # Convert the milliseconds timestamp to a readable date format if needed 
        $result += [PSCustomObject]@{ 
          AppRoleId      = $_.AppRoleId 
          CreatedDateTime   = $createdDateTime 
          PrincipalDisplayName = $_.PrincipalDisplayName 
          PrincipalId     = $_.PrincipalId 
          ResourceDisplayName = $_.ResourceDisplayName 
          ResourceId      = $_.ResourceId 
          SnapshotDate     = $SnapshotDate
        } 
      } 
    } 
    $result | ConvertTo-Json -Depth 10 | Out-File "AppRoleAssignments.json" 

3:建立數據表,並將 JSON 檔案與數據從 Microsoft Entra ID 匯入 Azure 數據總管

在本節中,我們會匯入新建立Microsoft Entra ID 服務的 JSON 檔案,作為 Azure 數據總管中的數據表,以供進一步分析。 在第一次使用 Azure 數據總管 Web UI 匯入時,您將根據 Web UI 從每個 JSON 檔案建議的架構來建立數據表。

在 Azure 數據總管叢集或免費叢集中設定資料庫之後,如本文第一節所述,請流覽至該資料庫。

  1. 登入 Azure 資料總管的 Web 使用者介面
  2. 從左側功能表中,選取 [查詢]

接下來,請針對每個導出的 JSON 檔案遵循這些步驟,將匯出的數據以新數據表的形式放入該 Azure 數據總管資料庫。

  1. 以滑鼠右鍵點選您要匯入資料的資料庫名稱。 選取 [取得資料

    查詢選項卡的螢幕截圖,右鍵點擊資料庫,並開啟 [取得選項] 對話框。

  2. 從可用的清單中選取資料來源。 在本教學課程中,您會從 本機檔案匯入資料

  3. 選取 [+ 新增資料表],然後根據您要匯入的 JSON 檔名輸入資料表名稱,例如,如果您要匯入 EntraUsers.json,請將數據表命名 EntraUsers。 第一次匯入之後,數據表已經存在,而且您可以選取該數據表做為後續匯入的目標數據表。

  4. 選取 [瀏覽檔案],選取 JSON 檔案,然後選取 [下一步]。

  5. Azure 數據總管會自動偵測架構,並在 [檢查] 索引標籤中提供預覽。選取 [完成] 以建立數據表並從該檔案匯入數據。 擷取資料之後,請按 [關閉]

  6. 針對您在上一節中產生的每個 JSON 檔案重複上述每個步驟。

在完成這些步驟後,資料庫中將會有表格 EntraUsersEntraGroupsEntraGroupMembershipApplicationsAppRolesAppRoleAssignments

4:使用 PowerShell 擷取 Microsoft Entra ID 治理資料

在本節中,您將使用 PowerShell 從 Microsoft Entra ID Governance Services 擷取數據。 如果您沒有 Microsoft Entra ID Governance、Microsoft Entra ID P2 或 Microsoft Entra Suite,請繼續在第 節使用 Azure 資料瀏覽器來建立自訂報表

為此,您可能需要 安裝 Microsoft Graph PowerShell 模組,以 提取 Microsoft Entra ID Governance 數據。 貴組織第一次針對此案例使用這些模組時,您必須具備全域管理員角色,才能允許 Microsoft Graph PowerShell 授與同意,以在租使用者中使用。 後續的互動可以使用低權限的角色。

  1. 開啟 PowerShell。
  2. 如果您尚未安裝所有 Microsoft Graph PowerShell 模組,請安裝必要的Microsoft Graph 模組。 本教學課程的本節需要下列模組:Microsoft.Graph.Identity.Governance。 如果您已安裝這些模組,請繼續進行下一個步驟。
   $modules = @('Microsoft.Graph.Identity.Governance')
   foreach ($module in $modules) {
   Install-Module -Name $module -Scope CurrentUser -AllowClobber -Force
   }
  1. 將模組匯入目前的 PowerShell 工作階段。
  $modules = @('Microsoft.Graph.Identity.Governance')
  foreach ($module in $modules) {
  Import-Module -Name $module
  } 
  1. 聯機至 Microsoft Graph。 本教學課程的這一節說明如何從權利管理和存取權檢閱擷取數據,因此需要 AccessReview.Read.AllEntitlementManagement.Read.All 許可權範圍。 針對其他報告使用案例,例如 PIM 或生命週期工作流程,請使用必要的許可權更新 Scopes 參數。 如需權限的詳細資訊,請參閱 Microsoft Graph 許可權參考
  Connect-MgGraph -Scopes "AccessReview.Read.All, EntitlementManagement.Read.All" -ContextScope Process -NoWelcome

此命令會提示您使用 Microsoft Entra 認證登入。 登入之後,如果您是第一次連線,或需要新的許可權,您可能需要同意所需的許可權。

PowerShell 查詢,用於擷取在 Azure 資料探勘器中建立自訂報表所需的 Microsoft Entra ID 治理數據。

您可以使用查詢,使用 PowerShell 從 Microsoft Graph 擷取Microsoft Entra ID Governance 數據,並將數據匯出至 JSON 檔案,這些檔案會匯入後續區段中的 Azure 數據總管。 產生具有這類數據的報告可能有多個案例,包括:

  • 關於歷史存取權檢閱的報告
  • 透過授權管理系統報告任務指派情况

取得存取權檢閱排程定義數據

使用存取權檢閱定義名稱和標識符來產生 JSON 檔案,以用來在 Azure 數據總管中建立自定義檢視。 此範例包含所有存取權檢閱,但如有需要,可以包含其他篩選。 如需了解詳情,請參閱 ,使用篩選查詢參數

   $allsched = Get-MgIdentityGovernanceAccessReviewDefinition -All
   $definitions = @()
   # Iterate over each definition
   foreach ($definition in $allsched) {
      $definitions += [PSCustomObject]@{
         Id = $definition.Id
         DisplayName = $definition.DisplayName
         SnapshotDate = $SnapshotDate
      }
   }
   $definitions | ConvertTo-Json -Depth 10 | Set-Content "EntraAccessReviewDefinitions.json"

取得存取權檢閱實例數據

若要使用 PowerShell 將所有 Access Review 定義、實例和決策導出成結構化資料夾格式,您可以使用 Microsoft Graph API。 此方法可確保您的數據會以階層方式組織,並配合指定的資料夾結構。

在開始之前,請注意下列事項:

  • 請確定您有 Microsoft Graph 的存取審核數據所需的權限。
  • 視數據量而定,腳本的運行時間可能會有所不同。 視需要監視程式並調整參數。
  1. 下載 Export_Access_Reviews.ps1 文稿,並將其儲存在本機。
  2. 在檔案總管中,解除封鎖腳本,以便在PowerShell中執行。
  3. 執行下列命令,此命令會將所有資料輸出到三個子資料夾中,ReviewInstancesReviewInstanceDecisionItemsReviewInstanceContactedReviewers
 .\ExportAccessReviews.ps1 -InstanceStartDate "11/15/2024" -InstanceEndDate "12/15/2024" -ExportFolder "C:\AccessReviewsExport\11_15_to_12_15" 

取得權利管理存取套件資料

產生 JSON 檔案,其中包含用來在 Azure 數據總管中建立自定義檢視的存取套件名稱和標識符。 此範例包含所有存取套件,但如有需要,可以包含其他篩選。

   $accesspackages1 = Get-MgEntitlementManagementAccessPackage -All
   $accesspackages2 = @()
   # Iterate over each access package
   foreach ($accesspackage in $accesspackages1) {
      $accesspackages2 += [PSCustomObject]@{
         Id = $accesspackage.Id
         DisplayName = $accesspackage.DisplayName
         SnapshotDate = $SnapshotDate
      }
   }
   $accesspackages2 | ConvertTo-Json -Depth 10 | Set-Content "EntraAccessPackages.json"

取得權利管理存取套件指派資訊

產生具有指派的 JSON 檔案,以存取用來在 Azure 數據總管中建立自定義檢視的套件。 此範例包含已交付的所有任務,但如有需要可以進一步進行篩選。

   $apassignments1 = Get-MgEntitlementManagementAssignment -ExpandProperty target,accessPackage -filter "state eq 'Delivered'" -all
   $apassignments2 = @()
   # Iterate over each access package assignment
   foreach ($assignment in $apassignments1) {
      $apassignments2 += [PSCustomObject]@{
         Id = $assignment.Id
         ScheduleStartDateTime = $assignment.Schedule.StartDateTime -replace "\\/Date\((\d+)\)\\/", '$1' 
         AccessPackageId = $assignment.AccessPackage.Id
         AccessPackageDisplayName = $assignment.AccessPackage.DisplayName
         TargetId = $assignment.Target.Id
         TargetDisplayName = $assignment.Target.DisplayName
         TargetEmail = $assignment.Target.Email
         TargetObjectId = $assignment.Target.ObjectId
         TargetPrincipalName = $assignment.Target.PrincipalName
         TargetSubjectType = $assignment.Target.SubjectType
         SnapshotDate = $SnapshotDate
      }
   }
   $apassignments2 | ConvertTo-Json -Depth 10 | Set-Content "EntraAccessPackageAssignments.json"

5:建立數據表,並將 JSON 檔案與數據從 Microsoft Entra ID Governance 匯入 Azure 數據總管

在本節中,我們會將新建立Microsoft Entra ID Governance 服務的 JSON 檔案匯入 Azure 數據總管,以及已針對 Microsoft Entra ID 服務匯入的數據,以進行進一步分析。 在第一次使用 Azure 數據總管 Web UI 匯入時,您將根據 Web UI 從每個 JSON 檔案建議的架構來建立數據表。

在 Azure 數據總管叢集或免費叢集中,流覽至保存您Microsoft Entra ID 數據的資料庫。

  1. 登入 Azure 資料總管的 Web 使用者介面
  2. 從左側功能表中,選取 [查詢]

接下來,請遵循上一節中每個匯出 JSON 檔案的這些步驟,將匯出的數據以新數據表的形式匯出至該 Azure 數據總管資料庫。

  1. 以滑鼠右鍵點選您要匯入資料的資料庫名稱。 選取 [取得資料

    查詢選項卡的螢幕截圖,右鍵點擊資料庫,並開啟 [取得選項] 對話框。

  2. 從可用的清單中選取資料來源。 在本教學課程中,您會從 本機檔案匯入資料

  3. 選取 [+ 新增數據表 並輸入數據表名稱,根據您要匯入的 JSON 檔案名稱,第一次匯入之後,數據表已經存在,而且您可以選取它作為後續匯入的目標數據表。

  4. 選取 [瀏覽檔案],選取 JSON 檔案,然後選取 [下一步]。

  5. Azure 數據總管會自動偵測架構,並在 [檢查] 索引標籤中提供預覽。選取 [完成] 以建立數據表並從該檔案匯入數據。 擷取資料之後,請按 [關閉]

  6. 針對您在上一節中產生的每個 JSON 檔案,針對每個資料夾重複上述每個步驟。

  7. 如果資料夾中有許多檔案,則您可以在建立資料表之後,使用 lightingest 來匯入其餘檔案。

在這些步驟結束時,除了第 3 節中所建立的數據表之外,您還會有數據表 EntraAccessReviewDefinitionsEntraAccessPackagesEntraAccessPackageAssignmentsReviewInstancesReviewInstanceDecisionItemsReviewInstanceContactedReviewers

6:使用 Azure 數據總管建置自定義報告

有了 Azure 數據總管中現在可用的數據,您就可以根據業務需求開始建立自定義報表。

Azure 資料總管是一種功能強大的資料分析工具,可進行高度調整且具有彈性,以提供產生自訂使用者存取報告的理想環境。 Azure 資料瀏覽器使用 Kusto 查詢語言(KQL)。

  1. 登入 Azure 資料總管的 Web 使用者介面
  2. 從左側功能表中,選取 [查詢]

下列查詢提供一般報告的範例,但您可以自訂這些報告以符合您的需求,並建立其他報告。

您也可以在 Excel 檢視報表,方法是選取 [匯出] 標籤,然後選取 [在 Excel 中開啟]。

範例 1:針對特定快照集日期產生直接和群組指派的應用程式角色指派

此報告提供誰具有目標應用程式的什麼存取權和存取時間的檢視,並可用於安全性稽核、合規性驗證,以及瞭解組織內的存取模式。

此查詢針對 Microsoft Entra AD 中的特定應用程式,並分析截至特定日期的角色指派情況。 此查詢會同時擷取直接和群組型角色指派,並將此數據與 EntraUsers 數據表的使用者詳細數據合併,以及來自 AppRoles 數據表的角色資訊。 在下列查詢中,將 targetSnapshotDate 設定為載入數據時所使用的 snapshotDate 值。

/// Define constants 
let targetServicePrincipalId = "<your service principal-id>"; // Target Service Principal ID 
let targetSnapshotDate = datetime("2024-01-13"); // Target Snapshot Date for the data 

// Extract role assignments for the target Service Principal and Snapshot Date 
let roleAssignments = AppRoleAssignments 
    | where ResourceId == targetServicePrincipalId and startofday(SnapshotDate) == targetSnapshotDate 
    | extend AppRoleIdStr = tostring(AppRoleId); // Convert AppRoleId to string for easier comparison 

// Prepare user data from EntraUsers table 
let users = EntraUsers 
    | project ObjectID, UserPrincipalName, DisplayName, ObjectIDStr = tostring(ObjectID); // Include ObjectID as string for joining 

// Prepare role data from AppRoles table 
let roles = AppRoles 
    | mvexpand AppRoles // Expand AppRoles to handle multiple roles 
    | extend RoleName = AppRoles.DisplayName, RoleId = tostring(AppRoles.Id) // Extract Role Name and ID 
    | project RoleId, RoleName; 
// Process direct assignments 
let directAssignments = roleAssignments 
    | join kind=inner users on $left.PrincipalId == $right.ObjectID // Join with EntraUsers on PrincipalId 
    | join kind=inner roles on $left.AppRoleIdStr == $right.RoleId // Join with roles to get Role Names 
    | project UserPrincipalName, DisplayName, CreatedDateTime, RoleName, AssignmentType = "Direct", SnapshotDate; 

// Process group-based assignments 

let groupAssignments = roleAssignments 
    | join kind=inner EntraGroupMembership on $left.PrincipalId == $right.GroupId // Join with Group Membership 
    | mvexpand Members // Expand group members 
    | extend MembersStr = tostring(Members) // Convert member ID to string 
    | distinct MembersStr, CreatedDateTime, AppRoleIdStr, SnapshotDate // Get distinct values 
    | join kind=inner users on $left.MembersStr == $right.ObjectIDStr // Join with EntraUsers for user details 
    | join kind=inner roles on $left.AppRoleIdStr == $right.RoleId // Join with roles for role names 
    | project UserPrincipalName, DisplayName, CreatedDateTime, RoleName, AssignmentType = "Group", SnapshotDate; 

// Combine results from direct and group-based assignments 
directAssignments 
| union groupAssignments 

範例 2:使用 Entra 資料來建置基本稽核者報告,其中顯示誰可在這兩個日期之間存取應用程式

此報告提供誰在兩個日期之間對目標應用程式進行過什麼存取和存取時間的檢視,並可用於安全性稽核、合規性驗證,以及瞭解組織內的存取模式。

此查詢會以 Microsoft Entra ID 內的特定應用程式為目標,並分析兩個日期之間的角色指派。 查詢會從 AppRoleAssignments 數據表擷取直接角色指派,並將此數據與 EntraUsers 數據表的使用者詳細數據合併,以及 AppRoles 數據表中的角色資訊。

// Set the date range and service principal ID for the query 
let startDate = datetime('2024-01-01'); 
let endDate = datetime('2024-03-14'); 
let servicePrincipalId = "<your service principal-id>"; 

// Query AppRoleAssignments for the specified service principal within the date range 
AppRoleAssignments 
| where ResourceId == servicePrincipalId and 
    todatetime(CreatedDateTime) between (startDate .. endDate) 

// Extend AppRoleId to a string for joining 
| extend AppRoleIdStr = tostring(AppRoleId) 

// Project the necessary fields for the join with EntraUsers and AppRoles 
| project PrincipalId, AppRoleIdStr, CreatedDateTime 

// Join with EntraUsers to get user details 
| join kind=inner (EntraUsers | project UserPrincipalName, DisplayName, ObjectID) on $left.PrincipalId == $right.ObjectID 

// Join with AppRoles to get the role display names 
| join kind=inner ( 
  AppRoles | mvexpand AppRoles | project RoleIdStr = tostring(AppRoles.Id), RoleDisplayName = tostring(AppRoles.DisplayName) 
) on $left.AppRoleIdStr == $right.RoleIdStr 

// Final projection of the report with the current date and time 
| project UserPrincipalName, DisplayName, RoleDisplayName, CreatedDateTime, ReportDate = now() 

範例 3:在兩個資料快照集日期之間,將使用者新增至應用程式

這些報告所提供的檢視是有關哪些使用者獲給定目標應用程式在兩個日期之間的應用程式角色指派。 這些報告可以用來追蹤一段時間的應用程式存取變更。

此查詢會以 Microsoft Entra ID 內的特定應用程式為目標,並變更為開始與結束日期之間的角色指派。

// Define the date range and service principal ID for the query 

let startDate = datetime("2024-03-01"); 
let endDate = datetime("2024-03-14"); 
let servicePrincipalId = "<your service principal-id>"; 
let earlierDate = startDate; // Update this to your specific earlier date 

AppRoleAssignments 
| where SnapshotDate < endDate and ResourceId == servicePrincipalId
| project PrincipalId, AppRoleId2 = tostring(AppRoleId), CreatedDateTime 
| join kind=anti ( 
    AppRoleAssignments 
    | where SnapshotDate < earlierDate and ResourceId == servicePrincipalId 
    | project PrincipalId, AppRoleId1 = tostring(AppRoleId) 
) on PrincipalId 
| join kind=inner (EntraUsers) on $left.PrincipalId == $right.ObjectID 
| join kind=inner (AppRoles 
                   | mvexpand AppRoles 
                   | project AppRoleId=tostring(AppRoles.Id), RoleDisplayName=tostring(AppRoles.DisplayName) 
                  ) on $left.AppRoleId2 == $right.AppRoleId 
| project UserPrincipalName, DisplayName, RoleDisplayName, CreatedDateTime, PrincipalId, Change = "Added" 

範例 4:存取權檢閱

審核完成 & 時程資訊

上傳數據之後,請使用下列 Kusto 查詢來檢閱它。

  • 上次存取權檢閱週期何時完成? 需要多久時間?
ReviewInstances 
| summarize LastCompletedDate = max(ReviewInstanceEndDateTime),  
            ReviewDuration = datetime_diff('minute', max(ReviewInstanceEndDateTime), min(ReviewInstanceStartDateTime))  
  • 存取權檢閱程式是否準時進行(例如每季、每年)?
ReviewInstances 
| extend ExpectedFrequency = "Quarterly" // Replace with organization's frequency 
| summarize ReviewsCompleted = count(), LastReviewEndDate = max(ReviewInstanceEndDateTime) 
| extend CurrentDate = now(),  
         TimeSinceLastReview = datetime_diff('day', now(), LastReviewEndDate) 
| extend IsOnSchedule = iff(TimeSinceLastReview <= 90, "Yes", "No") // Assuming quarterly = 90 days  

審查參與度 & 互動

  • 被指派的檢閱者是誰?
ReviewInstanceContactedReviewers 
| project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerName = DisplayName, ReviewerUserPrincipalName = UserPrincipalName, CreatedDateTime  
  • 哪些檢閱者積极參與並提供回應?
ReviewInstanceDecisionItems 
| where ReviewedBy_DisplayName != "AAD Access Reviews" 
| where Decision in ("Approve", "Deny") 
| project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerName = ReviewedBy_DisplayName, 
ReviewerUserPrincipalName = ReviewedBy_UserPrincipalName, Decision, ReviewedDateTime 
| distinct AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerName, ReviewerUserPrincipalName, Decision   
  • 回應存取權檢閱要求的檢閱者百分比。
let TotalReviewers = ReviewInstanceContactedReviewers 
    | summarize Total = dcount(Id) by AccessReviewDefinitionId, AccessReviewInstanceId;  

let RespondedReviewers = ReviewInstanceDecisionItems 
    | where ReviewedBy_DisplayName != "AAD Access Reviews" 
    | where ReviewedBy_Id != "00000000-0000-0000-0000-000000000000" 
    | where Decision in ("Approve", "Deny") 
    | summarize Responded = dcount(ReviewedBy_Id) by AccessReviewDefinitionId, AccessReviewInstanceId;  

TotalReviewers 
| join kind=leftouter RespondedReviewers on AccessReviewDefinitionId, AccessReviewInstanceId 
| extend Responded = coalesce(Responded, 0)  // Replace null with 0 for Responded 
| extend NotResponded = Total - Responded   // Calculate the number of non-responders 
| extend ResponsePercentage = (Responded * 100.0) / Total  // Percentage of those who responded 
| extend NonResponsePercentage = (NotResponded * 100.0) / Total  // Percentage of those who didn’t respond 
| project AccessReviewDefinitionId, AccessReviewInstanceId, Total, Responded, ResponsePercentage, NotResponded, NonResponsePercentage  
  • 每個檢閱者何時完成其工作?
ReviewInstanceDecisionItems 
| where Decision in ("Approve", "Deny") 
| project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerName = ReviewedBy_DisplayName, ReviewerUserPrincipalName = ReviewedBy_UserPrincipalName, ReviewedDateTime  
  • 哪些檢閱者沒有做出任何決定?
let AllReviewers = ReviewInstanceContactedReviewers 
    | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerId = Id, ReviewerUserPrincipalName = UserPrincipalName, ReviewerName = DisplayName; 
  
let ActiveReviewers = ReviewInstanceDecisionItems 
    | where Decision in ("Approve", "Deny") 
    | where ReviewedBy_DisplayName != "AAD Access Reviews" 
    | where ReviewedBy_Id != "00000000-0000-0000-0000-000000000000" 
    | summarize ActiveReviewers = make_set(ReviewedBy_Id) by AccessReviewDefinitionId, AccessReviewInstanceId; 

AllReviewers 
| extend ReviewerId = tostring(ReviewerId)  // Ensure ReviewerId is a string 
| join kind=leftanti ( 
    ActiveReviewers 
    | mv-expand ActiveReviewers 
    | extend ActiveReviewers = tostring(ActiveReviewers)  // Cast ActiveReviewers to a string 
) on $left.ReviewerId == $right.ActiveReviewers 
| project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerUserPrincipalName, ReviewerName 
  • 未互動的檢閱者百分比。
let TotalReviewers = ReviewInstanceContactedReviewers 
    | summarize Total = dcount(Id) by AccessReviewDefinitionId, AccessReviewInstanceId; 

let RespondedReviewers = ReviewInstanceDecisionItems 
    | where ReviewedBy_DisplayName != "AAD Access Reviews" 
    | where ReviewedBy_Id != "00000000-0000-0000-0000-000000000000" 
    | where Decision in ("Approve", "Deny") 
    | summarize Responded = dcount(ReviewedBy_Id) by AccessReviewDefinitionId, AccessReviewInstanceId; 
  
TotalReviewers 
| join kind=leftouter RespondedReviewers on AccessReviewDefinitionId, AccessReviewInstanceId 
| extend Responded = coalesce(Responded, 0)  // Replace null with 0 for Responded 
| extend NotResponded = Total - Responded   // Calculate the number of non-responders 
| extend ResponsePercentage = (Responded * 100.0) / Total  // Percentage of those who responded 
| extend NonResponsePercentage = (NotResponded * 100.0) / Total  // Percentage of those who didn’t respond 
| project AccessReviewDefinitionId, AccessReviewInstanceId, Total, Responded, ResponsePercentage, NotResponded, NonResponsePercentage  
  • 針對未回應的檢閱者觸發提醒嗎? 尚待決定?
// Step 1: Get the list of all reviewers 
let TotalReviewers = ReviewInstanceContactedReviewers 
    | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerId = Id, ReviewerUserPrincipalName = UserPrincipalName, ReviewerName = DisplayName; 
 
// Step 2: Get the list of reviewers who have responded 
let RespondedReviewers = ReviewInstanceDecisionItems 
    | where ReviewedBy_DisplayName != "AAD Access Reviews" 
    | where ReviewedBy_Id != "00000000-0000-0000-0000-000000000000" 
    | where Decision in ("Approve", "Deny") 
    | project AccessReviewDefinitionId, AccessReviewInstanceId, RespondedReviewerId = ReviewedBy_Id; 

// Step 3: Get the list of review instances 
let ReviewInstancesWithDetails = ReviewInstances 
    | project AccessReviewDefinitionId = ReviewDefinitionId,  
              AccessReviewInstanceId = ReviewInstanceId,  
              RemindersSent = ReviewDefinitionSettings_ReminderNotificationsEnabled,  
              StartDate = todatetime(ReviewInstanceStartDateTime),  
              EndDate = todatetime(ReviewInstanceEndDateTime) 
    | extend 
              ReminderSentDate = iif(RemindersSent, StartDate + (EndDate - StartDate) / 2, datetime(null)); 

// Step 4: Identify non-responsive reviewers and join with review instance details 
TotalReviewers 
| join kind=leftouter (ReviewInstancesWithDetails) on AccessReviewDefinitionId, AccessReviewInstanceId 
| join kind=leftanti RespondedReviewers on $left.ReviewerId == $right.RespondedReviewerId 
| project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerUserPrincipalName, ReviewerName, RemindersSent, ReminderSentDate 

使用者 & 存取權變更

  • 誰在存取檢閱期間失去對特定資源的存取權?
ReviewInstanceDecisionItems 
| where Decision == "Deny" 
| project User = Principal_DisplayName, Resource = Resource_DisplayName, Decision, Justification 
  • 使用者是否因為不活躍而被標示?
ReviewInstanceDecisionItems 
| where Insights contains "inactive" 
| project User = Principal_DisplayName, Resource = Resource_DisplayName, Insights, Decision 
  • 存取移除日期和喪失存取權限的原因。
ReviewInstanceDecisionItems 
| where Decision == "Deny" 
| project User = Principal_DisplayName, Resource=Resource_DisplayName, AccessRemovalDate = AppliedDateTime, Reason = Justification  
  • 未做出任何決策的使用者。
ReviewInstanceDecisionItems 
| where Decision == "NotReviewed" 
| project User = Principal_DisplayName, Resource=Resource_DisplayName 
  • 沒有評論者的評論。
ReviewInstances 
| join kind=leftanti ( 
    ReviewInstanceContactedReviewers 
    | summarize by AccessReviewInstanceId 
) on $left.ReviewInstanceId == $right.AccessReviewInstanceId  
  • 沒有用戶留下評論。
ReviewInstances 
| join kind=leftanti ( 
    ReviewInstanceDecisionItems 
    | summarize by AccessReviewInstanceId 
) on $left.ReviewInstanceId == $right.AccessReviewInstanceId 

檢閱決策數據

  • 決策:已核准、拒絕或未變更。
ReviewInstanceDecisionItems 
| summarize count() by Decision 
  • 已核准或拒絕存取的用戶數目。
ReviewInstanceDecisionItems 
| summarize ApprovedCount = countif(Decision == "Approve"), DeniedCount = countif(Decision == "Deny") 
  • 已記載核准原因嗎?
ReviewInstanceDecisionItems 
| where Decision == "Approve" and isnotempty(Justification) 
| summarize count() by ReviewedBy_DisplayName 

存取權檢閱品質與合規性檢查

  • 對休眠使用者而言,存取撤銷是考慮的嗎?
ReviewInstanceDecisionItems 
| where Insights contains "inactive" and Decision == "Deny" 
| project User = Principal_DisplayName, Decision 
  • 是否有任何存取權未正確移除?
ReviewInstanceDecisionItems 
| where ApplyResult != "New" and ApplyResult != "AppliedSuccessfully" 
  • 檢閱者是否記錄其決策?
ReviewInstanceDecisionItems 
| where isnotempty(Justification) 
| summarize count() by ReviewedBy_DisplayName 
  • 是否已為每個使用者擷取批注?
ReviewInstanceDecisionItems 
| where isnotempty(Justification) 
| project User = Principal_DisplayName, Resource = Resource_DisplayName, Comments = Justification 

     

設定持續匯入

此教程說明一次性數據擷取、轉換和載入(ETL)過程,將資料一次性擷取、轉換並載入到 Azure 數據總管,以供生成報告。 為了持續報告或比較一段時間的變更,您可以自動化將 Microsoft Entra 的資料填入 Azure 數據總管的過程,以確保您的資料庫持續擁有最新的數據。

您可以使用 Azure Automation這項 Azure 雲端服務來裝載擷取 Microsoft Entra ID 和 Microsoft Entra ID Governance 數據所需的 PowerShell 腳本。 如需詳細資訊,請參閱 使用 Azure 自動化自動化 Microsoft Entra ID 治理工作

您也可以使用 Azure 功能或命令行工具,例如 lightingest 來帶入資料並填入已存在的數據表。 如需詳細資訊,請參閱 使用 LightIngest 將資料內嵌至 Azure 資料總管

例如,若要將當前目錄中的檔案 EntraAccessPackages.json 載入至 EntraAccessPackages 數據表,作為當前登入的使用者:

az login
LightIngest.exe "https://ingest-CLUSTERHOSTNAME;Fed=True" -database:"DATABASE" -table:EntraAccessPackages -sourcepath:"." -pattern:"EntraAccessPackages.json" -format:multijson -azcli:true

在 Azure 監視器中查詢數據

如果您要將稽核、登入或其他Microsoft Entra 記錄傳送至 Azure 監視器,則可以將這些記錄從該 Azure 監視器 Log Analytics 工作區納入查詢中。 如需 Azure 監視器和 Azure 數據總管關聯性的詳細資訊,請參閱使用 Azure 數據總管在 Azure 監視器中 查詢數據

  1. 登入 Microsoft Entra 系統管理中心。

  2. 選擇診斷設定。

  3. 選取您要傳送記錄的Log Analytics工作場所。

  4. 在Log Analytics工作區概觀上,記錄工作區的訂用帳戶標識碼、資源組名和工作區名稱。

  5. 登入 Azure 入口網站。

  6. 瀏覽至 Azure 資料總管 網頁使用者介面

  7. 請確定您的 Azure 數據總管叢集已列出。

  8. 選取 ,[新增],然後建立 連線

  9. [新增連線] 視窗中,輸入 Log Analytics 工作區的 URL,其格式由雲端特定的主機名稱、訂用帳戶 ID、資源組名稱和 Azure Monitor Log Analytics 工作區名稱組成,如 [新增 Log Analytics 工作區] 中所述。

  10. 建立連線之後,Log Analytics 工作區會出現在左側窗格中,其中包含原生 Azure 數據總管叢集。

  11. 從左側功能表中,選取 [查詢],然後選取您的 Azure 數據總管叢集。

  12. 在查詢窗格中,您可以在您的 Azure 數據總管查詢中參考包含 Microsoft Entra 日誌的 Azure 監視器資料表。 例如:

    let CL1 = 'https://ade.loganalytics.io/subscriptions/*subscriptionid*/resourcegroups/*resourcegroupname*/providers/microsoft.operationalinsights/workspaces/*workspacename*';
    cluster(CL1).database('*workspacename*').AuditLogs | where Category == "EntitlementManagement"  and OperationName == "Fulfill access package assignment request"
    | mv-expand TargetResources | where TargetResources.type == 'AccessPackage' | project ActivityDateTime,APID = toguid(TargetResources.id)
    | join EntraAccessPackage on $left.APID == $right.Id
    | limit 100
    

從其他來源帶入數據

您也可以 在 Azure 數據總管中建立其他資料表 以匯入來自其他來源的資料。 如果資料位於 JSON 檔案中,類似於上述範例或 CSV 檔案,則您可以在第一次 從檔案取得資料時建立資料表,。 其中一個數據表已建立,您也可以 使用 LightIngest 從 JSON 或 CSV 檔案將數據內嵌至 Azure 數據總管

如需數據擷取的詳細資訊,請參閱 Azure 數據總管數據擷取概觀

範例 5:合併 Entra 和第二個來源的應用程式指派,來建立一份報告,顯示所有使用者在兩個日期之間曾經存取應用程式的情況。

此報告說明如何結合兩個不同的系統的數據,以在 Azure 數據總管中建立自定義報表。 其會將使用者、其角色和其他屬性的相關資料從兩個系統彙總成統一格式,以進行分析或報告。

此範例假設有一個名為 salesforceAssignments 的表格,其中包含從另一個應用程式帶入數據而填入的列 UserNameNameEmployeeIdDepartmentJobTitleAppNameRoleCreatedDateTime

// Define the date range and service principal ID for the query 

let startDate = datetime("2023-06-01"); 
let endDate = datetime("2024-03-13"); 
let servicePrincipalId = "<your service principal-id>"; 

// Pre-process AppRoleAssignments with specific filters and projections 
let processedAppRoleAssignments = AppRoleAssignments 
    | where ResourceId == servicePrincipalId and todatetime(CreatedDateTime) between (startDate .. endDate) 
    | extend AppRoleId = tostring(AppRoleId) 
    | project PrincipalId, AppRoleId, CreatedDateTime, ResourceDisplayName; // Exclude DeletedDateTime and keep ResourceDisplayName 

// Pre-process AppRoles to get RoleDisplayName for each role 
let processedAppRoles = AppRoles 
    | mvexpand AppRoles 
    | project AppRoleId = tostring(AppRoles.Id), RoleDisplayName = tostring(AppRoles.DisplayName); 

// Main query: Process EntraUsers by joining with processed role assignments and roles 
EntraUsers 
    | join kind=inner processedAppRoleAssignments on $left.ObjectID == $right.PrincipalId // Join with role assignments 
    | join kind=inner processedAppRoles on $left.AppRoleId == $right.AppRoleId // Join with roles to get display names 

    // Summarize to get the latest record for each unique combination of user and role attributes 
    | summarize arg_max(AccountEnabled, *) by UserPrincipalName, DisplayName, tostring(EmployeeId), Department, JobTitle, ResourceDisplayName, RoleDisplayName, CreatedDateTime 

    // Final projection of relevant fields including source indicator and report date 
    | project UserPrincipalName, DisplayName, EmployeeId=tostring(EmployeeId), Department, JobTitle, AccountEnabled=tostring(AccountEnabled), ResourceDisplayName, RoleDisplayName, CreatedDateTime, Source="EntraUsers", ReportDate = now() 

// Union with processed salesforceAssignments to create a combined report 
| union ( 
    salesforceAssignments 

    // Project fields from salesforceAssignments to align with the EntraUsers data structure 
    | project UserPrincipalName = UserName, DisplayName = Name, EmployeeId = tostring(EmployeeId), Department, JobTitle, AccountEnabled = "N/A", ResourceDisplayName = AppName, RoleDisplayName = Role, CreatedDateTime, Source = "salesforceAssignments", ReportDate = now() 
) 

下一步