
مجموعة منسقة من استعلامات KQL الخاصة بـ Microsoft Sentinel ودروس تعليمية لاصطياد التهديدات، وتحليل سجلات تسجيل الدخول في Azure AD، واكتشاف الحالات الشاذة، وبناء أنماط كشف فعّالة.
بعض النصائح والحيل والأمثلة لاستخدام KQL مع Microsoft Sentinel.
Kusto Query Language هي اللغة المستخدمة عبر Azure Monitor وAzure Data Explorer وAzure Log Analytics (وهو ما يستخدمه Microsoft Sentinel داخليًا). لطالما وجدت هذا المخطط المرئي الخاص بـ KQL مفيدًا -

نريد استخدام KQL لإنشاء استعلامات دقيقة وفعّالة للعثور على التهديدات والاكتشافات والأنماط والشذوذ من داخل مجموعة البيانات الأكبر لدينا.
خذ الاستعلام أدناه كمثال```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent
عند تشغيل استعلام مثل هذا، يخبر السطر الأول Microsoft Sentinel بالجدول الذي يجب البحث عن البيانات فيه، لذا في هذه الحالة نريد البحث في جدول SigninLogs، وهو المكان الذي تُرسل إليه بيانات تسجيل الدخول إلى Azure AD. يمكنك الاطلاع على قائمة الجداول [هنا](https://docs.microsoft.com/en-us/azure/sentinel/data-source-schema-reference).
بعد ذلك، سيقوم Microsoft Sentinel بتنفيذ استعلامك بالتسلسل، حيث سيتم تشغيل كل سطر واحدًا تلو الآخر حتى الوصول إلى النهاية، أو حدوث خطأ. لذا دعونا نقسّم استعلامنا سطرًا بسطر.```kql
SigninLogs
إذاً أولاً اخترنا جدول SigninLogs الخاص بنا.```kql SigninLogs | where TimeGenerated > ago(14d)
بعد ذلك، نطلب من Sentinel النظر إلى بيانات آخر 14 يومًا في هذا الجدول.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
بعد ذلك، نطلب من Sentinel العثور على السجلات فقط حيث يكون UserPrincipalName مساوياً لـ "[email protected]"```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0"
ثم نبحث عن السجلات التي يكون فيها ResultType == 0 فقط، وهو ما يمثل تسجيل دخول ناجحًا إلى Azure AD.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"
بعد ذلك، نبحث عن عمليات تسجيل الدخول إلى Microsoft Teams فقط.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent
يستخدم سطرنا الأخير معامل project لإرجاع 4 حقول فقط من سجلاتنا، لذا سنرى فقط TimeGenerated وLocation وIPAddress وUserAgent المعادة من بيانات SigninLogs الخاصة بنا.
هكذا تبني الاستعلامات، والآن الأساسيات.
## الأساسيات
### أساسيات الوقت
تم تحسين Microsoft Sentinel وKQL بشكل كبير لمرشحات الوقت، لذا إذا كنت تعرف الفترة الزمنية للبيانات التي تريد البحث عنها، فيجب عليك تصفية النطاق الزمني فورًا. استرجاع آخر 14 يومًا من السجلات، ثم البحث عن اسم مستخدم مثل الاستعلام أدناه -```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
أكثر كفاءة بكثير من البحث أولاً عن اسم مستخدم ثم البحث عن الفترة الزمنية مثل هذا -```kql SigninLogs | where UserPrincipalName == "[email protected]" | where TimeGenerated > ago(14d)
يوفر KQL العديد من الخيارات للاستعلام عن فترات زمنية محددة.```kql
SigninLogs
| where TimeGenerated > ago(14d)
كما في المثال الأول، سيبحث هذا عن آخر 14 يومًا.```kql SigninLogs | where TimeGenerated > ago(14h)
يمكنك أيضًا العمل لساعات.```kql
SigninLogs
| where TimeGenerated > ago(14m)
والدقائق.
تدعم KQL أيضًا الاستعلام بين نطاقات زمنية -```kql SigninLogs | where TimeGenerated between (ago(14d) .. ago(7d))
سيعثر هذا على بيانات SigninLogs بين 14 يومًا و7 أيام مضت.```kql
SigninLogs
| where TimeGenerated between (ago(14h) .. ago(7h))
بين 14 ساعة و7 ساعات مضت.```kql SigninLogs | where TimeGenerated between (ago(14m) .. ago(7m))
وبين 14 دقيقة و7 دقائق مضت.
### أساسيات Where
Where هو عامل (Operator) ستستخدمه في كل استعلام تقريبًا تكتبه. بهذه الطريقة تخبر Microsoft Sentinel بالبحث عن بيانات محددة. بناء الجملة (Syntax) مهم جدًا مع عامل where. إذا استخدمنا نفس مثالنا.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
سيقوم هذا بالبحث في جدول SigninLogs لدينا، خلال آخر 14 يومًا، عن تطابقات تامة حيث يكون UserPrincipalName لدينا مساويًا لـ [email protected]. في KQL، العامل == حساس لحالة الأحرف، لذا إذا بحثت عن [email protected] وكان اسم المستخدم في الواقع [email protected]، فلن تحصل على أي نتائج. المكافئ غير الحساس لحالة الأحرف هو =~.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName =~ "[email protected]"
سيؤدي هذا إلى العثور على أي تطابقات لـ [email protected] بغض النظر عن حساسية حالة الأحرف.
بدلاً من يساوي، يمكننا أيضًا استخدام يحتوي.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName contains "reprise_99"
سيؤدي هذا إلى العثور على أي إدخالات سجل حيث يحتوي UserPrincipalName على reprise_99، إذا كان لديك بيانات [email protected] و [email protected]، فسيجد كلاهما. عامل contains ليس حساسًا لحالة الأحرف، ولكن يمكنك استخدام contains_cs لجعله حساسًا لحالة الأحرف.
يمكنك استخدام إما startswith أو endswith إذا كنت تبحث عن أنماط معينة.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName startswith "reprise_99"
SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName endswith "testdomain.com"
Both startswith and endswith are not case sensitive, but you can use the startswith_cs or endswith_cs to make them case sensitive.
If you are searching for full words (greater than four characters), in KQL you can use the has operator. Using 'has' is more efficient than 'contains' as the data is indexed for you.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName has "Teams"
سيجد هذا أي SigninLogs حيث يحتوي اسم عرض التطبيق على كلمة Teams فيه، وقد يتضمن ذلك "Microsoft Teams" و"Microsoft Teams Web Client"، وكلاهما يلبي الاستعلام.
إذا كنت تبحث عن كلمات متعددة، يمكنك استخدام has_any أو has_all.```kql SigninLogs | where TimeGenerated > ago(14d) | where AppDisplayName has_any ("Teams","Outlook")
سيعيد هذا نتائج حيث يحتوي اسم عرض التطبيق على إما "Teams" أو "Outlook"```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName has_all ("Teams","Outlook")
سيعيد هذا النتائج التي يحتوي اسم عرض التطبيق فيها على "Teams" و"Outlook".
إذا كنت لا تعرف الحقول التي تريد البحث فيها، يمكنك أيضًا استخدام أحرف البدل (wildcards)، فهي غير فعالة لكنها قد تضعك على المسار الصحيح للعثور على ما تبحث عنه.```kql SigninLogs | where TimeGenerated > ago(14d) | where * contains "reprise_99"
سيؤدي هذا إلى البحث في جدول SigninLogs عن أي حقل يحتوي على reprise_99.
يدعم عدد من هذه الخيارات أيضًا استخدام `!` لعكس الاستعلام والعثور على نتائج لا ينطبق عليها الشرط.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName != "[email protected]"
سيعثر هذا الاستعلام على جميع سجلات تسجيل الدخول (SigninLogs) حيث لا يساوي UserPrincipalName القيمة [email protected]```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName !contains "reprise_99"
سيعثر هذا الاستعلام على جميع SigninLogs حيث لا يحتوي UserPrincipalName على reprise_99```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName !has "Teams"
هذا الاستعلام سيجد SigninLogs حيث لا يحتوي اسم عرض التطبيق على "Teams".
يتيح لنا Project تحديد الأعمدة التي يتم إرجاعها في استعلامنا وترتيبها.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"
| project TimeGenerated, Location, IPAddress, UserAgent
يستعلم هذا الاستعلام عن بيانات SigninLogs من آخر 14 يومًا، حيث تكون UserPrincipalname مساوية لـ [email protected]، وحيث تكون ResultType هي 0، وحيث يساوي اسم عرض التطبيق "Microsoft Teams"، ثم لكل تطابق في هذا الاستعلام يقوم بإرجاع TimeGenerated وLocation وIPAddress وUserAgent.
يمكننا إعادة تسمية الأعمدة كجزء من نفس الدالة.```kql
| project LogTime=TimeGenerated, SigninLocation=Location, IP=IPAddress, Agent=UserAgent
يُرجِع هذا البيانات نفسها، لكنه يعيد تسمية الأعمدة إلى LogTime وSigninLocation وIP وAgent.
يمكننا حتى معالجة المخرجات مباشرةً باستخدام عامل project.```kql | project LocalTime=TimeGenerated+5h, Location, IPAddress, UserAgent
يعيد هذا نفس البيانات، لكنه يغيّر اسم TimeGenerated إلى LocalTime ويحوّله إلى منطقة زمنية +5h إذا كنت تعمل في تلك المنطقة الزمنية.
project-away هو عكس project وسيزيل الأعمدة من استعلامك.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| project-away UserAgent
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"
في هذا الاستعلام نقوم بإزالة UserAgent. تذكّر، إذا أزلت عمودًا فلن تتمكن من الوصول إليه لاحقًا في استعلامك.
تُنتج Summarize جدولًا يجمّع محتوى استعلامك. تمتلك Summarize عددًا من دوال التجميع الأساسية. إذا أخذنا مثال الاستعلام نفسه مرة أخرى، يمكننا معالجة النتائج بطرق مختلفة باستخدام summarize.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize count() by AppDisplayName
سيقوم هذا الاستعلام بالبحث في جدول SigninLogs عن أي أحداث في آخر 14 يومًا، وعن أي تطابقات لـ [email protected]، حيث تكون النتيجة نجاحًا (ResultType == 0)، ثم تلخيص هذه الأحداث حسب اسم عرض التطبيق.
يمكنك اختياريًا تسمية عمود النتيجة.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppCount=count() by AppDisplayName
يؤدي هذا إلى إرجاع نفس البيانات ولكنه يحدّث اسم العمود المُرجَع إلى AppCount.
بدلاً من العدد الإجمالي، يمكنك تلخيص عدد مميز.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize DistinctAppCount=dcount(AppDisplayName) by AppDisplayName
سيؤدي هذا إلى إرجاع سجل واحد لكل تطبيق مميز قام [email protected] بتسجيل الدخول إليه.
يمكنك استخدام الدالتين arg_max وarg_min لإرجاع أحدث أو أقدم سجل يطابق استعلامك.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize arg_max(TimeGenerated, *) by UserPrincipalName
يبحث هذا الاستعلام عن جميع سجلات تسجيل الدخول خلال آخر 14 يومًا، التي تحتوي على [email protected] كـ UserPrincipalname، والتي تكون ناجحة، ثم يُرجع أحدث سجل.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize arg_min(TimeGenerated, *) by UserPrincipalName
هذا هو نفسه لكنه يُرجع أقدم سجل.
يمكنك استخدام countif لتوفير منطق لعمليات الجمع الخاصة بك.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize TeamsLogons=countif(AppDisplayName has "Teams"), SharePointLogons=countif(AppDisplayName has "SharePoint")
يلخّص هذا البيانات في عمودين جديدين، TeamsLogons حيث يحتوي اسم عرض التطبيق على "Teams" وSharePointLogons حيث يحتوي اسم عرض التطبيق على "SharePoint"
يمكنك مواصلة معالجة بياناتك من خلال إخبار KQL بوضع بياناتك في 'bins' زمنية.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count() by AppDisplayName, bin(TimeGenerated, 1d)
يعيد هذا نفس البيانات التي يعيدها مثال summarize الأول لدينا، ثم يجمع تلك البيانات في صناديق 1d.
يمكنك دمج هذه الدوال معًا حيثما كان ذلك مفيدًا.```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)
هذا مزيج من دالتي countif و bin لدينا، حيث نلخّص استنادًا إلى اسم عرض التطبيق ونضع النتائج أيضًا في صناديق 1d.
يمكنك إنشاء مجموعة من العناصر داخل استعلام.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppList=make_set(AppDisplayName) by UserPrincipalName
سيؤدي هذا إلى إخراج قائمة بالتطبيقات التي سجّل [email protected] الدخول إليها في قائمة تسمى AppList.
يمكنك دمج هذا مع سلة الوقت الخاصة بنا.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppList=make_set(AppDisplayName) by UserPrincipalName, bin(TimeGenerated, 1d)
سيؤدي ذلك إلى إنشاء قائمة بالتطبيقات التي سجّل الدخول إليها [email protected]، مقسمة إلى قائمة واحدة لكل يوم.
يتيح عامل العرض (render operator) لاستعلامات KQL تصور البيانات بتنسيقات مختلفة مثل المخططات الدائرية، أو المخططات الزمنية أو المساحية، والمخططات العمودية والشرطية.
إذا استخدمنا نفس مثالنا من جدول Signinlogs، يمكننا أن نرى كيف يمكننا تصور البيانات بطرق مختلفة.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count()by AppDisplayName | render piechart
يلخص هذا الاستعلام جميع التطبيقات التي سجّل الدخول إليها [email protected] خلال آخر 14 يومًا، ثم يعرض الناتج كمخطط دائري.

