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
CodePudding user response:
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