
Kuratierte Sammlung von KQL-Abfragen und -Tutorials für Microsoft Sentinel zur Bedrohungssuche, Analyse von Azure AD-Anmeldeprotokollen, Erkennung von Anomalien und Erstellung effizienter Erkennungsmuster.
Einige Tipps, Tricks und Beispiele für die Verwendung von KQL mit Microsoft Sentinel.
Kusto Query Language ist die Sprache, die in Azure Monitor, Azure Data Explorer und Azure Log Analytics verwendet wird (was Microsoft Sentinel unter der Haube nutzt). Ich habe diese Visualisierung zu KQL schon immer nützlich gefunden -

Wir wollen KQL verwenden, um genaue und effiziente Abfragen zu erstellen, um Bedrohungen, Erkennungen, Muster und Anomalien in unserem größeren Datensatz zu finden.
Nehmen Sie die folgende Abfrage als Beispiel```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent
Wenn wir eine Abfrage wie diese ausführen, teilt uns die erste Zeile mit, in welcher Tabelle Microsoft Sentinel nach Daten suchen soll. In diesem Fall möchten wir also die Tabelle `SigninLogs` durchsuchen, in die die Azure-AD-Anmeldedaten geschrieben werden. Eine Liste der Tabellen findest du [hier](https://docs.microsoft.com/en-us/azure/sentinel/data-source-schema-reference).
Microsoft Sentinel führt deine Abfrage dann sequenziell aus, d. h. jede Zeile wird nacheinander verarbeitet, bis das Ende erreicht ist oder ein Fehler auftritt. Schauen wir uns unsere Abfrage also Zeile für Zeile an.```kql
SigninLogs
Also zuerst haben wir unsere SigninLogs-Tabelle ausgewählt.```kql SigninLogs | where TimeGenerated > ago(14d)
Als Nächstes weisen wir Sentinel an, die Daten der letzten 14 Tage in dieser Tabelle zu betrachten.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
Als Nächstes fordern wir Sentinel auf, nur Protokolle zu finden, bei denen UserPrincipalName gleich "[email protected]" ist.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0"
Dann suchen wir nur nach Protokollen, bei denen der ResultType == 0 ist, was eine erfolgreiche Anmeldung bei Azure AD darstellt.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"
Als Nächstes suchen wir nur nach Anmeldungen bei Microsoft Teams.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent
Unsere letzte Zeile verwendet den project-Operator, um nur 4 Felder aus unseren Protokollen zurückzugeben, sodass wir aus unseren SigninLogs-Daten nur TimeGenerated, Location, IPAddress und UserAgent sehen.
So baust du Abfragen auf, jetzt zu den Grundlagen.
## Die Grundlagen
### Zeitliche Grundlagen
Microsoft Sentinel und KQL sind stark für Zeitfilter optimiert. Wenn du also den Zeitraum der Daten kennst, die du durchsuchen möchtest, solltest du den Zeitbereich sofort filtern: die letzten 14 Tage an Protokollen abrufen und dann nach einem Benutzernamen suchen, wie in der folgenden Abfrage -```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
Ist viel effizienter, als zuerst nach einem Benutzernamen und dann nach dem Zeitraum zu suchen, etwa so -```kql SigninLogs | where UserPrincipalName == "[email protected]" | where TimeGenerated > ago(14d)
KQL bietet viele Optionen zum Abfragen bestimmter Zeiträume.```kql
SigninLogs
| where TimeGenerated > ago(14d)
Wie im ersten Beispiel sucht dies nach den letzten 14 Tagen.```kql SigninLogs | where TimeGenerated > ago(14h)
Du kannst auch Stunden angeben.```kql
SigninLogs
| where TimeGenerated > ago(14m)
Und Minuten.
KQL unterstützt auch Abfragen zwischen Zeitbereichen -```kql SigninLogs | where TimeGenerated between (ago(14d) .. ago(7d))
Dadurch werden SigninLogs-Daten zwischen vor 14 Tagen und vor 7 Tagen gefunden.```kql
SigninLogs
| where TimeGenerated between (ago(14h) .. ago(7h))
Zwischen vor 14 Stunden und vor 7 Stunden.```kql SigninLogs | where TimeGenerated between (ago(14m) .. ago(7m))
Und zwischen vor 14 Minuten und vor 7 Minuten.
### Where-Grundlagen
Where ist ein Operator, den Sie in praktisch jeder Abfrage verwenden werden. So teilen Sie Microsoft Sentinel mit, nach bestimmten Daten zu suchen. Die Syntax ist beim where-Operator sehr wichtig. Wenn wir unser gleiches Beispiel verwenden.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
Dadurch wird in unserer SigninLogs-Tabelle über die letzten 14 Tage nach exakten Übereinstimmungen gesucht, bei denen unser UserPrincipalName gleich [email protected] ist. In KQL ist == case-sensitive. Wenn du also nach [email protected] suchst und der Benutzername tatsächlich [email protected] lautet, erhältst du keine Ergebnisse. Das nicht case-sensitive Gegenstück ist =```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName = "[email protected]"
Dies findet alle Übereinstimmungen für [email protected] unabhängig von der Groß-/Kleinschreibung.
Statt equals können wir auch contains verwenden.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName contains "reprise_99"
Dies findet alle Protokolleinträge, bei denen der UserPrincipalName reprise_99 enthält. Wenn Sie Daten für [email protected] und [email protected] hätten, würde es beide finden. Der contains-Operator ist nicht case-sensitiv, aber Sie können contains_cs verwenden, um ihn case-sensitiv zu machen.
Sie können entweder startswith oder endswith verwenden, wenn Sie nach bestimmten Mustern suchen.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName startswith "reprise_99"
SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName endswith "testdomain.com"
Sowohl startswith als auch endswith unterscheiden nicht zwischen Groß- und Kleinschreibung, aber Sie können startswith_cs oder endswith_cs verwenden, um sie case-sensitiv zu machen.
Wenn Sie nach vollständigen Wörtern suchen (mehr als vier Zeichen), können Sie in KQL den has-Operator verwenden. Die Verwendung von 'has' ist effizienter als 'contains', da die Daten für Sie indiziert sind.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName has "Teams"
Dadurch werden alle SigninLogs gefunden, bei denen der Anzeigename der Anwendung das Wort Teams enthält. Das kann „Microsoft Teams" und „Microsoft Teams Web Client" einschließen, die beide die Abfrage erfüllen.
Wenn Sie nach mehreren Wörtern suchen, können Sie has_any oder has_all verwenden.```kql SigninLogs | where TimeGenerated > ago(14d) | where AppDisplayName has_any ("Teams","Outlook")
Dies gibt Ergebnisse zurück, bei denen der Anzeigename der Anwendung entweder "Teams" oder "Outlook" enthält.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName has_all ("Teams","Outlook")
Dies wird Ergebnisse liefern, bei denen der Anzeigename der Anwendung "Teams" und "Outlook" enthält.
Wenn du nicht weißt, in welchen Feldern du suchen sollst, kannst du auch Platzhalter verwenden. Das ist ineffizient, führt dich aber vielleicht auf die richtige Spur, um zu finden, was du suchst.```kql SigninLogs | where TimeGenerated > ago(14d) | where * contains "reprise_99"
Dadurch wird die Tabelle SigninLogs nach jedem Feld durchsucht, das reprise_99 enthält.
Eine Reihe dieser Optionen unterstützt außerdem die Verwendung von !, um die Abfrage umzukehren und Ergebnisse zu finden, bei denen dies nicht zutrifft.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName != "[email protected]"
Diese Abfrage würde alle SigninLogs finden, bei denen UserPrincipalName nicht gleich [email protected] ist.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName !contains "reprise_99"
Diese Abfrage würde alle SigninLogs finden, bei denen der UserPrincipalName nicht reprise_99 enthält.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName !has "Teams"
Diese Abfrage würde SigninLogs finden, bei denen der Anzeigename der Anwendung nicht "Teams" enthält.
Project ermöglicht es uns, auszuwählen, welche Spalten in unserer Abfrage zurückgegeben werden und in welcher Reihenfolge.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent
Diese Abfrage durchsucht die SigninLogs-Daten der letzten 14 Tage nach Einträgen, bei denen UserPrincipalname gleich [email protected] ist, ResultType gleich 0 ist und der Anzeigename der Anwendung gleich "Microsoft Teams" ist. Für jede Übereinstimmung dieser Abfrage werden TimeGenerated, Location, IPAddress und UserAgent zurückgegeben.
Wir können Spalten als Teil derselben Funktion umbenennen.```kql
| project LogTime=TimeGenerated, SigninLocation=Location, IP=IPAddress, Agent=UserAgent
Dies gibt dieselben Daten zurück, benennt die Spalten jedoch in LogTime, SigninLocation, IP und Agent um.
Wir können die Ausgabe sogar inline mit dem project-Operator manipulieren.```kql | project LocalTime=TimeGenerated+5h, Location, IPAddress, UserAgent
Dies gibt dieselben Daten zurück, ändert aber den Namen TimeGenerated zu LocalTime und konvertiert in eine +5h Zeitzone, falls Sie in dieser Zeitzone arbeiten.
project-away ist das Gegenteil von project und entfernt Spalten aus Ihrer Abfrage.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| project-away UserAgent
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"
In dieser Abfrage entfernen wir UserAgent. Denken Sie daran: Wenn Sie eine Spalte entfernen, können Sie später in Ihrer Abfrage nicht mehr darauf zugreifen.
Summarize erzeugt eine Tabelle, die den Inhalt Ihrer Abfrage aggregiert. Summarize verfügt über eine Reihe zugrunde liegender Aggregationsfunktionen. Wenn wir erneut unsere Beispielabfrage nehmen, können wir die Ergebnisse mithilfe von summarize auf verschiedene Weise verändern.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize count() by AppDisplayName
Diese Abfrage durchsucht die Tabelle SigninLogs nach allen Ereignissen der letzten 14 Tage, nach Übereinstimmungen für [email protected], bei denen das Ergebnis erfolgreich ist (ResultType == 0), und fasst diese Ereignisse anschließend nach dem Anzeigenamen der Anwendung zusammen.
Sie können die Ergebnisspalte optional benennen.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppCount=count() by AppDisplayName
Dies gibt dieselben Daten zurück, aktualisiert jedoch den Namen der zurückgegebenen Spalte auf AppCount.
Anstelle einer Gesamtzahl können Sie eine eindeutige Anzahl zusammenfassen.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize DistinctAppCount=dcount(AppDisplayName) by AppDisplayName
Dies gibt einen einzelnen Datensatz für jede eindeutige Anwendung zurück, bei der [email protected] angemeldet war.
Sie können die Funktionen arg_max und arg_min verwenden, um entweder den neuesten oder den ältesten Datensatz zurückzugeben, der Ihrer Abfrage entspricht.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize arg_max(TimeGenerated, *) by UserPrincipalName
Diese Abfrage sucht nach allen Anmeldeprotokollen der letzten 14 Tage, die [email protected] als UserPrincipalname haben, die erfolgreich sind, und gibt dann den neuesten Datensatz zurück.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize arg_min(TimeGenerated, *) by UserPrincipalName
Dies ist dasselbe, gibt aber den ältesten Datensatz zurück.
Sie können countif verwenden, um Ihren Summierungen Logik hinzuzufügen.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize TeamsLogons=countif(AppDisplayName has "Teams"), SharePointLogons=countif(AppDisplayName has "SharePoint")
Dies fasst die Daten in zwei neue Spalten zusammen: TeamsLogons, wobei der Anzeigename der Anwendung "Teams" enthält, und SharePointLogons, wobei der Anzeigename der Anwendung "SharePoint" enthält.
Sie können Ihre Daten weiter bearbeiten, indem Sie KQL anweisen, Ihre Daten in Zeit-'bins' zu legen.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count() by AppDisplayName, bin(TimeGenerated, 1d)
Dies gibt dieselben Daten wie unser erstes summarize-Beispiel zurück und gruppiert diese Daten dann in 1d-Bins.
Sie können diese Funktionen miteinander kombinieren, wo es sinnvoll ist.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize TeamsLogons=countif(AppDisplayName has "Teams"), SharePointLogons=countif(AppDisplayName has "SharePoint") by bin(TimeGenerated, 1d)
Dies ist eine Kombination unserer countif- und bin-Funktionen, bei der wir basierend auf dem Anzeigenamen unserer Anwendung zusammenfassen und die Ergebnisse in 1d-Bins einordnen.
Sie können innerhalb einer Abfrage einen Satz von Elementen erstellen.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppList=make_set(AppDisplayName) by UserPrincipalName
This will output a list of applications that [email protected] has signed into to a list called AppList.
You can combine this with our time bin.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppList=make_set(AppDisplayName) by UserPrincipalName, bin(TimeGenerated, 1d)
Dies erstellt eine Liste der Anwendungen, bei denen sich [email protected] angemeldet hat, aufgeteilt in eine Liste pro Tag.
Der Render-Operator ermöglicht es KQL, Daten in verschiedenen Formaten zu visualisieren, z. B. Kreisdiagrammen, Zeit- oder Flächendiagrammen sowie Säulen- und Balkendiagrammen.
Wenn wir dasselbe Beispiel aus unserer Signinlogs-Tabelle verwenden, können wir sehen, wie wir die Daten auf verschiedene Weise visualisieren können.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count()by AppDisplayName | render piechart
Diese Abfrage fasst alle Anwendungen zusammen, bei denen sich [email protected] in den letzten 14 Tagen angemeldet hat, und rendert die Ausgabe dann als Kreisdiagramm.

