פוורשל לניתוח XML: קריאה ואימות

XML נמצאת בכל מקום. למרות השימוש המעצבן שלה בסוגריים מסולסלים, פורמט XML עדיין משמש בנרחבות. קבצי התצורה, הזרמים של RSS, קבצי Office (ה-'x' ב-.docx) הם רק חלק קטן מהרשימה. שימוש ב-PowerShell כדי לנתח קבצי XML הוא שלב חיוני במסע ה-PowerShell שלך.

מדריך זה יראה לך איך לנתח קבצי XML באמצעות PowerShell ולאמת אותם. זה ידריך אותך מאפס לגיבור בכל היבטי השגת נתוני XML והערכתם. תינתן לך כלים שיעזרו לך לאמת את תקינות הנתונים ב-XML ולעצור נתונים שגויים כבר בשער התסריטים שלך!

דרישות מוקדמות

על מנת לעקוב אחרי החומר המוצג, יש לך צורך ב:

  • גרסת PowerShell 3.0 ומעלה. הדוגמאות נוצרו ב- Windows PowerShell v5.1.
  • Notepad++, Visual Studio Code או עורך טקסט אחר שמבין XML.

ניתוח אלמנטים של PowerShell XML עם Select-Xml

נתחיל בכיסוי של אחת הדרכים הפופולריות והקלות ביותר להשתמש ב-PowerShell לניתוח XML, וזו באמצעות Select-Xml. פקודת הקובץ Select-Xml מאפשרת לך לספק קובץ XML או מחרוזת יחד עם "מסנן" הידוע בשם XPath כדי לאחזר מידע מסוים.

XPath הוא שרשרת של שמות אלמנטים. הוא משתמש בתחביר "דומה לנתיב" כדי לזהות ולנווט בצמתים במסמך XML.

נניח שיש לך קובץ XML עם מחשבים רבים ותרצה להשתמש ב-PowerShell כדי לפענח את קובץ ה-XML הזה. לכל מחשב יש אלמנטים שונים כמו שם, כתובת IP ואלמנט Include לצורך הכללה בדוח.

אלמנט הוא חלק XML עם תג פתיחה ותג סגירה, אולי עם טקסט בינהם, כמו <Name>SRV-01</Name>

<Computers>
	<Computer>
		<Name>SRV-01</Name>
		<Ip>127.0.0.1</Ip>
		<Include>true</Include>
	</Computer>	
	<Computer>
		<Name>SRV-02</Name>
		<Ip>192.168.0.102</Ip>
		<Include>false</Include>
	</Computer>	
	<Computer>
		<Name>SRV-03</Name>
		<Ip>192.168.0.103</Ip>
		<Include>true</Include>
	</Computer>	
</Computers>

תרצה להשתמש ב-PowerShell כדי לפענח את קובץ ה-XML ולקבל את שמות המחשבים. כדי לעשות זאת, תוכל להשתמש בפקודת Select-Xml.

בקובץ למעלה, שמות המחשבים מופיעים בטקסט הפנימי (InnerXML) של אלמנט ה-Name.

InnerXML הוא הטקסט בין שני התגי אלמנטים.

כדי למצוא את שמות המחשבים, תספק תחילה את ה-XPath המתאים (/Computers/Computer/Name). תחביר זה של XPath יחזיר רק את הצמתים Name תחת האלמנטים Computer. לאחר מכן, כדי לקבל רק את InnerXML של כל אלמנט Name, תשתמש במאפיין Node.InnerXML על כל אלמנט בעזרת לולאת ForEach-Object.

Select-Xml -Path C:\Work\computers-elm.xml -XPath '/Computers/Computer/Name' | ForEach-Object { $_.Node.InnerXML }

שימוש ב-PowerShell כדי לפענח מאפייני XML עם Select-Xml

עכשיו בואו נטפח בבעיה הזו של מציאת שמות מחשב מזווית שונה. הפעם, במקום התיאורים של המחשב המיוצגים עם רכיבים XML, הם מיוצגים עם תכונות XML.

תכונה היא חלק מפתח/ערך כגון name="SRV-01". תכונות מופיעות תמיד בתוך התגית הפתיחה, מיד אחרי שם התגית.

למטה נמצא קובץ XML עם התיאורים של המחשב מיוצגים עם תכונות. עכשיו אפשר לראות כל תיאור כתכונה ולא כרכיב.

