20

This is probably simple, but I'm trying to determine if a node exists in an XML document. I thought I found an answer in this post, How to check whether a node exists or not using powershell without getting exception?, but I didn't get it to work. This is my latest attempt.

foreach ($vendor in $xml.Vendors.Vendor| Where-Object  {$_.Type -match "Send"}) {
    $NodeExists = $vendor.SelectSingleNode($vendor.EncKey)
    if ($NodeExists -ne $null) {
        # Do something
    }
    else {
       # something else
    }
   }

Edit

Here is XML from my test file. I need to find out if EncKey exists or not for each vendor.

<?xml version="1.0" encoding="UTF-8"?>
    <!-- Vendors we will send and retreive files from Get-Send means we will get a file and send them a file Send means we will only send them a file-->
    <Vendors>
        <Vendor Type="Get-Send">
            <Name>Vendor1</Name>            
            <RemotePath>/Remote/Path1/</RemotePath>
            <EncKey>pgpenc.key</EncKey>
        </Vendor>
        <Vendor Type="Send">
            <Name>Vendor2</Name>            
            <RemotePath>/Remote/Path2/</RemotePath> 
            <!-- This one has no EncKey -->         
        </Vendor>
    </Vendors>
halfer
  • 19,824
  • 17
  • 99
  • 186
mack
  • 2,715
  • 8
  • 40
  • 68
  • Could you please provide a reduced test case for your issue? Something we can paste into our favorite Powershell editor and play with it? – Victor Zakharov Dec 06 '13 at 21:52

4 Answers4

10

The easiest way I can think of is to try to write the node value to a variable, and then to see if that variable is null. Here's an example with the standard bookstore xml file.

[xml]$bookstore = Get-Content .\bookstore.xml
foreach ($book in $bookstore.bookstore.book | Where-Object {$_.Type -match "novel"}) {
 $NodeExists = $book.author
 if($NodeExists){
  Write-Host $book.author
 }
 else{
  Write-Host 'No Author'
 }
} 

So for your script, I would think it might be

$NodeExists = $null
foreach ($vendor in $xml.Vendors.Vendor| Where-Object  {$_.Type -match "Send"}) {
 $NodeExists = $vendor.EncKey
 if ($NodeExists) {
  # Do something
 }
 else {
  # something else
  }
}
Alex McKenzie
  • 892
  • 2
  • 10
  • 20
  • 2
    Thanks Alex. This is along the lines of what I was attempting to do, but if $vendor.EncKey does not exists I get an exception: **Property 'EncKey' cannot be found on this object. Make sure that it exists.** and I never get to the "else" part of the statement. – mack Dec 09 '13 at 13:59
  • You can shorten this with "if ( $vendor.EncKey ) {" -- also, this is case insensitive, as compared to SelectSingleNode which did not work for me. I did not have any exception issue with this. – Jay Jun 18 '15 at 13:49
  • 2
    @mack you'll actually get an exception when you have [strict mode turned on](https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Core/Set-StrictMode?view=powershell-5.0). When the strict mode is turned off, then `$vendor.EncKey` evaluates to `$false` in the statement `if ( $vendor.EncKey )`, as provided by @Jay – laika Nov 21 '18 at 14:03
9

It appears that I was using the wrong syntax for SelectSingleNode. Here is a working example.

[xml]$xml = @'
<?xml version="1.0" encoding="UTF-8"?>
    <!-- Vendors we will send and retreive files from Get-Send means we will get a file and send them a file Send means we will only send them a file-->
    <Vendors>
        <Vendor Type="Get-Send">
            <Name>Vendor1</Name>            
            <RemotePath>/Remote/Path1/</RemotePath>
            <EncKey>pgpenc.key</EncKey>
        </Vendor>
        <Vendor Type="Send">
            <Name>Vendor2</Name>            
            <RemotePath>/Remote/Path2/</RemotePath> 
            <!-- This one has no EncKey -->         
        </Vendor>
    </Vendors>
'@

foreach ($vendor in $xml.Vendors.Vendor| Where-Object  {$_.Type -match "Send"}) {
    $NodeExists = $vendor.SelectSingleNode("./EncKey")
    if ($NodeExists -ne $null) {
        write-host "EncKey is null"
    }
    else {
       write-host "EncKey is not null"
    }
   }

EncKey is null
EncKey is not null

Thanks everyone for your help.

mack
  • 2,715
  • 8
  • 40
  • 68
  • According to my tests, SelectSingleNode is case sensitive, unless there is a way to change its behavior. – Jay Jun 18 '15 at 13:50
  • 1
    Good, but should be opposite. if ($NodeExists -ne $null) { write-host "EncKey is NOT null" } – Raf Jan 17 '19 at 10:58
  • An excellent article which simplifies the `SelectSingleNode(..)` implementation, using XML namespaces: [https://blog.danskingdom.com/powershell-functions-to-get-an-xml-node-and-get-and-set-an-xml-elements-value-even-when-the-element-does-not-already-exist/](https://blog.danskingdom.com/powershell-functions-to-get-an-xml-node-and-get-and-set-an-xml-elements-value-even-when-the-element-does-not-already-exist/) – kalenwatermeyer Jun 09 '20 at 12:35
4

Use XPath to select matching nodes. InnerText is searchable by text(). You can use where-object, or ? too; behaviour is a bit different. Without sample XML it's hard to be more precise. Use XPath like so,

[xml]$doc = @'
<root>
<Vendors>
<Vendor>
<Type>Send</Type>
</Vendor>
<Vendor>
<Type>Receive</Type>
</Vendor>
</Vendors>
</root>
'@

# Xpath query will return a NodeList, even if no match is found
$node1 = $doc.SelectNodes("//Vendor/Type[text() = 'Send']")
$node2 = $doc.SelectNodes("//Vendor/Type[text() = 'Sent']")
$node1.Count
1
$node2.Count
0

# where-object will return $null if no match is found
$node1 = $doc.SelectNodes("//Vendor/Type") | ? { $_.InnerText -eq "Send" }
$node2 = $doc.SelectNodes("//Vendor/Type") | ? { $_.InnerText -eq "Sent" }

$node1 -eq $null
False
$node2 -eq $null
True
vonPryz
  • 22,996
  • 7
  • 54
  • 65
4

Provided you load the $xml object as an XmlDocument using

$xml = new-object System.Xml.XmlDocument
$xml.LoadXml( (get-content $pathToXmlFile) )

Then you can do this, which is much simpler:

if ($vendor.encKey -ne $null) {
  # does exist
} else {
  # does not exist
}
Alex Fairchild
  • 1,025
  • 11
  • 11