Sie können es auch als Säulendiagramm rendern.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppCount=count()by AppDisplayName
| render columnchart

Oder ein Balkendiagramm.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count()by AppDisplayName | render barchart

Bei Zeitdaten fassen Sie Ihre Daten zunächst in Zeit-'Bins' zusammen, wie im Abschnitt 'summarize' beschrieben, und können Ihre Daten dann über einen Zeitraum visualisieren.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by bin(TimeGenerated, 1d)
| render timechart
Dadurch werden alle Anmeldungen von [email protected] pro Tag der letzten 14 Tage visualisiert und als Zeitdiagramm angezeigt.

Sie können render auch als areachart verwenden.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize SigninCount=count() by bin(TimeGenerated, 1d) | render areachart

Säulendiagramme und Balkendiagramme können ebenfalls mit Zeitdaten verwendet werden. Sie erhalten eine Säule oder einen Balken pro Zeit-'Bin' über Ihren größeren Zeitraum.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by bin(TimeGenerated, 1d)
| render columnchart
Dies ist dieselbe Abfrage wie unser Timechart, aber als Säulendiagramm mit einer Säule pro Tag dargestellt.
```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by bin(TimeGenerated, 1d)
| render barchart
Und ein Balkendiagramm.
```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by AppDisplayName, bin(TimeGenerated, 1d)
| render columnchart
Diese Abfrage findet alle Anmeldungen für unser Konto, zählt die Anmeldungen pro Anwendung und erstellt dann eine einzelne Spalte für jeden Tag.