<Computers>
	<Computer name="SRV-01" ip="127.0.0.1" include="true" />
	<Computer name="SRV-02" ip="192.168.0.102" include="false" />
	<Computer name="SRV-03" ip="192.168.0.103" include="true" />
</Computers>

מאחר שכל תיאור הוא תכונה הפעם, עלינו לשנות את ה- XPath כדי למצוא רק אלמנטים של מחשב. לאחר מכן, באמצעות cmdlet של ForEach-Object, נמצא שוב את ערך התכונה name.

Select-Xml -Path C:\Work\computers-attr.xml -XPath '/Computers/Computer' | ForEach-Object { $_.Node.name }

ובאמת, גם זה מביא לתוצאות זהות: SRV-01, SRV-02 ו- SRV-03 :

Reading data using Select-Xml

בשני המקרים, בין אם אתה קורא רכיבים או תכונות, תחביר של Select-Xml הוא מסורבל: הוא מכריח אותך להשתמש בפרמטר XPath, לאחר מכן לשלוף את התוצאה ללולאה, ולבסוף לחפש את הנתונים תחת המאפיין Node.

מזל ש-PowerShell מציע דרך נוחה ואינטואיטיבית יותר לקרוא קבצי XML. PowerShell מאפשר לך לקרוא קבצי XML ולהמיר אותם ל־ XML אובייקטים.

קשור: שימוש במאיץי סוגי נתונים של PowerShell כדי להאיץ את הקידוד

יצירת מחרוזת XML לאובייקטים

דרך נוספת להשתמש בפוורשל כדי לפענח XML היא להמיר את ה-XML הזה לאובייקטים. הדרך הקלה ביותר לעשות זאת היא באמצעות ה־[xml] אקסלרטור סוג.

על ידי הוספת קידומת שמות המשתנים עם [xml], פוורשל ממיר את ה-XML הטקסט הרגיל לאובייקטים שבאפשרותך לעבוד איתם.

[xml]$xmlElm = Get-Content -Path C:\Work\computers-elm.xml
[xml]$xmlAttr = Get-Content -Path C:\Work\computers-attr.xml

קריאת אלמנטים באובייקט XML

עכשיו גם המשתנה $xmlElm והמשתנה $xmlAttr הם אובייקטים של XML שניתן להתייחס אליהם באמצעות הסימון בנקודה. אולי אתה צריך למצוא את כתובת ה-IP של כל אלמנט מחשב. מאחר וקובץ ה-XML הוא אובייקט, אתה יכול לעשות זאת פשוט על ידי התייחסות לאלמנט ה-IP.

$xmlElm.Computers.Computer.ip

החל מגרסת PowerShell 3.0, אובייקט ה-XML מקבל את ערך המאפיין באמצעות התחביר אותו משמש לקריאת טקסט האלמנט. לכן, ערכי כתובות ה-IP נקראים מהקובץ המאפיינים בשפה הזו הדי זהה לשפה שבה נקראים ערכי האלמנטים.

קריאת מאפייני XML

באמצעות הסימון בנקודה הזהה, תוכל לקרוא גם מאפייני XML ללא קשר להבדלים במבנה ה-XML.

$xmlElm.Computers.Computer.ip
$xmlAttr.Computers.Computer.ip

והתוצאות מציגות כי שני האובייקטים קיבלו את אותם נתונים, כל אחד ממקובץ המתאים לו:

PowerShell to Parse XML : Reading XML Attributes

יותר מזה, עם האובייקט, לפי שהוא נטען לזיכרון, אתה גם מקבל IntelliSense שמאפשר להשלים אוטומטית באמצעות כריתות אם אתה משתמש ב- PowerShell ISE. לדוגמה, כפי שמוצג למטה.

IntelliSense and tab completion for XML object

עובר על נתוני ה-XML

פעם שאתה יודע לקרוא ישירות קבצי XML לאובייקט XML (על ידי הנאמנות על ה־[xml] אקסלרטור סוג), יש לך את כל הכוח המלא של אובייקטים בפוורשל.

אמרו, לדוגמה, שאתה נדרש לעבור דרך כל המחשבים שמופיעים בקובץ ה-XML עם המאפיין include="true" כדי לבדוק את מצב החיבור שלהם. הקוד למטה מראה כיצד ניתן לבצע זאת.

