IPv4NetworkScan.ps1
############################################################################################################### # Language : PowerShell 4.0 # Filename : IPv4NetworkScan.ps1 # Autor : BornToBeRoot (https://github.com/BornToBeRoot) # Description : Powerful asynchronus IPv4 Network Scanner # Repository : https://github.com/BornToBeRoot/PowerShell_IPv4NetworkScanner ############################################################################################################### <# .SYNOPSIS Powerful asynchronus IPv4 Network Scanner .DESCRIPTION This powerful asynchronus IPv4 Network Scanner allows you to scan every IPv4-Range you want (172.16.1.47 to 172.16.2.5 would work). But there is also the possibility to scan an entire subnet based on an IPv4-Address withing the subnet and a the subnetmask/CIDR. The default result will contain the the IPv4-Address, Status (Up or Down) and the Hostname. Other values can be displayed via parameter. .EXAMPLE .\IPv4NetworkScan.ps1 -StartIPv4Address 192.168.178.0 -EndIPv4Address 192.168.178.20 IPv4Address Status Hostname ----------- ------ -------- 192.168.178.1 Up fritz.box .EXAMPLE .\IPv4NetworkScan.ps1 -IPv4Address 192.168.178.0 -Mask 255.255.255.0 -DisableDNSResolving IPv4Address Status ----------- ------ 192.168.178.1 Up 192.168.178.22 Up .EXAMPLE .\IPv4NetworkScan.ps1 -IPv4Address 192.168.178.0 -CIDR 25 -EnableMACResolving IPv4Address Status Hostname MAC Vendor ----------- ------ -------- --- ------ 192.168.178.1 Up fritz.box XX-XX-XX-XX-XX-XX AVM Audiovisuelles Marketing und Computersysteme GmbH 192.168.178.22 Up XXXXX-PC.fritz.box XX-XX-XX-XX-XX-XX ASRock Incorporation .LINK https://github.com/BornToBeRoot/PowerShell_IPv4NetworkScanner/blob/master/README.md #> [CmdletBinding(DefaultParameterSetName = 'CIDR')] Param( [Parameter( ParameterSetName = 'Range', Position = 0, Mandatory = $true, HelpMessage = 'Start IPv4-Address like 192.168.1.10')] [IPAddress]$StartIPv4Address, [Parameter( ParameterSetName = 'Range', Position = 1, Mandatory = $true, HelpMessage = 'End IPv4-Address like 192.168.1.100')] [IPAddress]$EndIPv4Address, [Parameter( ParameterSetName = 'CIDR', Position = 0, Mandatory = $true, HelpMessage = 'IPv4-Address which is in the subnet')] [Parameter( ParameterSetName = 'Mask', Position = 0, Mandatory = $true, HelpMessage = 'IPv4-Address which is in the subnet')] [IPAddress]$IPv4Address, [Parameter( ParameterSetName = 'CIDR', Position = 1, Mandatory = $true, HelpMessage = 'CIDR like /24 without "/"')] [ValidateRange(0, 31)] [Int32]$CIDR, [Parameter( ParameterSetName = 'Mask', Position = 1, Mandatory = $true, Helpmessage = 'Subnetmask like 255.255.255.0')] [ValidateScript({ if ($_ -match "^(254|252|248|240|224|192|128).0.0.0$|^255.(254|252|248|240|224|192|128|0).0.0$|^255.255.(254|252|248|240|224|192|128|0).0$|^255.255.255.(254|252|248|240|224|192|128|0)$") { return $true } else { throw "Enter a valid subnetmask (like 255.255.255.0)!" } })] [String]$Mask, [Parameter( Position = 2, HelpMessage = 'Maxmium number of ICMP checks for each IPv4-Address (Default=2)')] [Int32]$Tries = 2, [Parameter( Position = 3, HelpMessage = 'Maximum number of threads at the same time (Default=256)')] [Int32]$Threads = 256, [Parameter( Position = 4, HelpMessage = 'Resolve DNS for each IP (Default=Enabled)')] [Switch]$DisableDNSResolving, [Parameter( Position = 5, HelpMessage = 'Resolve MAC-Address for each IP (Default=Disabled)')] [Switch]$EnableMACResolving, [Parameter( Position = 6, HelpMessage = 'Get extendend informations like BufferSize, ResponseTime and TTL (Default=Disabled)')] [Switch]$ExtendedInformations, [Parameter( Position = 7, HelpMessage = 'Include inactive devices in result')] [Switch]$IncludeInactive ) Begin { Write-Verbose -Message "Script started at $(Get-Date)" $OUIListPath = "$PSScriptRoot\Resources\oui.txt" function Convert-Subnetmask { [CmdLetBinding(DefaultParameterSetName = 'CIDR')] param( [Parameter( ParameterSetName = 'CIDR', Position = 0, Mandatory = $true, HelpMessage = 'CIDR like /24 without "/"')] [ValidateRange(0, 32)] [Int32]$CIDR, [Parameter( ParameterSetName = 'Mask', Position = 0, Mandatory = $true, HelpMessage = 'Subnetmask like 255.255.255.0')] [ValidateScript({ if ($_ -match "^(254|252|248|240|224|192|128).0.0.0$|^255.(254|252|248|240|224|192|128|0).0.0$|^255.255.(254|252|248|240|224|192|128|0).0$|^255.255.255.(255|254|252|248|240|224|192|128|0)$") { return $true } else { throw "Enter a valid subnetmask (like 255.255.255.0)!" } })] [String]$Mask ) Begin { } Process { switch ($PSCmdlet.ParameterSetName) { "CIDR" { # Make a string of bits (24 to 11111111111111111111111100000000) $CIDR_Bits = ('1' * $CIDR).PadRight(32, "0") # Split into groups of 8 bits, convert to Ints, join up into a string $Octets = $CIDR_Bits -split '(.{8})' -ne '' $Mask = ($Octets | ForEach-Object -Process { [Convert]::ToInt32($_, 2) }) -join '.' } "Mask" { # Convert the numbers into 8 bit blocks, join them all together, count the 1 $Octets = $Mask.ToString().Split(".") | ForEach-Object -Process { [Convert]::ToString($_, 2) } $CIDR_Bits = ($Octets -join "").TrimEnd("0") # Count the "1" (111111111111111111111111 --> /24) $CIDR = $CIDR_Bits.Length } } [pscustomobject] @{ Mask = $Mask CIDR = $CIDR } } End { } } # Helper function to convert an IPv4-Address to Int64 and vise versa function Convert-IPv4Address { [CmdletBinding(DefaultParameterSetName = 'IPv4Address')] param( [Parameter( ParameterSetName = 'IPv4Address', Position = 0, Mandatory = $true, HelpMessage = 'IPv4-Address as string like "192.168.1.1"')] [IPaddress]$IPv4Address, [Parameter( ParameterSetName = 'Int64', Position = 0, Mandatory = $true, HelpMessage = 'IPv4-Address as Int64 like 2886755428')] [long]$Int64 ) Begin { } Process { switch ($PSCmdlet.ParameterSetName) { # Convert IPv4-Address as string into Int64 "IPv4Address" { $Octets = $IPv4Address.ToString().Split(".") $Int64 = [long]([long]$Octets[0] * 16777216 + [long]$Octets[1] * 65536 + [long]$Octets[2] * 256 + [long]$Octets[3]) } # Convert IPv4-Address as Int64 into string "Int64" { $IPv4Address = (([System.Math]::Truncate($Int64 / 16777216)).ToString() + "." + ([System.Math]::Truncate(($Int64 % 16777216) / 65536)).ToString() + "." + ([System.Math]::Truncate(($Int64 % 65536) / 256)).ToString() + "." + ([System.Math]::Truncate($Int64 % 256)).ToString()) } } [pscustomobject] @{ IPv4Address = $IPv4Address Int64 = $Int64 } } End { } } # Helper function to create a new Subnet function Get-IPv4Subnet { [CmdletBinding(DefaultParameterSetName = 'CIDR')] param( [Parameter( Position = 0, Mandatory = $true, HelpMessage = 'IPv4-Address which is in the subnet')] [IPAddress]$IPv4Address, [Parameter( ParameterSetName = 'CIDR', Position = 1, Mandatory = $true, HelpMessage = 'CIDR like /24 without "/"')] [ValidateRange(0, 31)] [Int32]$CIDR, [Parameter( ParameterSetName = 'Mask', Position = 1, Mandatory = $true, Helpmessage = 'Subnetmask like 255.255.255.0')] [ValidateScript({ if ($_ -match "^(254|252|248|240|224|192|128).0.0.0$|^255.(254|252|248|240|224|192|128|0).0.0$|^255.255.(254|252|248|240|224|192|128|0).0$|^255.255.255.(254|252|248|240|224|192|128|0)$") { return $true } else { throw "Enter a valid subnetmask (like 255.255.255.0)!" } })] [String]$Mask ) Begin { } Process { # Convert Mask or CIDR - because we need both in the code below switch ($PSCmdlet.ParameterSetName) { "CIDR" { $Mask = (Convert-Subnetmask -CIDR $CIDR).Mask } "Mask" { $CIDR = (Convert-Subnetmask -Mask $Mask).CIDR } } # Get CIDR Address by parsing it into an IP-Address $CIDRAddress = [System.Net.IPAddress]::Parse([System.Convert]::ToUInt64(("1" * $CIDR).PadRight(32, "0"), 2)) # Binary AND ... this is how subnets work. $NetworkID_bAND = $IPv4Address.Address -band $CIDRAddress.Address # Return an array of bytes. Then join them. $NetworkID = [System.Net.IPAddress]::Parse([System.BitConverter]::GetBytes([UInt32]$NetworkID_bAND) -join (".")) # Get HostBits based on SubnetBits (CIDR) // Hostbits (32 - /24 = 8 -> 00000000000000000000000011111111) $HostBits = ('1' * (32 - $CIDR)).PadLeft(32, "0") # Convert Bits to Int64 $AvailableIPs = [Convert]::ToInt64($HostBits, 2) # Convert Network Address to Int64 $NetworkID_Int64 = (Convert-IPv4Address -IPv4Address $NetworkID.ToString()).Int64 # Convert add available IPs and parse into IPAddress $Broadcast = [System.Net.IPAddress]::Parse((Convert-IPv4Address -Int64 ($NetworkID_Int64 + $AvailableIPs)).IPv4Address) # Change useroutput ==> (/27 = 0..31 IPs -> AvailableIPs 32) $AvailableIPs += 1 # Hosts = AvailableIPs - Network Address + Broadcast Address $Hosts = ($AvailableIPs - 2) # Build custom PSObject [pscustomobject] @{ NetworkID = $NetworkID Broadcast = $Broadcast IPs = $AvailableIPs Hosts = $Hosts } } End { } } } Process { # Calculate Subnet (Start and End IPv4-Address) if ($PSCmdlet.ParameterSetName -eq 'CIDR' -or $PSCmdlet.ParameterSetName -eq 'Mask') { # Convert Subnetmask if ($PSCmdlet.ParameterSetName -eq 'Mask') { $CIDR = (Convert-Subnetmask -Mask $Mask).CIDR } # Create new subnet $Subnet = Get-IPv4Subnet -IPv4Address $IPv4Address -CIDR $CIDR # Assign Start and End IPv4-Address $StartIPv4Address = $Subnet.NetworkID $EndIPv4Address = $Subnet.Broadcast } # Convert Start and End IPv4-Address to Int64 $StartIPv4Address_Int64 = (Convert-IPv4Address -IPv4Address $StartIPv4Address.ToString()).Int64 $EndIPv4Address_Int64 = (Convert-IPv4Address -IPv4Address $EndIPv4Address.ToString()).Int64 # Check if range is valid if ($StartIPv4Address_Int64 -gt $EndIPv4Address_Int64) { Write-Error -Message "Invalid IP-Range... Check your input!" -Category InvalidArgument -ErrorAction Stop } # Calculate IPs to scan (range) $IPsToScan = ($EndIPv4Address_Int64 - $StartIPv4Address_Int64) Write-Verbose -Message "Scanning range from $StartIPv4Address to $EndIPv4Address ($($IPsToScan + 1) IPs)" Write-Verbose -Message "Running with max $Threads threads" Write-Verbose -Message "ICMP checks per IP: $Tries" # Properties which are displayed in the output $PropertiesToDisplay = @() $PropertiesToDisplay += "IPv4Address", "Status" if ($DisableDNSResolving -eq $false) { $PropertiesToDisplay += "Hostname" } if ($EnableMACResolving) { $PropertiesToDisplay += "MAC" } # Check if it is possible to assign vendor to MAC --> import CSV-File if ($EnableMACResolving) { if (Test-Path -Path $OUIListPath -PathType Leaf) { $OUIHashTable = @{ } Write-Verbose -Message "Read oui.txt and fill hash table..." foreach ($Line in Get-Content -Path $OUIListPath) { if (-not([String]::IsNullOrEmpty($Line))) { try { $HashTableData = $Line.Split('|') $OUIHashTable.Add($HashTableData[0], $HashTableData[1]) } catch [System.ArgumentException] { } # Catch if mac is already added to hash table } } $AssignVendorToMAC = $true $PropertiesToDisplay += "Vendor" } else { $AssignVendorToMAC = $false Write-Warning -Message "No OUI-File to assign vendor with MAC-Address found! Execute the script ""Create-OUIListFromWeb.ps1"" to download the latest version. This warning does not affect the scanning procedure." } } if ($ExtendedInformations) { $PropertiesToDisplay += "BufferSize", "ResponseTime", "TTL" } # Scriptblock --> will run in runspaces (threads)... [System.Management.Automation.ScriptBlock]$ScriptBlock = { Param( $IPv4Address, $Tries, $DisableDNSResolving, $EnableMACResolving, $ExtendedInformations, $IncludeInactive ) # +++ Send ICMP requests +++ $Status = [String]::Empty for ($i = 0; $i -lt $Tries; i++) { try { $PingObj = New-Object System.Net.NetworkInformation.Ping $Timeout = 1000 $Buffer = New-Object Byte[] 32 $PingResult = $PingObj.Send($IPv4Address, $Timeout, $Buffer) if ($PingResult.Status -eq "Success") { $Status = "Up" break # Exit loop, if host is reachable } else { $Status = "Down" } } catch { $Status = "Down" break # Exit loop, if there is an error } } # +++ Resolve DNS +++ $Hostname = [String]::Empty if ((-not($DisableDNSResolving)) -and ($Status -eq "Up" -or $IncludeInactive)) { try { $Hostname = ([System.Net.Dns]::GetHostEntry($IPv4Address).HostName) } catch { } # No DNS } # +++ Get MAC-Address +++ $MAC = [String]::Empty if (($EnableMACResolving) -and (($Status -eq "Up") -or ($IncludeInactive))) { $Arp_Result = (arp -a).ToUpper().Trim() foreach ($Line in $Arp_Result) { if ($Line.Split(" ")[0] -eq $IPv4Address) { $MAC = [Regex]::Matches($Line, "([0-9A-F][0-9A-F]-){5}([0-9A-F][0-9A-F])").Value } } } # +++ Get extended informations +++ $BufferSize = [String]::Empty $ResponseTime = [String]::Empty $TTL = $null if ($ExtendedInformations -and ($Status -eq "Up")) { try { $BufferSize = $PingResult.Buffer.Length $ResponseTime = $PingResult.RoundtripTime $TTL = $PingResult.Options.Ttl } catch { } # Failed to get extended informations } # +++ Result +++ if (($Status -eq "Up") -or ($IncludeInactive)) { [pscustomobject] @{ IPv4Address = $IPv4Address Status = $Status Hostname = $Hostname MAC = $MAC BufferSize = $BufferSize ResponseTime = $ResponseTime TTL = $TTL } } else { $null } } Write-Verbose -Message "Setting up RunspacePool..." # Create RunspacePool and Jobs $RunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $Threads, $Host) $RunspacePool.Open() [System.Collections.ArrayList]$Jobs = @() Write-Verbose -Message "Setting up jobs..." # Set up jobs for each IP... for ($i = $StartIPv4Address_Int64; $i -le $EndIPv4Address_Int64; $i++) { # Convert IP back from Int64 $IPv4Address = (Convert-IPv4Address -Int64 $i).IPv4Address # Create hashtable to pass parameters $ScriptParams = @{ IPv4Address = $IPv4Address Tries = $Tries DisableDNSResolving = $DisableDNSResolving EnableMACResolving = $EnableMACResolving ExtendedInformations = $ExtendedInformations IncludeInactive = $IncludeInactive } # Catch when trying to divide through zero try { $Progress_Percent = (($i - $StartIPv4Address_Int64) / $IPsToScan) * 100 } catch { $Progress_Percent = 100 } Write-Progress -Activity "Setting up jobs..." -Id 1 -Status "Current IP-Address: $IPv4Address" -PercentComplete $Progress_Percent # Create new job $Job = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock).AddParameters($ScriptParams) $Job.RunspacePool = $RunspacePool $JobObj = [pscustomobject] @{ RunNum = $i - $StartIPv4Address_Int64 Pipe = $Job Result = $Job.BeginInvoke() } # Add job to collection [void]$Jobs.Add($JobObj) } Write-Verbose -Message "Waiting for jobs to complete & starting to process results..." # Total jobs to calculate percent complete, because jobs are removed after they are processed $Jobs_Total = $Jobs.Count # Process results, while waiting for other jobs Do { # Get all jobs, which are completed $Jobs_ToProcess = $Jobs | Where-Object -FilterScript { $_.Result.IsCompleted } # If no jobs finished yet, wait 500 ms and try again if ($null -eq $Jobs_ToProcess) { Write-Verbose -Message "No jobs completed, wait 250ms..." Start-Sleep -Milliseconds 250 continue } # Get jobs, which are not complete yet $Jobs_Remaining = ($Jobs | Where-Object -FilterScript { $_.Result.IsCompleted -eq $false }).Count # Catch when trying to divide through zero try { $Progress_Percent = 100 - (($Jobs_Remaining / $Jobs_Total) * 100) } catch { $Progress_Percent = 100 } Write-Progress -Activity "Waiting for jobs to complete... ($($Threads - $($RunspacePool.GetAvailableRunspaces())) of $Threads threads running)" -Id 1 -PercentComplete $Progress_Percent -Status "$Jobs_Remaining remaining..." Write-Verbose -Message "Processing $(if($null -eq $Jobs_ToProcess.Count){"1"}else{$Jobs_ToProcess.Count}) job(s)..." # Processing completed jobs foreach ($Job in $Jobs_ToProcess) { # Get the result... $Job_Result = $Job.Pipe.EndInvoke($Job.Result) $Job.Pipe.Dispose() # Remove job from collection $Jobs.Remove($Job) # Check if result contains status if ($Job_Result.Status) { if ($AssignVendorToMAC) { $Vendor = [String]::Empty # Check if MAC is null or empty if (-not([String]::IsNullOrEmpty($Job_Result.MAC))) { # Split it, so we can search the vendor (XX-XX-XX-XX-XX-XX to XXXXXX) $MAC_VendorSearch = $Job_Result.MAC.Replace("-", "").Substring(0, 6) $Vendor = $OUIHashTable.Get_Item($MAC_VendorSearch) } [pscustomobject] @{ IPv4Address = $Job_Result.IPv4Address Status = $Job_Result.Status Hostname = $Job_Result.Hostname MAC = $Job_Result.MAC Vendor = $Vendor BufferSize = $Job_Result.BufferSize ResponseTime = $Job_Result.ResponseTime TTL = $ResuJob_Resultlt.TTL } | Select-Object -Property $PropertiesToDisplay } else { $Job_Result | Select-Object -Property $PropertiesToDisplay } } } } While ($Jobs.Count -gt 0) Write-Verbose -Message "Closing RunspacePool and free resources..." # Close the RunspacePool and free resources $RunspacePool.Close() $RunspacePool.Dispose() Write-Verbose -Message "Script finished at $(Get-Date)" } End { } |