Wenn Sie möchten, dass jede Anwendung eine eigene Spalte hat, können Sie sie als ungestapelt festlegen.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize SigninCount=count() by bin(TimeGenerated, 1d) | render columnchart with (kind=unstacked)

Sie können auch die Achse und den Titel Ihres Diagramms gemäß KQL umbenennen.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by AppDisplayName, bin(TimeGenerated, 1d)
| render columnchart with (kind=unstacked, ytitle="Total Sign Ins", xtitle="Day", title="Application Signins Per Day")

Sie können Logik in Ihren summarize-Aktionen kombinieren, um dynamische Inhalte für Ihren render-Operator zu erstellen.```kql SigninLogs | where TimeGenerated > ago(14d) | where ResultType == "0" | summarize TeamsCount=countif(AppDisplayName has "Teams"), OneDrive=countif(AppDisplayName has "OneDrive"), SharePointCount=countif(AppDisplayName has "SharePoint") by bin(TimeGenerated, 1d) | render columnchart with (kind=unstacked, ytitle="Sign In Count", xtitle="Day", title="Teams vs OneDrive vs SharePoint Sign Ins Per Day")
Diese Abfrage durchsucht alle Anmeldungen in Ihrem Mandanten und zählt dann drei Gruppen – eine, bei der der Anzeigename der Anwendung „Teams“ enthält, eine, bei der der Anzeigename der Anwendung „OneDrive“ enthält, und eine, bei der der Anzeigename der Anwendung „SharePoint“ enthält – für jeden Tag der letzten 14 Tage. Anschließend wird das Ergebnis als nicht gestapeltes Säulendiagramm gerendert.

### Grundlagen von Parse und Split
Parse und Split sind zwei verschiedene Möglichkeiten, eine Datenzeichenfolge basierend auf Übereinstimmungen in mehrere Spalten zu erweitern. Viele Logs, die in Microsoft Sentinel aufgenommen werden, können als eine einzige lange Zeichenfolge vorliegen (z. B. sysmon). Mit Parse und Split können Sie sie in lesbare Daten umwandeln.
Für diese Beispiele verwenden wir die folgenden Testdaten```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account'
];
Diese Daten sind nur eine einzelne Zeichenfolge, die so aussieht.

