다음을 통해 공유


자습서: Microsoft Entra ID의 데이터를 사용하여 ADX(Azure Data Explorer)에서 사용자 지정된 보고서

이 자습서에서는 Microsoft Entra ID의 데이터를 사용하여 ADX(Azure Data Explorer)에서 사용자 지정된 보고서를 만드는 방법을 알아봅니다. 이 자습서에서는 더 긴 보존 및 분석을 위해 감사 로그 데이터를 내보내는 데 중점을 둔 Azure Monitor 및 권한 관리 보관 및 보고서와 같은 다른 보고 옵션을 보완합니다. 이에 비해 Microsoft Entra ID 데이터를 Azure Data Explorer로 내보내면 확장성이 뛰어나고 유연한 스키마 및 보존 정책을 사용하여 여러 원본의 데이터 집계를 허용하여 사용자 지정 보고서를 만들 수 있는 유연성이 향상됩니다.

이 보고서는 SQL 데이터베이스가 있는 애플리케이션과 같은 다른 원본에서 내보낸 데이터와 함께 Microsoft Entra에서 내보낸 구성, 사용자 및 액세스 권한을 표시하는 방법을 보여 줍니다. 그런 다음 KQL(Kusto 쿼리 언어)을 사용하여 조직의 요구 사항에 따라 사용자 지정 보고서를 작성할 수 있습니다. Azure Data Explorer 내에서 이러한 유형의 보고서를 생성하는 것은 액세스 데이터를 장기간 유지하거나, 임시 조사를 수행하거나, 사용자 액세스 데이터에 대해 사용자 지정 쿼리를 실행해야 하는 경우에 특히 유용할 수 있습니다.

다음 단계를 수행하여 이러한 보고서를 만듭니다.

  1. Azure 구독에서 Azure Data Explorer를 설정합니다.
  2. PowerShell 스크립트 및 MS Graph를 사용하여 Microsoft Entra 및 타사 데이터베이스 또는 애플리케이션에서 데이터를 추출합니다.
  3. 빠르고 확장 가능한 데이터 분석 서비스인 Azure Data Explorer 데이터를 가져옵니다.
  4. Kusto 쿼리 언어를 사용하여 사용자 지정 쿼리를 빌드합니다.

이 자습서를 마치면 Microsoft 지원 도구를 사용하여 다양한 애플리케이션에서 사용자의 액세스 권한 및 권한에 대한 사용자 지정 보기를 개발하는 기술을 빌드하게 됩니다.

필수 조건

  • 필요한 권한이 있는지 확인합니다. 작업하려는 Entra 데이터 형식을 내보내는 데 적합한 권한과 내보낸 JSON 파일을 저장할 수 있는 권한이 필요합니다.

    • 사용자 데이터: 전역 관리자, 권한 있는 역할 관리자, 사용자 관리자
    • 그룹 데이터: 전역 관리자, 권한 있는 역할 관리자, 그룹 관리자
    • 애플리케이션/앱 역할 할당: 전역 관리자, 권한 있는 역할 관리자, 애플리케이션 관리자, 클라우드 애플리케이션 관리자
  • User.Read.All, Group.Read.All, Application.Read.All 및 Directory.Read.All을 허용하도록 PowerShell을 설정해야 합니다. 자세한 내용은 Microsoft Graph 권한 참조를 참조하세요.

  • 필요한 MS Graph PowerShell 모듈을 설치하고 내보낸 Entra 데이터를 저장할 디렉터리에 대한 쓰기 권한이 있는지 확인합니다.

  • 보고서에 포함할 데이터를 결정합니다. 이 문서의 스크립트는 Entra의 사용자, 그룹 및 애플리케이션의 특정 데이터를 샘플에 제공합니다. 이러한 샘플은 이 방법을 사용하여 생성할 수 있는 보고서 유형을 설명하기 위한 것이지만 특정 보고 요구 사항은 다를 수 있으며 다른 데이터 또는 추가 데이터가 필요할 수 있습니다.

1단계: Azure Data Explorer 설정

이전에 Azure Data Explorer를 사용하지 않은’경우 먼저 설정해야 합니다. Azure 구독 또는 신용 카드 또는 Azure 구독이 필요한 전체 클러스터 없이 무료 클러스터를 만들 수 있습니다. 빠른 시작을 참조하세요. 시작할 Azure Data Explorer 클러스터 및 데이터베이스를 만듭니다.

2단계: PowerShell을 사용하여 MS Graph에 연결 및 Entra 데이터 추출

