7.2. Parse Text-Based Application OutputMany administrators find a handful of command-line tools invaluable in their day-to-day tasks. Using the & syntax, we've seen how simple it is to work these tools into newer MSH scripts, but that's only half of the challenge. What is to be done when these legacy tools generate output? cmd.exe batch files and most command-line utilities are still limited to generating textual output, but given the string manipulation functionality in MSH, that needn't be an obstacle. 7.2.1. How Do I Do That?We'll focus on a commonly used tool for diagnosing network status and problems: ping. The ping tool ships with Windows and is used to send a series of ping packets to a destination to see whether the destination machine is reachable over the network. Let's see some typical output of the ping tool: MSH D:\MshScripts> ping 127.0.0.1 Pinging 127.0.0.1 with 32 bytes of data: Reply from 127.0.0.1: bytes=32 time<1ms TTL=128 Reply from 127.0.0.1: bytes=32 time<1ms TTL=128 Reply from 127.0.0.1: bytes=32 time<1ms TTL=128 Reply from 127.0.0.1: bytes=32 time<1ms TTL=128 Ping statistics for 127.0.0.1: Packets: Sent = 4, Received = 4, Lost = 0 (0% loss), Approximate round trip times in milli-seconds: Minimum = 0ms, Maximum = 0ms, Average = 0ms That output looks regulara characteristic that makes it a good candidate for parsing. We'll start with a function called IsMachineUp, which will invoke ping and determine whether valid replies were received. This function calls ping.exe, collects each line of its output, filters to show only those rows containing the Reply text, and uses that to conclude whether the ping succeeded: MSH D:\MshScripts> function IsMachineUp { >>$lines = $(&"ping.exe" $args[0] | where {$_ -match "^Reply.*bytes=" }) >>($lines.Count -gt 0) >>} >> MSH> IsMachineUp www.oreilly.com True MSH> IsMachineUp nonexistentmachine False We can take this one step further and actually convert the results into a more comfortable format for use inside MSH. The following function builds on the same idea as before, but this time uses some string manipulation to extract the interesting fields from the Reply lines: MSH D:\MshScripts> function ping { >>$lines = $(&"ping.exe" $args[0] | where {$_ -match "^Reply"}) >>foreach ($line in $lines) >>{ >> $parts=$line.Split(" ") >> $bytesPair=$parts[3].Split("=") >> $bytes=$bytesPair[1] >> $timePair=$parts[4].Split("=<") # separated by < or = >> $time=$timePair[1].Replace("ms","") # drop ms suffix >> $ttlPair=$parts[5].Split("=") >> $ttl=$ttlPair[1] >> 1|select-object ' >> @{expression = {$bytes}; name = "Bytes"},' >> @{expression = {$time}; name = "Time"},' >> @{expression = {$ttl}; name = "TTL"} >>} >>} >> MSH D:\MshScripts> ping www.oreilly.com Bytes Time TTL ----- ---- --- 32 167 244 32 101 244 32 97 244 32 106 244 MSH D:\MshScripts> ping www.oreilly.com | measure-object -Property Time -Average Count : 4 Average : 112.75 Sum : Max : Min : Property : Time 7.2.2. What Just Happened?Before we dig into how this example works, let's take a step back and look at some of the consequences of this approach. Although we've successfully transformed the output of the ping command into a structured format that can be used elsewhere in the shell, we've undermined a few of the key principles that give MSH its flexibility. By choosing to parse the text-based output of a command, we've reduced the robustness of the solution. If the output of the ping command were to change its format in response to a network condition, the parsing rules we've assumed might become invalid. With that caveat in mind, let's look at what just happened. In the first function, the goal is to capture the output of the ping command with a variable. Due to the presence of the where cmdlet, we can be confident that any lines matching the regular expression, which looks for successful ping responses, will be captured. To give a yes/no state of the availability, all that remains is to see whether any successful ping responses were found. Even just one reply line in the $lines array indicates that the remote machine returned something, so the function simply checks to see that one or more lines are present. Taking it a step further, we can actually break up each line of the output into its constituent parts. Using the Split method of the string class inside a loop, each of the three data pointsbytes returned, time taken, and TTLcan be separated into their own variables. select-object is then used to place a new object, with its fields populated, on the pipeline for each response. These objects become first-class citizens of MSH, and any of the familiar cmdlets can now be used on the data they hold. 7.2.3. What About ...... Extending the text-parsing approach to other command-line tools? Although anything that outputs text-based information can be parsed by an MSH script in this fashion, anything that relies heavily on string parsing will be brittle and prone to failure if the format changes in the future. Although ipconfig could be parsed to get a machine name, IP address, and MACor the net use output interpreted to understand mapped drivesall of this system configuration and state information is available via other channels, such as WMI and the .NET Framework Class Library. |