Wir können bestimmte Datenübereinstimmungen mit dem Folgenden herausparsen.```kql let ExampleText = datatable(TestData:string) [ 'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account' ]; ExampleText | parse TestData with * 'Name=' DisplayName ',' * | project DisplayName
Dies parst alle Daten zwischen `Name=` und `,` in eine neue Spalte namens `'DisplayName'`.

Sie können mit demselben Befehl mehrere Spalten auslesen, indem Sie entlang der Zeichenfolge abgleichen.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account'
];
ExampleText
| parse TestData with * 'Name=' DisplayName ',UPNSuffix=' DomainSuffix ',AadTenantId=' AzureADTenantId ',' *
| project DisplayName, DomainSuffix, AzureADTenantId
Dies parst drei neue Spalten – DisplayName, DomainSuffix und AzureADTenantId

Denken Sie daran, dass KQL seine Operationen sequenziell ausführt. Sobald wir parsen, können wir anschließend erneut auf der neu erstellten Spalte parsen.```kql let ExampleText = datatable(TestData:string) [ 'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account', ]; ExampleText | parse TestData with * 'Name=' DisplayName ',UPNSuffix=' DomainSuffix ',AadTenantId=' AzureADTenantId ',' * | project DisplayName, DomainSuffix, AzureADTenantId | parse DomainSuffix with * '.' TopLevelDomain | project DisplayName, DomainSuffix, TopLevelDomain, AzureADTenantId
Dies parst unsere Domain weiter, um die Top-Level-Domain zu finden, in diesem Fall eine .com