MS Graph PowerShell 모듈을 설치하고 MS Graph에 연결합니다.

  1. 필요한 MS Graph 모듈을 설치합니다. 이 자습서에는 Microsoft.Graph.Users, Microsoft.Graph.Groups, Microsoft.Graph.Applications, Microsoft.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. 모듈을 가져옵니다.
  $modules = @('Microsoft.Graph.Users', 'Microsoft.Graph.Groups', 'Microsoft.Graph.Applications', 'Microsoft.Graph.DirectoryObjects') 
  foreach ($module in $modules) { 
  Import-Module -Name $module 
  } 
  1. Microsoft Graph에 연결
  Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "Application.Read.All", "Directory.Read.All" 

이 명령은 MS Graph 자격 증명으로 로그인하라는 메시지를 표시합니다. 필요한 사용 권한 선택: 로그인한 후 처음 연결하는 경우 또는 새 권한이 필요한 경우 필요한 권한에 동의해야 할 수 있습니다.

ADX에서 사용자 지정 보고서를 빌드하는 데 필요한 데이터를 추출하는 PowerShell 쿼리

다음 쿼리는 PowerShell을 사용하여 MS Graph에서 Entra 데이터를 추출하고 3단계에서 Azure Data Explorer로 가져올 JSON 파일로 데이터를 내보냅니다. 이러한 유형의 데이터를 사용하여 보고서를 생성하는 여러 시나리오가 있을 수 있습니다.

  • 감사원은 구성원 부서에서 구성한 10개 그룹의 그룹 구성원을 나열하는 보고서를 보고 싶습니다.
  • 감사자는 두 날짜 사이에 애플리케이션에 액세스할 수 있는 모든 사용자의 보고서를 보고자 합니다.
  • 관리자는 Microsoft Entra ID 및 SQL 데이터베이스에서 애플리케이션에 추가된 모든 사용자를 보려고 합니다.

이러한 유형의 보고서는 Microsoft Entra ID에 기본 제공되지 않지만, Entra에서 데이터를 추출하고 Azure Data Explorer에서 사용자 지정 쿼리를 사용하여 결합하여 이러한 보고서를 직접 만들 수 있습니다.

이 자습서에서는 여러 영역에서 Entra 데이터를 추출합니다.

  • 표시 이름, UPN 및 작업 세부 정보와 같은 사용자 정보
  • 그룹 정보
  • 애플리케이션 및 역할 할당

이 데이터 집합을 사용하면 애플리케이션, 역할 정보 및 관련 기간에 대한 액세스 권한이 부여된 사용자에 대한 광범위한 쿼리 집합을 수행할 수 있습니다. 이러한 쿼리는 샘플 쿼리이며 데이터 및 특정 요구 사항은 여기에 표시된 것과 다를 수 있습니다.

참고 항목

더 큰 테넌트에는 MS Graph 모듈에서 처리할 제한/429 오류가 발생할 수 있습니다.

이러한 PowerShell 스크립트에서는 Entra 개체에서 JSON 파일로 선택한 속성을 내보냅니다. 그러면 이러한 내보낸 속성의 데이터를 사용하여 Azure Data Explorer에서 사용자 지정 보고서를 생성합니다. 이 데이터를 사용하여 Azure Data Explorer에서 만들 수 있는 보고서 유형을 설명하기 때문에 아래의 특정 속성이 이러한 예제에 포함되었습니다. 특정 보고 요구 사항은 아래에 표시된 것과 다를 수 있으므로 보고서에서 보고자 하는 이러한 스크립트에 특정 속성을 포함해야 하지만 아래와 동일한 패턴을 따라 스크립트를 빌드할 수 있습니다.

또한 JSON 파일의 데이터를 특정 날짜로 식별하고 Azure Data Explorer에서 시간이 지남에 따라 유사한 데이터 집합을 추적할 수 있도록 하는 하드 코딩된 스냅샷 날짜가 포함되었습니다. 스냅샷 날짜는 두 스냅샷 날짜 간의 데이터 변경 내용을 비교하는 데에도 유용합니다.

Entra 사용자 데이터 가져오기

