Samouczek: dostosowane raporty w usłudze Azure Data Explorer (ADX) przy użyciu danych z identyfikatora Entra firmy Microsoft
Z tego samouczka dowiesz się, jak tworzyć dostosowane raporty w usłudze Azure Data Explorer (ADX) przy użyciu danych z identyfikatora Entra firmy Microsoft. Ten samouczek uzupełnia inne opcje raportowania, takie jak Archiwum i raport za pomocą usługi Azure Monitor i zarządzania upoważnieniami, które koncentrują się na eksportowaniu danych dziennika inspekcji na potrzeby dłuższego przechowywania i analizy. Dla porównania eksportowanie danych identyfikatora entra firmy Microsoft do usługi Azure Data Explorer zapewnia większą elastyczność tworzenia raportów niestandardowych, umożliwiając agregację danych z wielu źródeł z ogromną skalowalnością oraz elastyczne zasady schematu i przechowywania.
W tym raporcie pokazano, jak pokazać konfigurację, użytkowników i prawa dostępu wyeksportowane z firmy Microsoft Entra wraz z danymi wyeksportowanymi z innych źródeł, takimi jak aplikacje z bazami danych SQL. Następnie możesz użyć język zapytań Kusto (KQL) do tworzenia niestandardowych raportów na podstawie wymagań organizacji. Generowanie tych typów raportów w usłudze Azure Data Explorer może być szczególnie przydatne, jeśli konieczne jest przechowywanie danych dostępu przez dłuższy czas, przeprowadzanie badań ad hoc lub uruchamianie niestandardowych zapytań dotyczących danych dostępu użytkowników.
Aby utworzyć te raporty, wykonaj następujące czynności:
- Skonfiguruj usługę Azure Data Explorer w subskrypcji platformy Azure.
- Wyodrębnianie danych z baz danych firmy Microsoft i aplikacji innych firm przy użyciu skryptów programu PowerShell i programu MS Graph.
- Zaimportuj dane do usługi Azure Data Explorer, szybkiej i skalowalnej usługi analizy danych.
- Skompiluj zapytanie niestandardowe przy użyciu język zapytań Kusto.
Po ukończeniu tego samouczka będziesz mieć umiejętności tworzenia dostosowanych widoków praw dostępu i uprawnień użytkowników w różnych aplikacjach przy użyciu narzędzi obsługiwanych przez firmę Microsoft.
Wymagania wstępne
Upewnij się, że masz wymagane uprawnienia. Musisz mieć odpowiednie uprawnienia do eksportowania typu danych Entra, z którym chcesz pracować, i uprawnienia do zapisywania wyeksportowanych plików JSON.
- Dane użytkownika: administrator globalny, administrator ról uprzywilejowanych, administrator użytkowników
- Dane grup: administrator globalny, administrator ról uprzywilejowanych, administrator grupy
- Przypisania ról aplikacji: administrator globalny, administrator ról uprzywilejowanych, administrator aplikacji, administrator aplikacji, administrator aplikacji w chmurze
Program PowerShell musi być ustawiony tak, aby zezwalał na ustawienie User.Read.All, Group.Read.All, Application.Read.All i Directory.Read.All. Aby uzyskać dodatkowe informacje, zobacz Dokumentacja uprawnień programu Microsoft Graph.
Upewnij się, że masz dostęp do zapisu w katalogu, w którym zainstalujesz wymagane moduły programu POWERShell programu MS Graph i gdzie zostaną zapisane wyeksportowane dane entra.
Określ, jakie dane mają zostać uwzględnione w raportach. Skrypty w tym artykule zawierają przykłady z określonymi danymi użytkowników, grup i aplikacji firmy Entra. Te przykłady mają na celu zilustrowanie typów raportów, które można wygenerować przy użyciu tego podejścia, ale konkretne potrzeby raportowania mogą się różnić i wymagać różnych lub dodatkowych danych.
Krok 1. Konfigurowanie usługi Azure Data Explorer
Jeśli wcześniej nie używasz usługi Azure Data Explorer, musisz najpierw ją skonfigurować. Możesz utworzyć bezpłatny klaster bez subskrypcji platformy Azure lub karty kredytowej lub pełnego klastra, który wymaga subskrypcji platformy Azure. Zobacz Szybki start: tworzenie klastra i bazy danych usługi Azure Data Explorer, aby rozpocząć pracę.
Krok 2. Nawiązywanie połączenia z programem MS Graph i wyodrębnianie danych entra przy użyciu programu PowerShell
Zainstaluj moduły programu POWERShell programu MS Graph i połącz się z programem MS Graph.
- Zainstaluj wymagane moduły programu MS Graph. Na potrzeby tego samouczka wymagane są następujące moduły: 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
}
- Zaimportuj moduły:
$modules = @('Microsoft.Graph.Users', 'Microsoft.Graph.Groups', 'Microsoft.Graph.Applications', 'Microsoft.Graph.DirectoryObjects')
foreach ($module in $modules) {
Import-Module -Name $module
}
- Nawiązywanie połączenia z programem Microsoft Graph
Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "Application.Read.All", "Directory.Read.All"
To polecenie wyświetli monit o zalogowanie się przy użyciu poświadczeń programu MS Graph. Wybierz pozycję Wymagane uprawnienia: po zalogowaniu może być konieczne wyrażenie zgody na wymagane uprawnienia, jeśli po raz pierwszy nawiąż połączenie lub jeśli są wymagane nowe uprawnienia.
Zapytania programu PowerShell służące do wyodrębniania danych potrzebnych do tworzenia niestandardowych raportów w usłudze ADX
Poniższe zapytania wyodrębniają dane Entra z programu MS Graph przy użyciu programu PowerShell i eksportują dane do plików JSON, które zostaną zaimportowane do usługi Azure Data Explorer w kroku 3. Może istnieć wiele scenariuszy generowania raportów z tego typu danymi:
- Audytor chce zobaczyć raport zawierający listę członków grupy dla 10 grup zorganizowanych przez dział członków.
- Audytor chce zobaczyć raport wszystkich użytkowników, którzy mieli dostęp do aplikacji między dwoma datami.
- Administrator chce wyświetlić wszystkich użytkowników dodanych do aplikacji z bazy danych Microsoft Entra ID i SQL Database.
Te typy raportów nie są wbudowane w identyfikator entra firmy Microsoft, ale te raporty można tworzyć samodzielnie, wyodrębniając dane z usługi Entra i łącząc je przy użyciu zapytań niestandardowych w usłudze Azure Data Explorer.
W tym samouczku wyodrębnimy dane Entra z kilku obszarów:
- Informacje o użytkowniku, takie jak nazwa wyświetlana, nazwa UPN i szczegóły zadania
- Informacje o grupie
- Przypisania aplikacji i ról
Ten zestaw danych umożliwi wykonywanie szerokiego zestawu zapytań dotyczących osób, które otrzymały dostęp do aplikacji, informacji o rolach i skojarzonych ram czasowych. Należy pamiętać, że są to przykładowe zapytania, a dane i określone wymagania mogą się różnić od przedstawionych tutaj.
Uwaga
Większe dzierżawy mogą napotkać błędy ograniczania przepustowości /429, które będą obsługiwane przez moduł MS Graph.
W tych skryptach programu PowerShell wyeksportujemy wybrane właściwości z obiektów Entra do plików JSON. Dane z tych wyeksportowanych właściwości będą następnie używane do generowania niestandardowych raportów w usłudze Azure Data Explorer. Poniższe właściwości zostały uwzględnione w tych przykładach, ponieważ używamy tych danych do zilustrowania typów raportów, które można utworzyć w usłudze Azure Data Explorer. Ponieważ konkretne potrzeby raportowania mogą się różnić w zależności od tego, co pokazano poniżej, należy uwzględnić określone właściwości w tych skryptach, które cię interesują podczas wyświetlania raportów, jednak możesz postępować zgodnie z tym samym wzorcem przedstawionym poniżej, aby ułatwić tworzenie skryptów.
Dołączyliśmy również ustaloną datę migawki poniżej, która identyfikuje dane w pliku JSON z określoną datą i pozwoli nam śledzić podobne zestawy danych w czasie w usłudze Azure Data Explorer. Data migawki jest również przydatna do porównywania zmian danych między dwiema datami migawki.
Pobieranie danych użytkownika entra
Ten skrypt spowoduje wyeksportowanie wybranych właściwości z obiektu użytkownika Entra do pliku JSON. Zaimportujemy te dane do usługi Azure Data Explorer w kroku 3.
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
Pobieranie danych grupy
Wygeneruj plik JSON z nazwami grup i identyfikatorami, które będą używane do tworzenia widoków niestandardowych w usłudze ADX. Przykład będzie zawierać wszystkie grupy, ale w razie potrzeby można uwzględnić dodatkowe filtrowanie. Jeśli filtrujesz w celu uwzględnienia tylko niektórych grup, możesz uwzględnić logikę w skry skrycie, aby sprawdzić grupy zagnieżdżone.
# 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"
Pobieranie danych członkostwa w grupie
Wygeneruj plik JSON z członkostwem w grupie, który będzie używany do tworzenia widoków niestandardowych w usłudze ADX.
# 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"
Pobieranie danych aplikacji i jednostki usługi
Generuje plik JSON ze wszystkimi aplikacjami i odpowiednimi jednostkami usługi w dzierżawie. Zaimportujemy te dane do usługi ADX w kroku 3, co umożliwi generowanie niestandardowych raportów związanych z aplikacjami na podstawie tych danych.
# 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"
Pobieranie danych approle
Wygeneruj plik JSON wszystkich elementów appRoles dla aplikacji dla przedsiębiorstw w usłudze Entra. Po zaimportowaniu do usługi ADX użyjemy tych danych do generowania raportów dotyczących przypisań ról aplikacji dla użytkowników.
# 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'
Pobieranie danych przypisania approle
Wygeneruj plik JSON wszystkich przypisań ról aplikacji w dzierżawie.
$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"
Krok 3. Importowanie danych plików JSON do usługi Azure Data Explorer
W kroku 3 zaimportujemy nowo utworzone pliki JSON do dalszej analizy. Jeśli jeszcze nie skonfigurowaliśmy usługi Azure Data Explorer, zobacz krok 1 powyżej.
Azure Data Explorer to zaawansowane narzędzie do analizy danych, które jest wysoce skalowalne i elastyczne, zapewniając idealne środowisko do generowania dostosowanych raportów dostępu użytkowników. Usługa ADX używa język zapytań Kusto (KQL).
Po skonfigurowaniu bazy danych wykonaj następujące kroki, aby pobrać wyeksportowane dane do usługi ADX.
- Kliknij prawym przyciskiem myszy nazwę bazy danych i wybierz polecenie Pobierz dane
- Wybierz pozycję Nowa tabela i wprowadź nazwę importowanych plików JSON, na przykład jeśli importujesz EntraUsers.json, nadaj tabeli nazwę EntraUsers. Po pierwszym zaimportowaniu tabela już istnieje i można ją wybrać jako tabelę docelową importu.
- Wybierz plik JSON.
- Usługa ADX automatycznie wykryje schemat i udostępni podgląd. Kliknij przycisk Zakończ , aby utworzyć tabelę i zaimportować dane.
- Wykonaj kroki od 1 do 4 dla każdego z plików JSON wygenerowanych w kroku 1.
Krok 4. Tworzenie niestandardowych raportów przy użyciu usługi ADX
Gdy dane są teraz dostępne w usłudze ADX, możesz rozpocząć tworzenie dostosowanych raportów na podstawie wymagań biznesowych. Poniższe zapytania zawierają przykłady typowych raportów, ale można dostosować te raporty do własnych potrzeb i tworzyć dodatkowe raporty.
Przykład 1. Generowanie przypisań ról aplikacji dla przypisań bezpośrednich i grup dla określonej daty migawki
Ten raport zawiera informacje o tym, kto miał dostęp i kiedy ma dostęp do aplikacji docelowej oraz może służyć do inspekcji zabezpieczeń, weryfikacji zgodności i zrozumienia wzorców dostępu w organizacji.
To zapytanie dotyczy określonej aplikacji w usłudze Entra AD i analizuje przypisania ról od określonej daty. Zapytanie pobiera zarówno przypisania ról bezpośrednich, jak i opartych na grupach, scalając te dane ze szczegółami użytkownika z tabeli EntraUsers i informacjami o roli z tabeli 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
Przykład 2. Tworzenie podstawowego raportu audytora przy użyciu danych Entra pokazujących, kto miał dostęp do aplikacji między tymi dwoma datami
Ten raport zawiera informacje o tym, kto miał dostęp do aplikacji docelowej między dwiema datami i może być używany do inspekcji zabezpieczeń, weryfikacji zgodności i zrozumienia wzorców dostępu w organizacji.
To zapytanie dotyczy określonej aplikacji w ramach identyfikatora Entra firmy Microsoft i analizuje przypisania ról między dwiema datami. Zapytanie pobiera bezpośrednie przypisania ról z tabeli AppRoleAssignments i scala te dane ze szczegółami użytkownika z tabeli EntraUsers i informacji o roli z tabeli 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()
Przykład 3. Pobieranie użytkowników do aplikacji między dwiema datami migawki danych
Te raporty zapewniają widok, dla których użytkownicy otrzymali przypisanie roli aplikacji do aplikacji docelowej między dwiema datami. Te raporty mogą służyć do śledzenia zmian dostępu do aplikacji w czasie.
To zapytanie dotyczy określonej aplikacji w ramach identyfikatora Entra firmy Microsoft i zmienia przypisanie ról między datą początkową a końcową.
// 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"
Przykład 4: Łączenie przypisań aplikacji z entra i drugiego źródła (na przykład eksportu SQL) w celu utworzenia raportu wszystkich użytkowników (przypisań entra i przypisań lokalnych), którzy mieli dostęp do usługi Salesforce między dwiema datami
Ten raport ilustruje sposób łączenia danych z dwóch oddzielnych systemów w celu tworzenia niestandardowych raportów w usłudze ADX. Agreguje dane dotyczące użytkowników, ich ról i innych atrybutów z dwóch systemów w ujednolicony format analizy lub raportowania.
// 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()
)