PowerShell para Analisar XML: Ler e Validar

XML está por todo o lado. Apesar do seu uso irritante de símbolos de maior e menor, o formato XML ainda é amplamente utilizado. Ficheiros de configuração, feeds RSS, ficheiros do Office (o ‘x’ no .docx) são apenas uma lista parcial. Utilizar o PowerShell para analisar ficheiros XML é uma etapa essencial na sua jornada com o PowerShell.

Este tutorial mostrará como o PowerShell analisa ficheiros XML e os valida. Isto irá guiá-lo de iniciante a especialista em todos os aspetos de obtenção e avaliação de dados XML. Serão fornecidas ferramentas que o ajudarão a validar a integridade dos dados XML e a impedir que dados defeituosos entrem nos seus scripts logo à partida!

Pré-requisitos

Para acompanhar o material apresentado, deverá ter:

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

Analisando Elementos XML do PowerShell com Select-Xml

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

XPath é uma sequência 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 um monte de computadores e gostaria de usar o PowerShell para analisar esse arquivo XML. Cada computador possui vários elementos como nome, endereço IP e um elemento Include para inclusão em um relatório.

Um elemento é uma porção XML com uma tag de abertura e uma tag de fechamento, possivelmente com algum texto entre elas, 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 esse 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 duas tags do elemento.

Para encontrar os nomes dos computadores, você primeiro forneceria o XPath apropriado (/Computers/Computer/Name). Essa sintaxe XPath retornaria apenas os nós Name sob os elementos Computer. Em seguida, para obter apenas o InnerXML de cada elemento Name, você alcançaria 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 uma maneira 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". Os 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 obriga você a usar o parâmetro XPath, depois encaminhar 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 Acelerar a Codificação

Conversão de 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

Leitura de Elementos do Objeto XML

Agora, tanto as variáveis $xmlElm quanto $xmlAttr são objetos XML que permitem que você faça referência a propriedades por meio da 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 fazendo referência ao elemento IP.

$xmlElm.Computers.Computer.ip

A partir 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.

Leitura de Atributos XML

Usando exatamente a mesma notação de ponto, você pode ler atributos XML, 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 arquivo correspondente:

PowerShell to Parse XML : Reading XML Attributes

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

IntelliSense and tab completion for XML object

Iterando pelos Dados XML

Depois de lidar com a leitura direta de arquivos XML para objetos XML (aproveitando o acelerador de tipo [xml]), você tem todo o poder dos objetos 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 em um objeto XML.
  • Iterando pelos computadores relevantes para obter o status da conexão deles.
  • Finalmente, enviando o objeto de saída do resultado para o pipeline.
 ## convertendo o texto do arquivo em um objeto XML
 [xml]$xmlAttr = Get-Content -Path C:\Work\computers-attr.xml

 ## iterando pelos computadores definidos 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 de elementos e atributos que podem aparecer nesse documento específico.
  • O número e a ordem de 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++ vão reclamar se não tiver), mas os dados podem não atender aos requisitos do projeto. Aqui é onde o esquema entra em jogo. Ao lidar com dados XML, é necessário garantir que todos os dados, todos, 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 profundas 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 verificar antecipadamente se os dados estão corretos? Vamos primeiro ver alguns possíveis tipos de erro.

Erros Possíveis nos Dados XML

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

Erros de Metadados XML

Este arquivo MorePeople.xml abaixo é perfeitamente válido em termos de sintaxe. Você pode ver abaixo que o arquivo possui um único elemento People (o elemento raiz) com três elementos Person dentro. Essa estrutura é perfeitamente aceitável. Ainda assim, ela contém uma exceção, consegue identificá-la?

<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 não conseguiu, ela está apenas escondida. O problema está no primeiro elemento interno:

O que deveria ser um Country foi escrito incorretamente, e o Canadá foi degradado para um County.

Erros nos Dados XML

Depois de corrigir o problema do Country em MorePeople.xml, outro problema surgiu:

<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 está errado? Desta vez, o problema, novamente na primeira linha Person , está em um dos valores. Alguém decidiu que yes é um substituto bom o suficiente para true – mas o código como abaixo falhará em obter o primeiro elemento, pois está procurando por true, não por yes:

$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 apenas um elemento interno. Para os exemplos acima, vamos criar um arquivo de exemplo como este People.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 o 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 é este em uma 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 na localização especificada 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.

Vamos ver dois exemplos. Observe que quando o arquivo MorePeople.xml estiver livre de erros, a função acima retornará True.

Validation success

Quando o arquivo MorePeople.xml contém dados incorretos (a chave Country foi escrita incorretamente como County), a função retornará alguns detalhes de falha e retornará 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 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 à proteção necessária.

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

Adição de 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 minúsculo verdadeiro ou falso.

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

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

O item de esquema original para o atributo País (após ter sido adicionado manualmente o use=”required”), é o seguinte:

<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 requeridos declarados em xs:minLength e em xs:maxLength.

Seguindo todas essas mudanças, a declaração final do 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 estiver girando após a explicação acima, você ganhou 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 no lugar e 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 crescer 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/