PoshCode Logo PowerShell Code Repository

Import-NmapXML by aaaa bb 10 months ago (modification of post by jgrote view diff)
diff | embed code: <script type="text/javascript" src="http://PoshCode.org/embed/2596"></script>download | new post

This is a Powershell v2 module that takes NMAP XML output and formats it into custom powershell objects, allowing you to manipulate NMAP output data in Powershell. It operates similarly to import-csv.

Based on original script by Jason Fossen: http://blogs.sans.org/windows-security/2009/06/11/powershell-script-to-parse-nmap-xml-output/
He did all the heavy lifting, I just repackaged it as a module.

  1. #Requires -Version 2.0
  2. function Import-NmapXML
  3. {
  4.         ####################################################################################
  5.         #.Synopsis
  6.         #    Parse XML output files of the nmap port scanner (www.nmap.org).
  7.         #
  8.         #.Description
  9.         #    Parse XML output files of the nmap port scanner (www.nmap.org) and  
  10.         #    emit custom objects with properties containing the scan data. The
  11.         #    script can accept either piped or parameter input.  The script can be
  12.         #    safely dot-sourced without error as is.
  13.         #
  14.         #.Parameter Path  
  15.         #    Either 1) a string with or without wildcards to one or more XML output
  16.         #    files, or 2) one or more FileInfo objects representing XML output files.
  17.         #
  18.         #.Parameter OutputDelimiter
  19.         #    The delimiter for the strings in the OS, Ports and Services properties.
  20.         #    Default is a newline.  Change it when you want single-line output.
  21.         #
  22.         #.Parameter RunStatsOnly
  23.         #    Only displays general scan information from each XML output file, such
  24.         #    as scan start/stop time, elapsed time, command-line arguments, etc.
  25.         #
  26.         #.Parameter ShowProgress
  27.         #    Prints progress information to StdErr while processing host entries.    
  28.         #
  29.         #.Example
  30.         #    dir *.xml | Import-NMAPXML
  31.         #
  32.         #.Example
  33.         #        Import-NmapXML -path onefile.xml
  34.         #    Import-NmapXML -path *files.xml
  35.         #
  36.         #.Example
  37.         #    $files = dir *some.xml,others*.xml
  38.         #    Import-NmapXML -path $files    
  39.         #
  40.         #.Example
  41.         #    Import-NmapXML -path scanfile.xml -runstatsonly
  42.         #
  43.         #.Example
  44.         #    Import-NmapXML scanfile.xml -OutputDelimiter " "
  45.         #
  46.         #Requires -Version 2.0
  47.         #
  48.         #.Notes
  49.         #  Author: Jason Fossen (http://blogs.sans.org/windows-security/)  
  50.     #  Edited: Justin Grote <justin+powershell NOSPAMAT grote NOSPAMDOT name>
  51.         # Version: 3.6.1-JWG1
  52.         # Updated: 02.Feb.2011
  53.         #   LEGAL: PUBLIC DOMAIN.  SCRIPT PROVIDED "AS IS" WITH NO WARRANTIES OR GUARANTEES OF
  54.         #          ANY KIND, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY AND/OR FITNESS FOR
  55.         #          A PARTICULAR PURPOSE.  ALL RISKS OF DAMAGE REMAINS WITH THE USER, EVEN IF
  56.         #          THE AUTHOR, SUPPLIER OR DISTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF
  57.         #          ANY SUCH DAMAGE.  IF YOUR STATE DOES NOT PERMIT THE COMPLETE LIMITATION OF
  58.         #          LIABILITY, THEN DELETE THIS FILE SINCE YOU ARE NOW PROHIBITED TO HAVE IT.
  59.         ####################################################################################
  60.     [CmdletBinding()]
  61.    
  62.         param (
  63.         [Parameter(Mandatory=$true,ValueFromPipeline=$true)]$Path,
  64.         [String] $OutputDelimiter = "`n",
  65.         [Switch] $RunStatsOnly,
  66.         [Switch] $ShowProgress
  67.     )
  68.        
  69.         if ($Path -match "/\?|/help|-h|-help|--h|--help")
  70.         {
  71.                 "`nPurpose: Process nmap XML output files (www.nmap.org).`n"
  72.                 "Example: Import-NmapXML scanfile.xml"
  73.         "Example: Import-NmapXML *.xml -runstatsonly `n"
  74.                 exit
  75.         }
  76.  
  77.         if ($Path -eq $null) {$Path = @(); $input | foreach { $Path += $_ } }
  78.         if (($Path -ne $null) -and ($Path.gettype().name -eq "String")) {$Path = dir $path} #To support wildcards in $path.  
  79.         $1970 = [DateTime] "01 Jan 1970 01:00:00 GMT"
  80.  
  81.         if ($RunStatsOnly)
  82.         {
  83.                 ForEach ($file in $Path)
  84.                 {
  85.                         $xmldoc = new-object System.XML.XMLdocument
  86.                         $xmldoc.Load($file)
  87.                         $stat = ($stat = " " | select-object FilePath,FileName,Scanner,Profile,ProfileName,Hint,ScanName,Arguments,Options,NmapVersion,XmlOutputVersion,StartTime,FinishedTime,ElapsedSeconds,ScanTypes,TcpPorts,UdpPorts,IpProtocols,SctpPorts,VerboseLevel,DebuggingLevel,HostsUp,HostsDown,HostsTotal)
  88.                         $stat.FilePath = $file.fullname
  89.                         $stat.FileName = $file.name
  90.                         $stat.Scanner = $xmldoc.nmaprun.scanner
  91.                         $stat.Profile = $xmldoc.nmaprun.profile
  92.                         $stat.ProfileName = $xmldoc.nmaprun.profile_name
  93.                         $stat.Hint = $xmldoc.nmaprun.hint
  94.                         $stat.ScanName = $xmldoc.nmaprun.scan_name
  95.                         $stat.Arguments = $xmldoc.nmaprun.args
  96.                         $stat.Options = $xmldoc.nmaprun.options
  97.                         $stat.NmapVersion = $xmldoc.nmaprun.version
  98.                         $stat.XmlOutputVersion = $xmldoc.nmaprun.xmloutputversion
  99.                         $stat.StartTime = $1970.AddSeconds($xmldoc.nmaprun.start)      
  100.                         $stat.FinishedTime = $1970.AddSeconds($xmldoc.nmaprun.runstats.finished.time)
  101.                         $stat.ElapsedSeconds = $xmldoc.nmaprun.runstats.finished.elapsed
  102.            
  103.             $xmldoc.nmaprun.scaninfo | foreach {
  104.                 $stat.ScanTypes += $_.type + " "
  105.                 $services = $_.services  #Seems unnecessary, but solves a problem.
  106.  
  107.                 if ($services.contains("-"))
  108.                 {
  109.                     #In the original XML, ranges of ports are summarized, e.g., "500-522",
  110.                     #but the script will list each port separately for easier searching.
  111.                     $array = $($services.replace("-","..")).Split(",")
  112.                     $temp  = @($array | where { $_ -notlike "*..*" })  
  113.                     $array | where { $_ -like "*..*" } | foreach { invoke-expression "$_" } | foreach { $temp += $_ }
  114.                     $temp = [Int32[]] $temp | sort
  115.                     $services = [String]::Join(",",$temp)
  116.                 }
  117.                    
  118.                 switch ($_.protocol)
  119.                 {
  120.                     "tcp"  { $stat.TcpPorts  = $services ; break }
  121.                     "udp"  { $stat.UdpPorts  = $services ; break }
  122.                     "ip"   { $stat.IpProtocols = $services ; break }
  123.                     "sctp" { $stat.SctpPorts = $services ; break }
  124.                 }
  125.             }
  126.            
  127.             $stat.ScanTypes = $($stat.ScanTypes).Trim()
  128.            
  129.                         $stat.VerboseLevel = $xmldoc.nmaprun.verbose.level
  130.                         $stat.DebuggingLevel = $xmldoc.nmaprun.debugging.level         
  131.                         $stat.HostsUp = $xmldoc.nmaprun.runstats.hosts.up
  132.                         $stat.HostsDown = $xmldoc.nmaprun.runstats.hosts.down          
  133.                         $stat.HostsTotal = $xmldoc.nmaprun.runstats.hosts.total
  134.                         $stat                  
  135.                 }
  136.                 return #Don't process hosts.  
  137.         }
  138.        
  139.         ForEach ($file in $Path) {
  140.                 If ($ShowProgress) { [Console]::Error.WriteLine("[" + (get-date).ToLongTimeString() + "] Starting $file" ) }
  141.  
  142.                 $xmldoc = new-object System.XML.XMLdocument
  143.                 $xmldoc.Load($file)
  144.                
  145.                 # Process each of the <host> nodes from the nmap report.
  146.                 $i = 0  #Counter for <host> nodes processed.
  147.                 $xmldoc.nmaprun.host | foreach-object {
  148.                         $hostnode = $_   # $hostnode is a <host> node in the XML.
  149.                
  150.                         # Init variables, with $entry being the custom object for each <host>.
  151.                         $service = " " #service needs to be a single space.
  152.                         $entry = ($entry = " " | select-object FQDN, HostName, Status, IPv4, IPv6, MAC, Ports, Services, OS, Script)
  153.  
  154.                         # Extract state element of status:
  155.                         $entry.Status = $hostnode.status.state.Trim()
  156.                         if ($entry.Status.length -lt 2) { $entry.Status = "<no-status>" }
  157.  
  158.                         # Extract fully-qualified domain name(s), removing any duplicates.  
  159.             $hostnode.hostnames.hostname | foreach-object { $entry.FQDN += $_.name + " " }
  160.                         $entry.FQDN = [System.String]::Join(" ",@($entry.FQDN.Trim().Split(" ") | sort-object -unique)) #Avoid -Join and -Split for now
  161.                         if ($entry.FQDN.Length -eq 0) { $entry.FQDN = "<no-fullname>" }
  162.  
  163.                         # Note that this code cheats, it only gets the hostname of the first FQDN if there are multiple FQDNs.
  164.                         if ($entry.FQDN.Contains(".")) { $entry.HostName = $entry.FQDN.Substring(0,$entry.FQDN.IndexOf(".")) }
  165.                         elseif ($entry.FQDN -eq "<no-fullname>") { $entry.HostName = "<no-hostname>" }
  166.                         else { $entry.HostName = $entry.FQDN }
  167.  
  168.                         # Process each of the <address> nodes, extracting by type.
  169.                         $hostnode.address | foreach-object {
  170.                                 if ($_.addrtype -eq "ipv4") { $entry.IPv4 += $_.addr + " "}
  171.                                 if ($_.addrtype -eq "ipv6") { $entry.IPv6 += $_.addr + " "}
  172.                                 if ($_.addrtype -eq "mac")  { $entry.MAC  += $_.addr + " "}
  173.                         }        
  174.                         if ($entry.IPv4 -eq $null) { $entry.IPv4 = "<no-ipv4>" } else { $entry.IPv4 = $entry.IPv4.Trim()}
  175.                         if ($entry.IPv6 -eq $null) { $entry.IPv6 = "<no-ipv6>" } else { $entry.IPv6 = $entry.IPv6.Trim()}
  176.                         if ($entry.MAC  -eq $null) { $entry.MAC  = "<no-mac>" }  else { $entry.MAC  = $entry.MAC.Trim() }
  177.  
  178.  
  179.                         # Process all ports from <ports><port>, and note that <port> does not contain an array if it only has one item in it.
  180.                         if ($hostnode.ports.port -eq $null) { $entry.Ports = "<no-ports>" ; $entry.Services = "<no-services>" }
  181.                         else
  182.                         {
  183.                                 $hostnode.ports.port | foreach-object {
  184.                                         if ($_.service.name -eq $null) { $service = "unknown" } else { $service = $_.service.name }
  185.                                         $entry.Ports += $_.state.state + ":" + $_.protocol + ":" + $_.portid + ":" + $service + $OutputDelimiter
  186.                     # Build Services property. What a mess...but exclude non-open/non-open|filtered ports and blank service info, and exclude servicefp too for the sake of tidiness.
  187.                     if ($_.state.state -like "open*" -and ($_.service.tunnel.length -gt 2 -or $_.service.product.length -gt 2 -or $_.service.proto.length -gt 2)) { $entry.Services += $_.protocol + ":" + $_.portid + ":" + $service + ":" + ($_.service.product + " " + $_.service.version + " " + $_.service.tunnel + " " + $_.service.proto + " " + $_.service.rpcnum).Trim() + " <" + ([Int] $_.service.conf * 10) + "%-confidence>$OutputDelimiter" }
  188.                                 }
  189.                                 $entry.Ports = $entry.Ports.Trim()
  190.                 if ($entry.Services -eq $null) { $entry.Services = "<no-services>" } else { $entry.Services = $entry.Services.Trim() }
  191.                         }
  192.  
  193.  
  194.                         # Extract fingerprinted OS type and percent of accuracy.
  195.                         $hostnode.os.osmatch | foreach-object {$entry.OS += $_.name + " <" + ([String] $_.accuracy) + "%-accuracy>$OutputDelimiter"}
  196.             $hostnode.os.osclass | foreach-object {$entry.OS += $_.type + " " + $_.vendor + " " + $_.osfamily + " " + $_.osgen + " <" + ([String] $_.accuracy) + "%-accuracy>$OutputDelimiter"}  
  197.             $entry.OS = $entry.OS.Replace("  "," ")
  198.             $entry.OS = $entry.OS.Replace("<%-accuracy>","") #Sometimes no osmatch.
  199.                         $entry.OS = $entry.OS.Trim()
  200.                         if ($entry.OS.length -lt 16) { $entry.OS = "<no-os>" }
  201.  
  202.            
  203.             # Extract script output, first for port scripts, then for host scripts.
  204.             $hostnode.ports.port | foreach-object {
  205.                 if ($_.script -ne $null) {
  206.                     $entry.Script += "<PortScript id=""" + $_.script.id + """>$OutputDelimiter" + ($_.script.output -replace "`n","$OutputDelimiter") + "$OutputDelimiter</PortScript> $OutputDelimiter $OutputDelimiter"
  207.                 }
  208.             }
  209.            
  210.             if ($hostnode.hostscript -ne $null) {
  211.                 $hostnode.hostscript.script | foreach-object {
  212.                     $entry.Script += '<HostScript id="' + $_.id + '">' + $OutputDelimiter + ($_.output.replace("`n","$OutputDelimiter")) + "$OutputDelimiter</HostScript> $OutputDelimiter $OutputDelimiter"
  213.                 }
  214.             }
  215.            
  216.             if ($entry.Script -eq $null) { $entry.Script = "<no-script>" }
  217.    
  218.    
  219.                         # Emit custom object from script.
  220.                         $i++  #Progress counter...
  221.                         $entry
  222.                 }
  223.  
  224.                 If ($ShowProgress) { [Console]::Error.WriteLine("[" + (get-date).ToLongTimeString() + "] Finished $file, processed $i entries." ) }
  225.         }
  226. }

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me