סקריפט זה הוא:

  • קריאת הקובץ והמרתו לאובייקט XML.
  • עוברים על המחשבים הרלוונטיים כדי לקבל את מצב החיבור שלהם.
  • לבסוף, שולחים את אובייקט תוצאת הפלט לצינור.
 ## ממירים את טקסט הקובץ לאובייקט XML
 [xml]$xmlAttr = Get-Content -Path C:\Work\computers-attr.xml

 ## עוברים דרך קבוצת המחשבים עם include="true"
 $xmlAttr.Computers.Computer | Where-Object include -eq 'true' |  ForEach-Object {
     ## בודקים האם המחשב הנוכחי מחובר
     if(Test-Connection -ComputerName $_.ip -Count 1 -Quiet)
     {
         $status = 'Connection OK'
     }
     else
     {
         $status = 'No Connection'
     }

     ## מוציאים את אובייקט התוצאה
     [pscustomobject]@{
         Name = $_.name
         Ip = $_.ip
         Status = $status
     }
 }

והתוצאות של הסקריפט מוצגות למטה:

Connection status results

סכמות XML

בקטע הקודם, ראית שני קבצי XML שונים המייצגים סט נתונים בשני אופנים שונים. הדרכים האלה נקראות סכמות XML. סכמת XML מגדירה את היבנים החוקיים של מסמך XML ספציפי:

  • שמות האלמנטים והמאפיינים שיכולים להופיע במסמך ספציפי זה.
  • מספר וסדר של אלמנטים ילד.
  • סוגי הנתונים של אלמנטים ומאפיינים.

הסכמה מגדירה בעיקר את מבנה ה-XML.

אימות נתוני XML

קובץ XML עשוי להכיל את התחביר הנכון (עורכים כמו Notepad++ יתריעו אם לא), אך הנתונים בו עשויים לא לתאם את דרישות הפרויקט. כאן נכנסת הסכמה לתמוך. כשאתה משתמש בנתוני XML, עליך לוודא שכל הנתונים כולם תקינים לפי הסכמה המוגדרת.

הדבר האחרון שאתה רוצה הוא לגלות שגיאות בנתונים בזמן ריצה, כשהסקריפט שלך כבר ביצע פעולות בלתי-הפיכות במערכת הקבצים ובמערכת הרישומים, עד אותו זמן.

אז, איך אפשר לבדוק מראש שהנתונים נכונים? נראה תחילה כמה סוגי שגיאות אפשריים.

שגיאות אפשריות בנתוני XML

בדרך כלל, השגיאות שנמצאות בקבצי XML שייכות לאחד משני הקטגוריות הבאות: שגיאות במטה-נתונים ושגיאות בתוך הנתונים עצמם.

שגיאות במטה-נתונים של XML

קובץ זה MorePeople.xml למטה, תקף בצורת התחביר. ניתן לראות שבקובץ יש לו אלמנט People אחד (אלמנט השורש) עם שלושה אלמנטים Person בתוכם. המבנה הזה הוא מקובל לחלוטין. עדיין, יש בו יוצא מן הכלל אחד, אתה יכול לראות את זה?

<People>
	<Person Name="Debra" County="Canada" IsAdmin="true" />
	<Person Name="Jacob" Country="Israel" IsAdmin="true" />
	<Person Name="Olivia" Country="Cyprus" IsAdmin="false" />
</People>

אל תדאג אם לא, הוא פשוט מוסתר. הבעיה נמצאת ברמת האלמנט הפנימי הראשון:

מה שהיה אמור להיות Country כתבו בטעות, וקנדה הופכת ל-County.

שגיאות בנתוני XML

לאחר תיקון הבעיה ב-Country ב־MorePeople.xml, סתם עוברה בעיה נוספת:

<People>
	<Person Name="Debra" Country="Canada" IsAdmin="yes" />
	<Person Name="Jacob" Country="Israel" IsAdmin="true" />
	<Person Name="Olivia" Country="Cyprus" IsAdmin="false" />
</People>

המטה-נתונים, כלומר, האלמנטים והתכונות, בסדר. אז מה שגוי? הפעם הבעיה, שוב בשורת הפתיחה הראשונה Person , נמצאת באחת הערכים . מישהו החליט ש-yes הוא מספיק טוב כתחליף ל-true – אך קוד כמו זה למטה יכשל בקבלת האלמנט הראשון מכיוון שהוא מחפש true, ולא yes:

$peopleXml.People.Person | Where-Object IsAdmin -eq 'true'

יצירת סכימת XML

