2

I've got a xml log file. The file looks like this:

<Transaction name='0' id='1'>
    <Response>Warning</Response>
    <Statistic mode='Element'>
      <Information>0</Information>
      <Warning>0</Warning>
      <Error>0</Error>
    </Statistic>
    <Messages>
      <Message state='Warning'>Personal-Nr.: 12345, Tom Test</Message>
      <Message state='Warning'>This is a warning message 1</Message>
      <Message state='Warning'>This is a warning message 2</Message>
      <Message state='Warning'>This is a warning message 3</Message>
      <Message state='Warning'>This is a warning message 4</Message>
    </Messages>
</Transaction>

This pattern repeats about 900 times Sometimes with more or less Messages. Now I just want to get all the Transactions where the <Response>Error</Response> occurs.

So I made this code in Powershell:

## parsing xml file and opening inner node
Select-Xml -Path C:\Users\user\path\path\file.xml -XPath '/Paths/Task/Transaction' | ForEach-Object { $_.Node.InnerXML }

## looping through Response set with include="Error"
$_.Node.InnerXML | Where-Object Response -eq 'Error' | ForEach-Object { $_.Messages }
echo $_.Messages

But the only data I get is all of the transactions, no matter if the response is Warning or Error. Even further, it doesn't even matter if I only leave the Select-Xml line and delete the rest. The result is always the same. I always get ALL of the responses.

So my question is: How do I get only get the transactions where the Response is Error?

Bonus question: Is there a possibility to just have the first message line of each Error transaction as a output? So that I have a list of all the Personal-Nr that were in an error transaction?

Thanks a lot

1 Answer 1

2

The statements you've posted are completely independent at the moment - the first one outputs the textual encoding of all the transactions nodes, and the second and third ones simply do nothing, because $_ no longer has a value assigned to it at that point.

To properly "connect" them, you'd have to either place the filtering logic inside the first ForEach-Object block, eg.:

Select-Xml ... |ForEach-Object {
  if($_.Node.Response -eq 'Error'){ $_.Messages }
}

... or store the output from each step in an interim variable, eg.:

$allTransactions = Select-Xml ... -XPath '//Transaction' 

$allTransactions |ForEach-Object {
  if($_.Node.Response -eq 'Error'){ $_.Messages }
}

I should point out that ForEach-Object { if(...){ $_ } } is a bit of an anti-pattern unless your code has more complicated side effects - the more idiomatic solution would be to invoke the Where-Object cmdlet to filter the output from Select-Xml:

$allTransactions |Where-Object {
  $_.Node.Response -eq 'Error'
} |ForEach-Object Messages

While these suggestions might solve your problem, I strongly recommend not doing any of that - XPath is much more capable than what you're currently using it for :)


How do I get only get the transactions where the Response is "Error"?

I'd suggest simplifying your code by using a more accurate XPath expression with Select-Xml - one that looks for exactly what you want:

Select-Xml -Path C:\Users\user\path\path\file.xml -XPath '/Paths/Task/Transaction[Response = "Error"]'

Is there a possibility to just have the first message line of each "Error" transaction as a output? So that I have a list of all the "Personal-Nr" that were in an error transaction?

Sure thing!

Once again the easiest way is to modify the XPath expression, this time to only resolve the first <Message> node under a <Transaction> fitting the criteria above:

# beware that index selectors in XPath start at 1, not 0
//Transaction[Response = "Warning"]/Messages/Message[1]

But that's not all! XPath has several useful functions - so we can go one step deeper and have XPath extract and decode the message text for us too!

//Transaction[Response = "Warning"]/Messages/Message[1]/text()

This will cause Select-Xml to return a node set consisting of XmlText instances which you can convert directly to strings to get the raw string content.

Putting it back together with Select-Xml, you end up with something like this:

$filePath = 'C:\Users\user\path\path\file.xml'
$xPath = '//Transaction[Response = "Warning"]/Messages/Message[1]/text()'
$messages = Select-Xml -Path $filePath -XPath $xPath |ForEach-Object ToString

$messages
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for that detailed solution. That was exactly what I was looking for! The last code you posted worked like a charm.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.