PowerShell para Analisar XML: Ler e Validar

O XML está por toda parte. Apesar de seu uso irritante de colchetes angulares, o formato XML ainda é amplamente utilizado. Arquivos de configuração, feeds RSS, arquivos do Office (o ‘x’ no .docx) são apenas uma lista parcial. Usar o PowerShell para analisar arquivos XML é um passo essencial em sua jornada com o PowerShell.

Este tutorial mostrará como o PowerShell analisa arquivos XML e os valida. Isso o levará do zero ao herói em todos os aspectos de obtenção e avaliação de dados XML. Você receberá ferramentas que o ajudarão a validar a integridade dos dados XML e impedirão que dados defeituosos entrem nos seus scripts logo na entrada!

Pré-requisitos

Para acompanhar o material apresentado, você deve ter:

  • Versão do PowerShell 3.0 ou superior. Os exemplos foram criados no Windows PowerShell v5.1
  • Notepad++, Visual Studio Code ou outro editor de texto que entenda XML.

Analisando Elementos XML do PowerShell com Select-Xml

Vamos primeiro cobrir uma das maneiras mais populares e fáceis de usar o PowerShell para analisar XML, que é com o Select-Xml. O cmdlet Select-Xml permite que você forneça um arquivo ou string XML juntamente com um “filtro” conhecido como XPath para extrair informações específicas.

XPath é uma cadeia de nomes de elementos. Ele usa uma sintaxe “semelhante a um caminho” para identificar e navegar pelos nós em um documento XML.

Vamos supor que você tenha um arquivo XML com uma série de computadores e gostaria de usar o PowerShell para analisar esse arquivo XML. Cada computador tem vários elementos como nome, endereço IP e um elemento Include para inclusão em um relatório.

Um elemento é uma parte XML com uma tag de abertura e uma tag de fechamento, possivelmente com algum texto no meio, como <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>

Você gostaria de usar o PowerShell para analisar este arquivo XML e obter os nomes dos computadores. Para fazer isso, você poderia usar o comando Select-Xml.

No arquivo acima, os nomes dos computadores aparecem no texto interno (InnerXML) do elemento Name.

InnerXML é o texto entre as tags dos dois elementos.

Para encontrar os nomes dos computadores, você primeiro forneceria o XPath apropriado (/Computadores/Computador/Name). Esta sintaxe XPath retornaria apenas os nós Name sob os elementos Computador. Em seguida, para obter apenas o InnerXML de cada elemento Name, alcance a propriedade Node.InnerXML em cada elemento com um loop ForEach-Object.

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

Usando o PowerShell para Analisar Atributos XML com Select-Xml

Agora vamos abordar esse problema de encontrar nomes de computadores de um ângulo diferente. Desta vez, em vez dos descritores de computador representados com elementos XML, eles são representados com atributos XML.

Um atributo é uma parte chave/valor, como name="SRV-01". Atributos sempre aparecem dentro da tag de abertura, logo após o nome da tag.

Abaixo está o arquivo XML com descritores de computador representados com atributos. Agora você pode ver cada descritor como um atributo em vez de um elemento.

<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>

Como cada descritor é um atributo desta vez, ajuste um pouco o XPath para encontrar apenas os elementos de Computador. Em seguida, usando o cmdlet ForEach-Object novamente, encontre o valor do atributo name.

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

E de fato, isso também traz os mesmos resultados: SRV-01, SRV-02 e SRV-03 :

Reading data using Select-Xml

Em ambos os casos, seja lendo elementos ou atributos, a sintaxe do Select-Xml é complicada: ela força você a usar o parâmetro XPath, depois redirecionar o resultado para um loop e, finalmente, procurar os dados sob a propriedade Node.

Felizmente, o PowerShell oferece uma maneira mais conveniente e intuitiva de ler arquivos XML. O PowerShell permite que você leia arquivos XML e os converta em objetos XML.

Relacionado: Usando Aceleradores de Tipos de Dados do PowerShell para Agilizar a Codificação

Convertendo Strings XML em Objetos

Outra maneira de usar o PowerShell para analisar XML é converter esse XML em objetos. A maneira mais fácil de fazer isso é com o acelerador de tipo [xml].

Ao prefixar os nomes das variáveis com [xml], o PowerShell converte o XML original em texto simples em objetos com os quais você pode trabalhar.

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

Lendo Elementos de Objetos XML

Agora, tanto as variáveis $xmlElm quanto $xmlAttr são objetos XML que permitem que você faça referência a propriedades via notação de ponto. Talvez você precise encontrar o endereço IP de cada elemento do computador. Como o arquivo XML é um objeto, você pode fazer isso simplesmente referenciando o elemento IP.