يمكنك أيضًا العرض كمخطط أعمدة.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppCount=count()by AppDisplayName
| render columnchart

أو مخطط شريطي.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize AppCount=count()by AppDisplayName | render barchart

بالنسبة لبيانات الوقت، قم أولاً بتلخيص بياناتك في 'bins' زمنية كما هو موضح في قسم التلخيص، ويمكنك بعد ذلك تمثيل بياناتك بيانياً على مدى فترة زمنية.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by bin(TimeGenerated, 1d)
| render timechart
يُظهر هذا جميع عمليات تسجيل الدخول بواسطة [email protected] يوميًا على مدار آخر 14 يومًا ويعرضها كمخطط زمني.

يمكنك أيضًا استخدام render كـ areachart.```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize SigninCount=count() by bin(TimeGenerated, 1d) | render areachart

يمكن أيضًا استخدام المخططات العمودية والمخططات الشريطية مع بيانات الوقت. ستحصل على عمود أو شريط لكل 'bin' زمني عبر الفترة الزمنية الأكبر.```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by bin(TimeGenerated, 1d)
| render columnchart
هذا هو نفس الاستعلام المستخدم في المخطط الزمني، لكنه يُعرض كمخطط أعمدة مع عمود لكل يوم.
```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by bin(TimeGenerated, 1d)
| render barchart
ومخطط أعمدة.

مع المخططات العمودية أو الشريطية، يمكنك تكديسها معًا (وهو الوضع الافتراضي).```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize SigninCount=count() by AppDisplayName, bin(TimeGenerated, 1d)
| render columnchart
يبحث هذا الاستعلام عن جميع عمليات تسجيل الدخول لحسابنا، ويحسب عمليات تسجيل الدخول لكل تطبيق، ثم ينشئ عمودًا واحدًا لكل يوم.

