3

I have an XML file at $DSConfigPath that contains information as such:

<dataSources>
  <add id="DataSource1" type="Fixed" distance="1">
    <parameters>
      <name />
      <address>10.1.1.10</address>
      <otherIdentifiers>DataSource1</otherIdentifiers>
      <activeStatus>true</activeStatus>
      <ip>10.1.1.10</ip>
      <port>952</port>
    </parameters>
  </add>
  <add id="DataSource2" type="Fixed" distance="2">
    <parameters>
      <name />
      <address>10.1.1.11</address>
      <otherIdentifiers>DataSource2</otherIdentifiers>
      <activeStatus>true</activeStatus>
      <ip>10.1.1.11</ip>
      <port>952</port>
    </parameters>
  </add>
  <add id="DataSource3" type="Fixed" distance="3">
    <parameters>
      <name />
      <address>10.1.1.12</address>
      <otherIdentifiers>DataSource1</otherIdentifiers>
      <activeStatus>false</activeStatus>
      <ip>10.1.1.12</ip>
      <port>952</port>
    </parameters>
  </add>
</dataSources>

My goal is to do a do a port connection test to any IP/port where the <activeStatus> is 'true.'

I've got the following function, which I've verified will give me the correct results when I put in a specific $hostname and $port:

function Test-Port($hostname, $port) {
    # This works no matter in which form we get $host - hostname or ip address
    try {
        $ip = [System.Net.Dns]::GetHostAddresses($hostname) | 
              Select-Object IPAddressToString -ExpandProperty IPAddressToString
        if ($ip.GetType().Name -eq "Object[]") {
            #If we have several ip's for that address, let's take first one
            $ip = $ip[0]
        }
    } catch {
        Write-Host "Possibly $hostname is wrong hostname or IP"
        return
    }
    $t = New-Object Net.Sockets.TcpClient
    # We use Try\Catch to remove exception info from console if we can't connect
    try {
        $t.Connect($ip,$port)
    } catch {}

    if ($t.Connected) {
        $t.Close()
        $msg = "Port $port is operational"
    } else {
        $msg = "Port $port on $ip is closed, "
        $msg += "You may need to contact your IT team to open it. "                                 
    }
    Write-Host $msg
}

So now when I add the following variables:

[xml]$DSConfig = gc "$DSConfigPath"

$DS = $dsconfig.datasources.add.parameters
$DSName = $DS.otherIdentifiers
$DSIP = $DS.ip
$DSPort = $DS.port
$DSActive = $DS | Where-Object {$_.activeStatus -eq 'True'}

$hostname = $DSIP   # yes I realize this is redundant
$port = $DSPORT     # and yes, I realize this is redundant as well

Then run:

foreach ($DSActive in $DSConfig) {Test-Port $hostname $port}

I get the result:

Possibly 10.1.1.10 10.1.1.11is wrong hostname or IP

Any suggestion how I can get it to test the connection to 10.1.1.10:952, give that result, then test the connection to 10.1.1.11:952 and then give that result?

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
Christopher Cass
  • 817
  • 4
  • 19
  • 31
  • `foreach ($DSActive in $DSConfig)` makes no sense - you just defined `$DSConfig` as an `[xml]` document in the previous block. Please post [an mvce](https://stackoverflow.com/help/mcve) – Mathias R. Jessen Jan 29 '18 at 18:03
  • 1
    Thanks... Admittedly I have no idea what I'm doing... That's why I was coming here for help... – Christopher Cass Jan 29 '18 at 18:31
  • It would help if you could make sure that the sample XML you're posting is actually valid (as in "not missing a couple angular brackets"). – Ansgar Wiechers Jan 29 '18 at 22:38

3 Answers3

4

Use SelectNodes() to grab the <parameters> nodes from the xml document:

# Read XML document
[xml]$DSConfig = gc "$DSConfigPath"

# Select <parameters> nodes
$ParametersNode = $DSConfig.SelectNodes('//parameters')

# Loop over selected nodes
foreach($Node in $ParametersNode){
  # Test if activeStatus == 'true'
  if($Node.activeStatus -eq 'true') {
    # Run the Test-Port command
    Test-Port $Node.ip -port $Node.port
  }
}
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
1

The statement

$DS = $dsconfig.datasources.add.parameters

puts a list of all <parameter> nodes in the variable $DS. If you check the $DS.Count you'll see that it has the value 3 for your sample data, and if you echo the variable you'll see something like this:

PS C:\> Write-Output $DS

name             :
address          : 10.1.1.10
otherIdentifiers : DataSource1
activeStatus     : true
ip               : 10.1.1.10
port             : 952

name             :
address          : 10.1.1.11
otherIdentifiers : DataSource2
activeStatus     : true
ip               : 10.1.1.11
port             : 952

name             :
address          : 10.1.1.12
otherIdentifiers : DataSource1
activeStatus     : false
ip               : 10.1.1.12
port             : 952

Next the statements

$DSIP = $DS.ip
$DSPort = $DS.port

fill the variables $DSIP and $DSPort with a list of all IP addresses and all ports respectively.

PS C:\> Write-Output $DSIP
10.1.1.10
10.1.1.11
10.1.1.12

On PowerShell v3 and newer, that is. Prior to PowerShell v3 the statements would throw an error, because older versions don't support member enumeration, i.e. accessing properties and methods on the elements of an array when the array object itself doesn't have that property or method.

When you're passing this list of IP addresses to Test-Port, the statement

$ip = [System.Net.Dns]::GetHostAddresses($hostname)

fails with a MethodInvocationException, because GetHostAddresses() expects a single string, not a string array.

Also, your loop

foreach ($DSActive in $DSConfig) {Test-Port $hostname $port}

will terminate after one iteration because $DSConfig has just one element: the root node (<dataSources>).

To have your code call Test-Path for each IP address and port change the above loop into this:

foreach ($DSActive in $DSConfig.datasources.add.parameters) {
    Test-Port $DSActive.ip $DSActive.port
}
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
0

Alternative method using Select-Xml. This effectively does the same thing as Mathias's answer, but uses a cmdlet instead of the method. Not particularly useful unless you want to take advantage of some of the other options built into the cmdlet that the method doesn't make available (not real useful here, but can definitely be useful in more complex uses).

# Read XML document
[xml]$DSConfig = gc "$DSConfigPath"

# Select <parameters> nodes
$ParametersNode = Select-Xml -Xml $DSConfig -XPath '//parameters'|% {
    Test-Port $_.Node.ip $_.Node.port
}
TheMadTechnician
  • 34,906
  • 3
  • 42
  • 56