$xmlElm.Computers.Computer.ip

Começando da versão 3.0 do PowerShell, o objeto XML obtém o valor do atributo com a mesma sintaxe usada para ler o texto interno do elemento. Portanto, os valores dos endereços IP são lidos do arquivo de atributos com exatamente a mesma sintaxe do arquivo de elementos.

Lendo Atributos XML

Usando exatamente a mesma notação de ponto, você pode ler atributos XML também, apesar das diferenças na estrutura XML.

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

E os resultados abaixo mostram que ambos obtiveram os mesmos dados, cada um do seu respectivo arquivo:

PowerShell to Parse XML : Reading XML Attributes

Além disso, com o objeto, uma vez carregado na memória, você até obtém o IntelliSense para completar automaticamente se estiver usando o PowerShell ISE. Por exemplo, conforme mostrado abaixo.

IntelliSense and tab completion for XML object

Iterando Através dos Dados XML

Depois de conseguir ler arquivos XML diretamente para objetos XML (aproveitando o acelerador de tipo [xml]), você tem todo o poder total dos objetos do PowerShell.

Diga, por exemplo, que você precisa iterar por todos os computadores que aparecem no arquivo XML com o atributo include=”true” para verificar o status da conexão deles. O código abaixo mostra como isso pode ser feito.

Este script é:

  • Lendo o arquivo e convertendo-o para um objeto XML.
  • Iterando pelos computadores relevantes para obter o status da conexão deles.
  • Por fim, enviando o objeto de saída do resultado para o pipeline.
 ## convertendo o texto do arquivo para um objeto XML
 [xml]$xmlAttr = Get-Content -Path C:\Work\computers-attr.xml

 ## iterando pelos conjuntos de computadores com include="true"
 $xmlAttr.Computers.Computer | Where-Object include -eq 'true' |  ForEach-Object {
     ## verificando se o computador atual está online
     if(Test-Connection -ComputerName $_.ip -Count 1 -Quiet)
     {
         $status = 'Connection OK'
     }
     else
     {
         $status = 'No Connection'
     }

     ## output do objeto de resultado
     [pscustomobject]@{
         Name = $_.name
         Ip = $_.ip
         Status = $status
     }
 }

E os resultados do script acima são mostrados abaixo:

Connection status results

Esquemas XML

Na seção anterior, você viu dois arquivos XML diferentes representando um conjunto de dados de duas maneiras diferentes. Essas maneiras são chamadas de esquemas XML. Um esquema XML define os blocos de construção legais de um documento XML específico:

  • Os nomes dos elementos e atributos que podem aparecer nesse documento específico.
  • O número e a ordem dos elementos filhos.
  • Os tipos de dados para elementos e atributos.

O esquema essencialmente define a estrutura do XML.

Validando Dados XML

Um arquivo XML pode ter a sintaxe correta (editores como o Notepad++ irão reclamar se não tiver), mas seus dados podem não corresponder aos requisitos do projeto. É aí que entra o esquema. Quando você trabalha com dados XML, é preciso garantir que todos os dados sejam válidos de acordo com o esquema definido.

A última coisa que você quer é descobrir erros nos dados em tempo de execução, 500 linhas no meio do script. Nesse momento, ele pode ter realizado algumas operações irreversíveis no sistema de arquivos e no registro.

Então, como você pode verificar antecipadamente se os dados estão corretos? Vamos ver primeiro alguns possíveis tipos de erro.

Possíveis erros nos dados XML

Em geral, os erros encontrados em arquivos XML pertencem a duas categorias: erros de metadados e erros nos próprios dados.

Erros de metadados XML

Este arquivo MorePeople.xml abaixo tem uma sintaxe perfeitamente válida. Você pode ver abaixo que o arquivo tem um único elemento People (o elemento raiz) com três elementos Person dentro. Essa estrutura é perfeitamente aceitável. Ainda assim, ele contém uma exceção, você consegue ver?

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

Não se preocupe se você não conseguiu, está apenas escondido. O problema está no primeiro elemento interno:

O que deveria ser um Country foi digitado incorretamente, e Canada foi degradado para County.

Erros nos dados XML

Após corrigir o problema do Country no MorePeople.xml, outro problema apareceu:

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

Os metadados, ou seja, os elementos e atributos, estão corretos. Então, o que há de errado? Desta vez, o problema, novamente na primeira linha Pessoa , está em um dos valores. Alguém decidiu que sim é um substituto bom o suficiente para verdadeiro – mas o código como abaixo falhará ao obter o primeiro elemento, pois está procurando por verdadeiro, não por sim:

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