이 스크립트는 Entra 사용자 개체에서 선택한 속성을 JSON 파일로 내보냅니다. 3단계에서 이 데이터를 Azure Data Explorer로 가져옵니다.

  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"] = "2024-01-11" 
      [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 

그룹 데이터 가져오기

ADX에서 사용자 지정 보기를 만드는 데 사용할 그룹 이름 및 ID가 있는 JSON 파일을 생성합니다. 샘플에는 모든 그룹이 포함되지만 필요한 경우 추가 필터링을 포함할 수 있습니다. 특정 그룹만 포함하도록 필터링하는 경우 중첩된 그룹을 확인하는 논리를 스크립트에 포함할 수 있습니다.

    # 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" 

그룹 멤버 자격 데이터 가져오기

ADX에서 사용자 지정 보기를 만드는 데 사용할 그룹 멤버 자격이 있는 JSON 파일을 생성합니다.

    # 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 
      } 
      # 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 파일을 생성합니다. 3단계에서 이 데이터를 ADX로 가져와서 이 데이터를 기반으로 애플리케이션과 관련된 사용자 지정 보고서를 생성할 수 있습니다.

    # 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 
      } 
    } | ConvertTo-Json -Depth 10 | Set-Content "Applications.json" 

AppRole 데이터 가져오기

Entra에서 엔터프라이즈 앱에 대한 모든 appRoles의 JSON 파일을 생성합니다. ADX로 가져온 후에는 이 데이터를 활용하여 사용자에 대한 앱 역할 할당과 관련된 보고서를 생성합니다.

    # 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 
      } 
    } 
    # 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     = "2024-03-13" # Hard-coded date 
        } 
      } 
    } 
    $result | ConvertTo-Json -Depth 10 | Out-File "AppRoleAssignments.json" 

3단계: Azure Data Explorer로 JSON 파일 데이터 가져오기

3단계에서는 추가 분석을 위해 새로 만든 JSON 파일을 가져옵니다. Azure Data Explorer를 아직 설정하지 않은 경우 위의 1단계를 참조하세요.

Azure Data Explorer는 확장성이 뛰어나고 유연하여 사용자 지정 사용자 액세스 보고서를 생성하기에 이상적인 환경을 제공하는 강력한 데이터 분석 도구입니다. ADX는 KQL(Kusto 쿼리 언어)을 사용합니다.

데이터베이스를 설정했으면 다음 단계에 따라 내보낸 데이터를 ADX로 가져옵니다.

  1. 데이터베이스 이름을 마우스 오른쪽 단추로 클릭하고 데이터 가져오기를 를 선택합니다.
  2. 새 테이블을 선택하고 가져오고 있는 JSON 파일의 이름을 입력합니다. 예를 들어 EntraUsers.json 가져오는 경우 entraUsers 테이블 이름을 지정합니다. 첫 번째 가져오기 후에는 테이블이 이미 존재하며 가져오기의 대상 테이블로 선택할 수 있습니다.
  3. JSON 파일을 선택합니다.
  4. ADX는 스키마를 자동으로 검색하고 미리 보기를 제공합니다. 마침을 클릭하여 테이블을 만들고 데이터를 가져옵니다.
  5. 1단계에서 생성한 각 JSON 파일에 대해 1-4단계를 수행합니다.

4단계: ADX를 사용하여 사용자 지정 보고서 빌드

이제 ADX에서 데이터를 사용할 수 있으므로 비즈니스 요구 사항에 따라 사용자 지정된 보고서 만들기를 시작할 준비가 되었습니다. 다음 쿼리는 일반적인 보고서의 예를 제공하지만 필요에 맞게 이러한 보고서를 사용자 지정하고 추가 보고서를 만들 수 있습니다.

예제 1: 특정 스냅샷 날짜에 대한 직접 및 그룹 할당에 대한 앱 역할 할당 생성

이 보고서는 대상 앱에 대한 액세스 권한이 있는 사용자와 시기 및 보안 감사, 규정 준수 확인 및 조직 내 액세스 패턴 이해에 사용할 수 있는 사용자에 대한 보기를 제공합니다.

이 쿼리는 Entra AD 내의 특정 애플리케이션을 대상으로 하며 특정 날짜를 기준으로 역할 할당을 분석합니다. 쿼리는 직접 및 그룹 기반 역할 할당을 모두 검색하여 이 데이터를 EntraUsers 테이블의 사용자 세부 정보 및 AppRoles 테이블의 역할 정보와 병합합니다.

/// 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: Entra의 앱 할당과 두 번째 원본(예: SQL 내보내기)을 결합하여 두 날짜 사이에 Salesforce에 액세스할 수 있는 모든 사용자(엔트라 할당 및 로컬 할당)의 보고서를 만듭니다.

이 보고서는 별도의 두 시스템의 데이터를 결합하여 ADX에서 사용자 지정 보고서를 만드는 방법을 보여 줍니다. 두 시스템의 사용자, 역할 및 기타 특성에 대한 데이터를 분석 또는 보고를 위한 통합 형식으로 집계합니다.

// 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() 
) 

다음 단계