إذا كنت تريد أن يكون لكل تطبيق عموده الخاص، يمكنك ضبطه ليكون غير مكدس.```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)

يمكنك أيضًا إعادة تسمية المحور وعنوان الرسم البياني الخاص بك بما يتوافق مع KQL.```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")

يمكنك دمج المنطق في إجراءات summarize الخاصة بك لبناء محتوى ديناميكي لعامل render الخاص بك.```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")
يبحث هذا الاستعلام في جميع عمليات تسجيل الدخول إلى مستأجرك، ثم يحسب ثلاث مجموعات - واحدة حيث يحتوي اسم عرض التطبيق على "Teams"، وواحدة حيث يحتوي اسم عرض التطبيق على "OneDrive" وواحدة حيث يحتوي اسم عرض التطبيق على "SharePoint" لكل يوم على مدار الأيام الأربعة عشر الماضية، ثم يعرضها كمخطط أعمدة غير مكدس.

### أساسيات Parse و Split
إن Parse و Split طريقتان مختلفتان لتوسيع سلسلة بيانات إلى عدة أعمدة بناءً على التطابقات. قد تأتي الكثير من السجلات المستوعبة في Microsoft Sentinel كسلسلة واحدة طويلة (مثل sysmon)، وتتيح لك Parse و Split معالجتها إلى بيانات قابلة للقراءة.
في هذه الأمثلة، سنستخدم بيانات الاختبار التالية```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'
];
هذه البيانات مجرد سلسلة واحدة تبدو بهذا الشكل.