Criando um Esquema XML

Agora que você conhece os tipos de erros que podem ocorrer, é hora de mostrar como um arquivo de esquema ajuda. O primeiro passo é criar um arquivo de dados de exemplo. O exemplo pode ser o menor possível e conter nada além de um único elemento interno. Para os exemplos acima, vamos criar um arquivo de exemplo como este Pessoas.xml :

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

Agora, construa uma função PowerShell abaixo e use-a com os dados de exemplo para criar o esquema .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

    ## construa o caminho do esquema substituindo '.xml' por '.xsd'
    $SchemaPath = "$dir\$baseName.xsd"

    try
    {
        $dataSet = New-Object -TypeName System.Data.DataSet
        $dataSet.ReadXml($XmlExample) | Out-Null
        $dataSet.WriteXmlSchema($SchemaPath)
        
        ## mostre o arquivo de esquema resultante
        Get-Item $SchemaPath
    }
    catch
    {
        $err = $_.Exception.Message
        Write-Host "Failed to create XML schema for $XmlExample`nDetails: $err" -ForegroundColor Red
    }
}

Copie a função para seu ISE ou seu editor PowerShell favorito, carregue-a na memória e use-a para criar o esquema. Com a função carregada, o código para criar o esquema é esta única linha:

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

Os resultados mostrarão o caminho para o esquema recém-criado:

Creating XML schema from a sample data file

Usando o Arquivo de Esquema para Validar Seus Dados

Dê uma olhada no local especificado pelos resultados acima. Se o arquivo .xsd estiver lá, você está no caminho certo para ver a validação em ação. Para a etapa de confirmação, use a função abaixo:

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
    }
}

Carregue a função na memória e use-a para validar o MorePeople.xml dos dois exemplos de erro. Para acionar a validação, use o comando abaixo:

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

Os resultados reais dependem do conteúdo do MorePeople.xml.

Vejamos dois exemplos. Observe que quando o MorePeople.xml estiver sem erros, a função acima retornará True.

Validation success

Quando o arquivo MorePeople.xml contém dados incorretos (a chave Country foi soletrada como County), a função retornará alguns detalhes do erro e False.

Validation failure – wrong attribute name detected

Como você pode ver, o erro especificado na saída detalhada é muito informativo: ele aponta para o arquivo culpado e indica o componente exato onde ocorreu o problema.

Ajustando o Esquema de Validação

Vamos dar uma olhada no arquivo de esquema e depois ver como podemos melhorá-lo ainda mais.

O esquema criado pelo New-XmlSchema por padrão é o seguinte:

<?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>

O esquema padrão acima é bom, mas não perfeito. De fato, ele identificou o erro de digitação com o atributo Country. No entanto, se você deixar o esquema como está, caso outras expectativas que você possa ter não sejam atendidas, esses problemas não serão relatados como erros pela validação Test-XmlBySchema. Vamos resolver isso.

A tabela abaixo apresenta alguns casos que não são considerados erros de validação e passarão despercebidos pelo Test-XmlBySchema. Em cada linha, a coluna da direita mostra como alterar manualmente o esquema para adicionar suporte para a proteção necessária.

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

Adicionando configuração ao esquema padrão – exemplos

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>

Com a restrição booleana em vigor para o atributo IsAdmin no exemplo, seu valor deve ser em minúsculas verdadeiro ou falso.

Validação de comprimento de string com xs:restriction

A validação de comprimento de string é um pouco complexa. Portanto, mesmo que seja mostrada acima como parte do esquema modificado, ela merece um pouco mais de atenção.

O item de esquema original para o atributo Country (depois de ter adicionado manualmente o use=”required”), é como abaixo:

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

Para adicionar a proteção de comprimento, você deve adicionar o elemento <xs:simpleType> e, dentro dele, a <xs:restriction base="xs:string">. Essa restrição, por sua vez, contém os limites necessários declarados em xs:minLength e em xs:minLength.

Após todas essas alterações, a declaração final de xs:attribute cresceu de uma única linha para um nó gigante de 8 linhas:

<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>

Se sua cabeça não girar após a explicação acima, você tem o direito de ver a validação em ação. Para fazer isso, vamos intencionalmente encurtar o valor Canadá para uma sílaba de duas letras: Ca

Com o nome curto do país em vigor e o arquivo MorePeople.xml salvo, você está pronto para executar o comando de validação abaixo:

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

E os resultados realmente mostram que o esquema complexo fez o seu trabalho:

A string length error detected by the schema validation

A validação do esquema XML pode aumentar em complexidade e validar praticamente qualquer padrão que você possa imaginar, especialmente quando combinada com expressões regulares.

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