Automating Port Query (portqry.exe) for Testing Connectivity

Recently, I was working with a federated customer that wanted to deploy Exchange 2003 in an environment where mailbox servers and routing bridgeheads would be separated by firewalls. As part of this effort, it was my job to determine that we had full connectivity to every department where Exchange 2003 would ultimately be installed. Port Query (portqry.exe) seemed like a good choice to conduct this test, but with a need to validate nearly 100 machines, we had to find a way to automate the process.

Shell scripting is sometimes forgotten in a Microsoft world where VBScript and other scripting languages offer so much power and flexibility, but in this case, it seemed like the way to go. After all, portqry already contained the functionality we required, so it didn't make sense to re-write this in a scripting language. The process required two files, one which contained the names and address of the machines to be tested, and another to actually execute the tests. The first file, called machines.csv was written in Microsoft Excel 2003 and saved as a CSV format. It had two columns (no header names in the file): one for IP Address and one for machine name. It looked like something larger than the following:

10.1.0.11,HostName1
10.1.0.25,HostName2

The batch file was simple enough but was written using the FOR loop built into the Windows shell environment (type FOR /? in the command prompt for info on this command). It consisted of two lines of code, plus some qualifiers.

FOR /F "eol=; tokens=1,2 delims=," %%I IN (machines.csv) DO CALL :PORT %%I %%J
GOTO END

:PORT
PortQry -n %1 -p TCP -e 135 -q -l "%2.log"

:END

The FOR /F command specifies that we should look within the file denoted in parentheses after the IN qualifier (in this case, machines.csv). We tell the for command that each line ends with a semi-colon, although a carriage return will also produce a line break. The "tokens" reference merely states that we are interested in assigning columns 1 and 2 to variables %I and %J, respectively (note that we use double-% because the code operates within a batch file). "Delims" states that each column is separated by a comma. For each line in the machines.csv file, we call the PortQry command, looking for TCP listeners and endpoints on port 135. We operate in quiet mode and write the output to a log file, named for the machine name in column 2 of the machines.csv file.

The example provided is greatly simplified from what we actually did on site with the customer, but I did want to share a few gotchas we ran into. The first one was with the -L parameter in Portqry. The log file must be named using only valid characters and the dash "-" is not considered valid as it apparently invokes an assumption that a different parameter is being provided to the portqry command. The other problem we had was that if we wanted to run multiple instances of portqry within the call loop, we couldn't append to the log file. In this case, we had to dump the entire batch file to a log file using the redirect switch from the command prompt.

Our ultimate compromise was to not use the -e switch and instead use the -o switch, specifying all ports we were interested in and BOTH for the protocol switch (-p), instead of just TCP. This resulted in us generating more data than we actually needed, but was a good compromise since it allowed us to write everything to a single log file for each machine being tested.

Another anomaly in our environment was that all machines were supposed to be using IPSec, so while the batch file executed, we monitored connecitivty for IPSec security associations with the IPSec Monitor snap-in. Since some traffic was excluded from the IPSec transport, we could see when connections were transmitting in the clear versus those (particularly port 135) being wrapped within our 3DES and SHA1 IPSec policy.

The entire test took approximately 5 minutes to run and about 1 hour to write and debug the code as well as compile the csv file. If I can save someone else that 1 hour, it was worth the post.