Private/FormatNmapXml.ps1
Update-TypeData -TypeName PoshNmapHost -DefaultDisplayPropertySet IPv4,FQDN,Status,OpenPorts -Force function FormatNmapXml { <# .SYNOPSIS Takes the raw formatting from ConvertFrom-NmapXML and makes a useful Powershell Object out of the output. Meant to be called from ConvertFrom-NmapXml .INPUTS [Hashtable] .OUTPUTS [PoshNmapResult] .NOTES The raw formatting is still available as the nmaprun property on the object, to maintain compatibility #> [CmdletBinding()] param ( #Nmaprun output from ConvertFrom-NmapXml. We use hashtable because it's the easiest to manipulate quickly [Parameter(ValueFromPipeline)][Hashtable]$InputNmapXml, #Return a summary of the scan rather than individual hosts [Switch]$Summary ) if (-not $inputNmapXml.nmaprun) {throwUser "This is not a valid Hashtable output from Convert-NmapXML"} $nmaprun = $inputNmapXml.nmaprun #Only return a summary if that was requested if ($summary) {return (FormatNmapXmlSummary $nmapRun)} #Generate nicer host entries $i=1 $itotal = $nmaprun.host | measure | % count foreach ($hostnode in $nmaprun.host) { write-progress -Activity "Parsing NMAP Result" -Status "Processing Scan Entries" -CurrentOperation "Processing $i of $itotal" -PercentComplete (($i/$itotal)*100) # Init variables, with $entry being the custom object for each <host>. $service = " " #service needs to be a single space. $entry = [ordered]@{ PSTypeName = 'PoshNmapHost' } # Extract state element of status $entry.Status = $hostnode.status.state.Trim() if ($entry.Status.length -lt 2) { $entry.Status = $null } # Extract fully-qualified domain name(s), removing any duplicates. $entry.FQDNs = $hostnode.hostnames.hostname.name | select -Unique $entry.FQDN = $entry.FQDNs | select -first 1 # Note that this code cheats, it only gets the hostname of the first FQDN if there are multiple FQDNs. if ($entry.FQDN -eq $null) { $entry.HostName = $null } elseif ($entry.FQDN -like "*.*") { $entry.HostName = $entry.FQDN.Substring(0,$entry.FQDN.IndexOf(".")) } else { $entry.HostName = $entry.FQDN } # Process each of the <address> nodes, extracting by type. $hostnode.address | foreach-object { if ($_.addrtype -eq "ipv4") { $entry.IPv4 += $_.addr + " "} if ($_.addrtype -eq "ipv6") { $entry.IPv6 += $_.addr + " "} if ($_.addrtype -eq "mac") { $entry.MAC += $_.addr + " "} } if ($entry.IPv4 -eq $null) { $entry.IPv4 = $null } else { $entry.IPv4 = $entry.IPv4.Trim()} if ($entry.IPv6 -eq $null) { $entry.IPv6 = $null } else { $entry.IPv6 = $entry.IPv6.Trim()} if ($entry.MAC -eq $null) { $entry.MAC = $null } else { $entry.MAC = $entry.MAC.Trim()} # Process all ports from <ports><port>, and note that <port> does not contain an array if it only has one item in it. if ($hostnode.ports.port -eq $null) { $entry.Ports = $null ; $entry.Services = $null } else { $entry.Ports = @() $hostnode.ports.port | foreach-object { if ($_.service.name -eq $null) { $service = "unknown" } else { $service = $_.service.name } $entry.Ports += [ordered]@{ Protocol=$_.protocol Port=$_.portid Service=$service State=$_.state.state } # 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. 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" } } if ($entry.Services -eq $null) { $entry.Services = $null } else { $entry.Services = $entry.Services.Trim() } #Provide a nicer ToString Output $entry.Ports | Add-Member -MemberType ScriptMethod -Name ToString -Force -Value {$this.protocol,$this.port -join ':'} } $entry.OpenPorts = $entry.ports.count # If there is 100% Accuracy OS, show it $CertainOS = $hostnode.os.osmatch | where {$_.accuracy -eq 100} | select -first 1 if ($CertainOS) {$Entry.OS = $certainOS.name; $Entry.OSDetail = $certainOS} else {$Entry.OS=$null} $entry.BestGuessOS = ($hostnode.os.osmatch | select -first 1).name $entry.BestGuessOSPercent = ($hostnode.os.osmatch | select -first 1).accuracy $entry.OSGuesses = $hostnode.os.osmatch if (@($entry.OSGuesses).count -lt 1) { $entry.OS = $null } # Extract script output, first for port scripts, then for host scripts. $entry.Script = $null $hostnode.ports.port | foreach-object { if ($_.script -ne $null) { $entry.Script += "<PortScript id=""" + $_.script.id + """>$OutputDelimiter" + ($_.script.output -replace "`n","$OutputDelimiter") + "$OutputDelimiter</PortScript> $OutputDelimiter $OutputDelimiter" } } if ($hostnode.hostscript -ne $null) { $hostnode.hostscript.script | foreach-object { $entry.Script += '<HostScript id="' + $_.id + '">' + $OutputDelimiter + ($_.output.replace("`n","$OutputDelimiter")) + "$OutputDelimiter</HostScript> $OutputDelimiter $OutputDelimiter" } } $i++ #Progress counter... #Add raw host reference $entry.nmapResult = $hostnode [PSCustomObject]$entry } } |