يمكننا استخراج مطابقات بيانات معينة باستخدام ما يلي```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
سيؤدي هذا إلى تحليل جميع البيانات بين Name= و , في عمود جديد يسمى 'DisplayName'.

يمكنك تحليل أعمدة متعددة داخل نفس الأمر عن طريق المطابقة على طول السلسلة النصية```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
يقوم هذا بتحليل ثلاثة أعمدة جديدة - DisplayName وDomainSuffix وAzureADTenantId

تذكّر أن KQL ينفّذ عملياته بشكل تسلسلي، وبمجرد أن نحلّل يمكننا التحليل مرة أخرى على العمود المُنشأ حديثًا.```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
يقوم هذا بتحليل نطاقنا بشكل أكبر للعثور على النطاق من المستوى الأعلى، وهو في هذه الحالة .com

عند استخدام عامل التشغيل parse، سيمر KQL عبر جميع صفوف بياناتك وسيُرجع حتى النتائج التي لا يوجد لها تطابق. لذلك اعتمادًا على بنية بياناتك، قد ينتهي بك الأمر إلى العديد من الصفوف ذات البيانات الفارغة. إذا قمنا بتوسيع بيانات المثال لدينا لتشمل صفًا آخر من البيانات، بأسماء مختلفة، وشغّلنا نفس الاستعلام، سترى نتائج فارغة.```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