Bei Verwendung des Parse-Operators durchläuft KQL alle Ihre Datenzeilen und gibt auch Ergebnisse zurück, bei denen keine Übereinstimmung vorliegt. Je nach Ihrer Datenstruktur können Sie also viele Zeilen mit leeren Daten erhalten. Wenn wir unsere Beispieldaten um eine weitere Datenzeile mit anderen Namen erweitern und dieselbe Abfrage ausführen, sehen Sie leere Ergebnisse.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account',
'Display=Reprise99,UPN=testdomain.com,AadDirectoryId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadObjectId=cf6f2df6-b754-48dc-b7bc-c8339caf211,Name=Test User,AccountType=account'
]
;
ExampleText
| parse TestData with * 'Name=' DisplayName ',UPNSuffix=' DomainSuffix ',AadTenantId=' AzureADTenantId ',' *
| project DisplayName, DomainSuffix, AzureADTenantId
| parse DomainSuffix with * '.' TopLevelDomain
| project DisplayName, DomainSuffix, TopLevelDomain, AzureADTenantId

Um dem entgegenzuwirken, können Sie den Operator 'parse-where' verwenden, der nur Ergebnisse zurückgibt, bei denen Übereinstimmungen mit Ihrer Abfrage vorliegen.```kql let ExampleText = datatable(TestData:string) [ 'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account', 'Display=Reprise99,UPN=testdomain.com,AadDirectoryId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadObjectId=cf6f2df6-b754-48dc-b7bc-c8339caf211,Name=Test User,AccountType=account' ] ; ExampleText | parse-where TestData with * 'Name=' DisplayName ',UPNSuffix=' DomainSuffix ',AadTenantId=' AzureADTenantId ',' * | project DisplayName, DomainSuffix, AzureADTenantId | parse DomainSuffix with * '.' TopLevelDomain | project DisplayName, DomainSuffix, TopLevelDomain, AzureADTenantId
Wir können sehen, dass wir wieder bei einem einzelnen Ergebnis angelangt sind, bei dem wir eine Übereinstimmung mit unserem Parse hatten.

Split trennt eine Textzeichenfolge anhand eines Trennzeichens in ein Array. Wenn wir zu unseren ursprünglichen Testdaten zurückgehen, können wir anhand des Kommas trennen.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account',
]
;
ExampleText
| extend SplitData = split(TestData,',')
| project SplitData
We will be returned an array with our string split out.

Split is index aware so if your data is in the same order, you can split directly into new columns.```kql let ExampleText = datatable(TestData:string) [ 'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account', ] ; ExampleText | extend Name = split(TestData,',')[0] | extend DomainSuffix = split(TestData,',')[1] | extend AzureADTenantId = split(TestData,',')[2] | extend AzureADUserId = split(TestData,',')[3] | extend DisplayName = split(TestData,',')[4] | extend AccountType = split(TestData,',')[5] | project Name, DomainSuffix, AzureADTenantId, AzureADUserId, DisplayName, AccountType
Wenn wir die Position unserer Daten innerhalb der Zeichenfolge kennen, können wir sie direkt in benannte Spalten aufteilen.