עכשיו שאתה יודע אילו סוגי שגיאות עשויות להתרחש, הגיע הזמן להראות כיצד קובץ סכימה עוזר. הצעד הראשון הוא ליצור קובץ נתונים דוגמתי. הדוגמה יכולה להיות הדוגמה הקטנה ביותר ולכלול רק אלמנט פנימי אחד. עבור הדוגמאות האלה, ניצור קובץ דוגמה כזה People.xml:

<People>
	<Person Name="Jeff" Country="USA" IsAdmin="true" />
</People>

עכשיו נבנה פונקציית PowerShell למטה ונשתמש בה עם הנתונים הדוגמתיים כדי ליצור את סכימת ה-.xsd.

function New-XmlSchema
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ })]
        [ValidatePattern('\.xml')]
        [string]$XmlExample
    ) 

    $item = Get-Item $XmlExample
    $dir = $item.Directory
    $name = $item.Name
    $baseName = $item.BaseName

    ## בניית נתיב הסכימה על ידי החלפת '.xml' ב-'.xsd'
    $SchemaPath = "$dir\$baseName.xsd"

    try
    {
        $dataSet = New-Object -TypeName System.Data.DataSet
        $dataSet.ReadXml($XmlExample) | Out-Null
        $dataSet.WriteXmlSchema($SchemaPath)
        
        ## הצגת הקובץ סכימה המתקבל
        Get-Item $SchemaPath
    }
    catch
    {
        $err = $_.Exception.Message
        Write-Host "Failed to create XML schema for $XmlExample`nDetails: $err" -ForegroundColor Red
    }
}

העתק את הפונקציה ל-ISE שלך או לעורך PowerShell האהוב עליך, טען אותה לזכרון והשתמש בה כדי ליצור את הסכימה. עם הפונקציה טעונה, הקוד ליצירת הסכימה הוא ליינר זה:

New-XmlSchema -XmlExample 'C:\Work\People.xml'

התוצאות תציג את הנתיב אל סכימה החדשה שנוצרה:

Creating XML schema from a sample data file

שימוש בקובץ הסכימה כדי לוודא את הנתונים שלך

ראה את המיקום שצוין על ידי התוצאות למעלה. אם הקובץ .xsd נמצא שם, אתה בדרך הנכונה לראות את האימות בפעולה. לשלב האישור, השתמש בפונקציה הבאה:

function Test-XmlBySchema
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ })]
        [ValidatePattern('\.xml')]
        [string]$XmlFile,
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ })]
        [ValidatePattern('\.xsd')]
        [string]$SchemaPath
    )

    try
    {
        [xml]$xml = Get-Content $XmlFile
        $xml.Schemas.Add('', $SchemaPath) | Out-Null
        $xml.Validate($null)
        Write-Verbose "Successfully validated $XmlFile against schema ($SchemaPath)"
        $result = $true
    }
    catch
    {
        $err = $_.Exception.Message
        Write-Verbose "Failed to validate $XmlFile against schema ($SchemaPath)`nDetails: $err"
        $result = $false
    }
    finally
    {
        $result
    }
}

טען את הפונקציה לזיכרון, והשתמש בה לאימות הקובץ MorePeople.xml מתוך שני הדוגמאות לשגיאות. כדי להפעיל את האימות, השתמש בפקודה הבאה:

Test-XmlBySchema -XmlFile 'C:\Work\MorePeople.xml' -SchemaPath 'C:\Work\People.xsd' -Verbose

התוצאות הממשיות תלויות בתוכן של MorePeople.xml.

בוא נראה שתי דוגמאות. שים לב שכאשר הקובץ MorePeople.xml ללא שגיאות, הפונקציה למעלה תחזיר True.

Validation success

כאשר הקובץ MorePeople.xml מכיל נתונים שגויים (המפתח Country כתוב באופן שגוי כ County), הפונקציה תחזיר פרטי כישלון ותחזיר False.

Validation failure – wrong attribute name detected

כפי שאתה יכול לראות, השגיאה שצוינה בפלט מפורט היא מאוד משמעותית: היא מכוונת אל קובץ האשם ומצביעה על הרכיב המדויק בו שבו התרחשה הבעיה.

עיצוב סכמת האימות

בוא נסתכל על קובץ הסכמה, ולאחר מכן נראה איך נוכל לשפר אותו עוד יותר.

הסכמה שנוצרה על ידי New-XmlSchema בברירת מחדל היא הבאה:

