XML 파싱을 위한 PowerShell: 읽고 유효성 검사하기

XML은 어디에서나 사용됩니다. 꺽쇠 괄호를 사용하는 방식이 지루하긴 하지만, 여전히 XML 형식은 널리 사용됩니다. 구성 파일, RSS 피드, Office 파일(.docx의 ‘x’)은 그저 일부 목록에 불과합니다. XML 파일을 구문 분석하기 위해 PowerShell을 사용하는 것은 PowerShell 여행에서 필수적인 단계입니다.

이 튜토리얼에서는 PowerShell이 XML 파일을 구문 분석하고 유효성을 검사하는 방법을 보여줍니다. 이를 통해 XML 데이터 획득과 평가의 모든 측면에 대해 제로에서 히어로로 이동할 수 있습니다. 스크립트의 게이트에서 XML 데이터 무결성을 검증하고 잘못된 데이터를 막는 데 도움이 되는 도구를 제공받게 될 것입니다!

전제 조건

제시된 자료를 따라하기 위해서는 다음이 필요합니다:

  • PowerShell 버전 3.0 이상. 예제는 Windows PowerShell v5.1에서 작성되었습니다.
  • Notepad++, Visual Studio Code 또는 XML을 이해하는 다른 텍스트 편집기.

Select-Xml을 사용하여 PowerShell에서 XML 요소 구문 분석하기

먼저 PowerShell을 사용하여 XML을 구문 분석하는 가장 인기 있는 방법 중 하나인 Select-Xml에 대해 알아보겠습니다. Select-Xml cmdlet을 사용하면 XML 파일이나 문자열과 XPath라는 “필터”를 함께 제공하여 특정 정보를 추출할 수 있습니다.

XPath는 요소 이름의 연속입니다. 이는 XML 문서의 노드를 식별하고 탐색하기 위해 “경로 형식”의 구문을 사용합니다.

예를 들어, 컴퓨터에 대한 여러 요소(이름, IP 주소 및 보고서에 포함될 Include 요소)가 포함된 XML 파일이 있다고 가정해 보겠습니다.

요소는 개방 태그와 폐쇄 태그로 구성된 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 명령을 사용할 수 있습니다.

위의 파일에서 컴퓨터 이름은 Name 요소의 내부 텍스트(InnerXML)에 나타납니다.

InnerXML은 두 요소 태그 사이의 텍스트입니다.

컴퓨터 이름을 찾으려면 먼저 적절한 XPath(/Computers/Computer/Name)을 제공해야 합니다. 이 XPath 구문은 Computer 요소 아래의 Name 노드만 반환합니다. 그런 다음 각 요소에 대해 ForEach-Object 루프를 사용하여 각 Name 요소의 InnerXML만 가져옵니다.

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

Select-Xml을 사용하여 PowerShell에서 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를 조금 수정하여 컴퓨터 요소만 찾도록 조정합니다. 그런 다음 다시 ForEach-Object cmdlet을 사용하여 name 속성의 값을 찾습니다.

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

실제로도 같은 결과가 나타납니다: SRV-01, SRV-02SRV-03 :

Reading data using Select-Xml

요소나 속성을 읽을 때 둘 다 Select-Xml의 구문은 번거로워집니다. XPath 매개변수를 사용하고 결과를 루프로 파이핑한 다음 Node 속성 아래의 데이터를 찾아야 합니다.

다행히 PowerShell은 XML 파일을 읽고 XML 개체로 변환하는 더 편리하고 직관적인 방법을 제공합니다.

관련 자료: 코딩 속도를 높이기 위해 PowerShell 데이터 유형 가속기 사용하기

XML 문자열을 개체로 캐스팅

XML을 파싱하기 위해 PowerShell을 사용하는 또 다른 방법은 XML을 개체로 변환하는 것입니다. 이 작업은 [xml] 형식 가속기를 사용하여 가장 쉽게 수행할 수 있습니다.

변수 이름에 [xml]을 접두사로 붙이면 PowerShell은 원래의 일반 텍스트 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

또한, 개체를 메모리에 로드하면 PowerShell ISE를 사용하는 경우 IntelliSense를 사용하여 탭 완성을 할 수도 있습니다. 다음과 같이 예시를 보여줍니다.

IntelliSense and tab completion for XML object

XML 데이터 반복

XML 파일을 직접 XML 개체로 읽어들이는 방법을 이용하면 ( [xml] 형식 가속기를 활용하여) PowerShell 개체의 모든 기능을 활용할 수 있습니다.

예를 들어, include=”true” 속성이 있는 XML 파일에 나타나는 모든 컴퓨터를 순환해야 한다고 가정해보십시오. 아래 코드는 그 작업을 수행하는 방법을 보여줍니다.

이 스크립트는 다음과 같습니다.

  • 파일을 읽고 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 데이터를 사용할 때는 정의된 스키마에 따라 모든 데이터가 유효한지 확인해야 합니다.

실행 중에 데이터 오류를 발견하는 것은 원하지 않는 일입니다. 스크립트의 중간에 있는 500줄 정도로 깊은 곳에서 이미 파일 시스템과 레지스트리에 대한 불가역적인 작업을 수행했을 수도 있습니다.

그렇다면 데이터가 올바른지 미리 어떻게 확인할 수 있을까요? 먼저 일부 가능한 오류 유형을 살펴보겠습니다.

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에 또 다른 문제가 sneaked in:

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

메타데이터, 즉 요소와 속성은 괜찮습니다. 그래서 무엇이 문제인가요? 이번에도 첫 번째 사람 줄에서 문제는 중 하나에 있습니다. 누군가가 에 대한 충분한 대체어라고 결정했지만, 아래와 같은 코드는 첫 번째 요소를 가져오지 못하고 이 아니라 를 찾기 때문에 실패할 것입니다.

$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:minLengthxs: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

결과는 실제로 복잡한 스키마가 작동한 것을 보여줍니다:

A string length error detected by the schema validation

XML 스키마 유효성 검사는 복잡성을 높일 수 있으며, 특히 정규 표현식과 결합될 때 생각할 수 있는 어떤 패턴도 검증할 수 있습니다.

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