commit 3a5c753df37473c9a88337f0efe12588c66bb7dc Author: djamel Date: Thu Oct 3 14:46:04 2024 +0200 Ajouter scan.ps1 diff --git a/scan.ps1 b/scan.ps1 new file mode 100644 index 0000000..fb34fad --- /dev/null +++ b/scan.ps1 @@ -0,0 +1,634 @@ +############################################################################################################### +# 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 { + +} \ No newline at end of file