لمواجهة ذلك، يمكنك استخدام عامل التشغيل 'parse-where'، الذي لا يُرجع سوى النتائج التي تحتوي على تطابقات مع استعلامك.```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
يمكننا أن نرى أننا عدنا إلى نتيجة واحدة حيث كان لدينا تطابق في التحليل الذي قمنا به.

تقوم الدالة Split بفصل سلسلة نصية إلى مصفوفة بناءً على محدد. إذا عدنا إلى بيانات الاختبار الأصلية، يمكننا إجراء التقسيم بناءً على علامة الفاصلة.```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
سيتم إرجاع مصفوفة مع تقسيم السلسلة النصية الخاصة بنا.

التقسيم واعٍ بالفهرس، لذا إذا كانت بياناتك بنفس الترتيب، يمكنك التقسيم مباشرة إلى أعمدة جديدة.```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
إذا كنا نعرف موقع بياناتنا داخل السلسلة، يمكننا تقسيمها مباشرة إلى أعمدة مسماة.

بمجرد تقسيم بياناتنا، يمكننا الاستعلام عنها كما لو كانت منظمة منذ البداية. لذا إذا أضفنا سجلًا ثانيًا إلى بياناتنا، ثم قمنا بالاستعلام عن تطابقات محددة، فسنجد ما نبحث عنه.```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"
نحصل على نتيجة واحدة فقط، حيث يحتوي الاسم على "Reprise99"، أما السجل الثاني الذي يحتوي اسمه على "Reprise103" فلا يتم العثور عليه.