<?xml version="1.0" standalone="yes"?>
<xs:schema id="People" xmlns="" xmlns:xs="<http://www.w3.org/2001/XMLSchema>" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="People" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="Person">
          <xs:complexType>
            <xs:attribute name="Name" type="xs:string" />
            <xs:attribute name="Country" type="xs:string" />
            <xs:attribute name="IsAdmin" type="xs:string" />
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>

הסכמה הברירת מחדל למעלה היא טובה, אך לא מושלמת. באמת שהיא תפסה את הטעות במאפיין Country. אך, אם תשאיר את הסכמה כפי שהיא, במקרה שאילו ציפיות אחרות שיכולות להיות לך לא יתקיימו – כאלו בעיות לא יודו כשגיאות על ידי האימות של Test-XmlBySchema. בוא נפתור זאת.

הטבלה למטה מציגה מקרים שאינם נחשבים לשגיאות אימות ויתרו בלתי שלטוניות על ידי Test-XmlBySchema. בכל שורה, העמודה הימנית מציגה איך לשנות באופן ידני את הסכימה כדי להוסיף תמיכה בהגנה הנדרשת.

A modified version of the schema file, with all protections added, is shown right after the table.

הוספת הגדרה לסכימת ברירת המחדל – דוגמאות

Required Behavior Relevant setting on the schema file
At least one Person element Set the minOccurs value of the Person element to 1
Make Name, Country and IsAdmin attributes mandatory Add use=”required” to each of these attributes declaration
Allow only true/false values for IsAdmin Set type=”xs:boolean” for the IsAdmin declaration
Allow only Country names between 3 and 40 characters Use the xs:restriction (see detailed explanation after the schema text)
<?xml version="1.0" standalone="yes"?>
<xs:schema id="People" xmlns="" xmlns:xs="<http://www.w3.org/2001/XMLSchema>" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="People" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="Person">
          <xs:complexType>
            <xs:attribute name="Name" type="xs:string" use="required" />
	    <xs:attribute name="Country" use="required">
	      <xs:simpleType>
		<xs:restriction base="xs:string">
		  <xs:minLength value="3"/>
		  <xs:maxLength value="40"/>
		</xs:restriction>
	      </xs:simpleType>
	    </xs:attribute>	
            <xs:attribute name="IsAdmin" type="xs:boolean" use="required" />
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>

עם ההגבלה הבוליאנית במקום עבור המאפיין IsAdmin בדוגמה, עליה להיות ערך באותיות קטנות true או false.

אימות אורך מחרוזת עם xs:restriction

אימות אורך המחרוזת קצת מורכב. לכן, אף שהוא מוצג למעלה כחלק מהסכימה המותאמת, יש להקדיש קצת יותר תשומת לב.

פריט הסכימה המקורי עבור המאפיין Country (לאחר הוספת use="required" באופן ידני) הוא כדלקמן:

<xs:attribute name="Country" type="xs:string" use="required" />

כדי להוסיף את ההגנה על האורך, יש להוסיף את אלמנט <xs:simpleType>, ובתוכו, את <xs:restriction base="xs:string">. ההגבלה הזו מכילה בתוכה את הגבלות הכרחיות המצוינות ב־xs:minLength ו־xs:minLength.

לאחר כל השינויים האלה, ההכרזה הסופית של xs:attribute גדלה משורה יחידה לצומת של 8 שורות:

<xs:attribute name="Country">
  <xs:simpleType>
	<xs:restriction base="xs:string">
	  <xs:minLength value="3"/>
	  <xs:maxLength value="40"/>
	</xs:restriction>
  </xs:simpleType>
</xs:attribute>

אם הראש שלך לא סובל אחרי ההסבר למעלה, יש לך את הזכות לראות את האימות בפעולה. כדי לעשות זאת, נקצר במכוון את הערך Canada לשתי סילבות: Ca

עם שם המדינה הקצר במקום, ו־MorePeople.xml שמור, אתה מוכן להפעיל את פקודת האימות למטה:

Test-XmlBySchema -XmlFile 'C:\Work\MorePeople.xml' -SchemaPath 'C:\Work\People.xsd' -Verbose

והתוצאות באמת מראות שסכימת ה-XML המורכבת עשתה את עבודתה:

A string length error detected by the schema validation

אימות סכימת XML יכול להתרחב במורכבות ולאמת כמעט כל דפוס שתוכל לחשוב עליו, במיוחד כאשר משולבים ביטויים רגולריים.

Source:
https://adamtheautomator.com/powershell-parse-xml/