Sobald wir unsere Daten aufgeteilt haben, können wir sie abfragen, als wären sie von Anfang an strukturiert gewesen. Wenn wir also einen zweiten Datensatz zu unseren Daten hinzufügen und dann nach bestimmten Übereinstimmungen suchen, finden wir, wonach wir suchen.```kql
let ExampleText = datatable(TestData:string)
[
'Name=Reprise99,UPNSuffix=testdomain.com,AadTenantId=345c1234-a833-43e4-1d34-123440a5bcdd1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User,Type=account',
'Name=Reprise103,UPNSuffix=testdomain.com,AadTenantId=331c1234-a841-43e5-1d31-12220a5bcee1,AadUserId=cf6f2df6-b754-48dc-b7bc-c8339caf211,DisplayName=Test User 2,Type=account'
]
;
ExampleText
| extend Name = split(TestData,',')[0]
| extend DomainSuffix = split(TestData,',')[1]
| extend AzureADTenantId = split(TestData,',')[2]
| extend AzureADUserId = split(TestData,',')[3]
| extend DisplayName = split(TestData,',')[4]
| extend AccountType = split(TestData,',')[5]
| project Name, DomainSuffix, AzureADTenantId, AzureADUserId, DisplayName, AccountType
| where Name contains "Reprise99"
Wir erhalten nur einen Treffer, bei dem Name "Reprise99" enthält; unser zweiter Datensatz, bei dem Name "Reprise103" enthält, wird nicht gefunden.
