mirror of
https://github.com/Stone-Red-Code/ARP-Scanner.git
synced 2026-09-04 08:56:04 +02:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f49f2d595 | ||
|
|
92b1fd0598 | ||
|
|
6f6a2c2ee5 | ||
|
|
687b882d5e | ||
|
|
95774eaee8 | ||
|
|
6de6edbf48 | ||
|
|
dabc878624 | ||
|
|
1420655b8d | ||
|
|
96b261238e | ||
|
|
89f1401de9 | ||
|
|
924e0016cb | ||
|
|
40cb53d037 | ||
|
|
a0c2811fae | ||
|
|
a4536f05d8 | ||
|
|
38ea2e99c7 |
@@ -6,8 +6,7 @@
|
|||||||
|
|
||||||
1. Download the latest release
|
1. Download the latest release
|
||||||
- Chocolatey (Windows): `choco install arp-scanner`
|
- Chocolatey (Windows): `choco install arp-scanner`
|
||||||
- Snapcraft (Linux): `snap install arp-scanner`\
|
- Snapcraft (Linux): `snap install arp-scanner`
|
||||||
and `snap connect arp-scanner:network-control` to give it permission to send arp requests
|
|
||||||
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
|
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
|
||||||
1. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
|
1. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
|
||||||
|
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
> This programm may not work on all linux distributions because the [ArpLookup](https://github.com/georg-jung/ArpLookup) library has some [limitations](https://github.com/georg-jung/ArpLookup#supported-platforms)
|
> This programm may not work on all linux distributions because the [ArpLookup](https://github.com/georg-jung/ArpLookup) library has some [limitations](https://github.com/georg-jung/ArpLookup#supported-platforms)
|
||||||
|
|
||||||
## Follow the development
|
## Follow the development
|
||||||
[](https://twitter.com/search?q=%23ArpScanner%20%40StoneRedCode)
|
[](https://twitter.com/search?q=%23ArpScanner%20%40StoneRedCode&f=live)
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||

|

|
||||||
|
|||||||
@@ -40,7 +40,7 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t
|
|||||||
<!-- Note that unstable versions like 0.0.1 can be considered a released version, but it's
|
<!-- Note that unstable versions like 0.0.1 can be considered a released version, but it's
|
||||||
possible that one can release a 0.0.1-beta before you release a 0.0.1 version. If the version
|
possible that one can release a 0.0.1-beta before you release a 0.0.1 version. If the version
|
||||||
number is final, that is considered a released version and not a prerelease. -->
|
number is final, that is considered a released version and not a prerelease. -->
|
||||||
<version>0.2.0.20240103</version>
|
<version>1.0.0.0</version>
|
||||||
<!-- <packageSourceUrl>Where is this Chocolatey package located (think GitHub)? packageSourceUrl
|
<!-- <packageSourceUrl>Where is this Chocolatey package located (think GitHub)? packageSourceUrl
|
||||||
is highly recommended for the community feed</packageSourceUrl>-->
|
is highly recommended for the community feed</packageSourceUrl>-->
|
||||||
<!-- owners is a poor name for maintainers of the package. It sticks around by this name for
|
<!-- owners is a poor name for maintainers of the package. It sticks around by this name for
|
||||||
@@ -68,17 +68,16 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t
|
|||||||
what url are the software docs located?</docsUrl>-->
|
what url are the software docs located?</docsUrl>-->
|
||||||
<!--<mailingListUrl></mailingListUrl>-->
|
<!--<mailingListUrl></mailingListUrl>-->
|
||||||
<!--<bugTrackerUrl></bugTrackerUrl>-->
|
<!--<bugTrackerUrl></bugTrackerUrl>-->
|
||||||
<tags>arp-scanner arp p2p fiel-transfer</tags>
|
<tags>arp-scanner arp</tags>
|
||||||
<summary>A lightweight cross-platform IP scanner</summary>
|
<summary>A lightweight cross-platform IP scanner</summary>
|
||||||
<description>
|
<description>
|
||||||
**Usage**
|
**Usage**
|
||||||
|
|
||||||
1. Download the latest release
|
1. Download the latest release
|
||||||
- Chocolatey (Windows): `choco install arp-scanner --version=0.2.0`
|
- Chocolatey (Windows): `choco install arp-scanner`
|
||||||
- Snapcraft (Linux): `snap install arp-scanner`\
|
- Snapcraft (Linux): `snap install arp-scanner`
|
||||||
and `snap connect arp-scanner:network-control` to give it permission to send arp requests
|
|
||||||
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
|
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
|
||||||
1. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
|
2. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
|
||||||
|
|
||||||
> Note: This programm may not work on all linux distributions because the [ArpLookup](https://github.com/georg-jung/ArpLookup) library has some [limitations](https://github.com/georg-jung/ArpLookup#supported-platforms)
|
> Note: This programm may not work on all linux distributions because the [ArpLookup](https://github.com/georg-jung/ArpLookup) library has some [limitations](https://github.com/georg-jung/ArpLookup#supported-platforms)
|
||||||
|
|
||||||
@@ -112,6 +111,6 @@ enclosed in quotation marks, you should use an editor that supports UTF-8, not t
|
|||||||
<files>
|
<files>
|
||||||
<!-- this section controls what actually gets packaged into the Chocolatey package -->
|
<!-- this section controls what actually gets packaged into the Chocolatey package -->
|
||||||
<file src="tools\**" target="tools" />
|
<file src="tools\**" target="tools" />
|
||||||
<file src="..\src\ARP-Scanner\bin\Release\net6.0\**" target="tools" />
|
<file src="..\src\ARP-Scanner\bin\Release\net8.0\**" target="tools" />
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
@@ -2,5 +2,7 @@
|
|||||||
Verification is intended to assist the Chocolatey moderators and community
|
Verification is intended to assist the Chocolatey moderators and community
|
||||||
in verifying that this package's contents are trustworthy.
|
in verifying that this package's contents are trustworthy.
|
||||||
|
|
||||||
|
Source Code: https://github.com/Stone-Red-Code/Arp-Scanner
|
||||||
|
Releases: https://github.com/Stone-Red-Code/ARP-Scanner/releases
|
||||||
Checksum: c37a793bae228da768c9d4223d12608bfa28decbe7142e9ac19a49795cf23b82b2f0434032b98883343c658276bf2e4423926fbf79c65c48ae6cf11ad48d37ad
|
Checksum: c37a793bae228da768c9d4223d12608bfa28decbe7142e9ac19a49795cf23b82b2f0434032b98883343c658276bf2e4423926fbf79c65c48ae6cf11ad48d37ad
|
||||||
Veify using: certutil -hashfile ARP-Scanner.exe sha512
|
Veify using: certutil -hashfile C:\ProgramData\Chocolatey\lib\arp-scanner\ARP-Scanner.exe sha512
|
||||||
+5
-6
@@ -1,14 +1,13 @@
|
|||||||
name: arp-scanner # you probably want to 'snapcraft register <name>'
|
name: arp-scanner # you probably want to 'snapcraft register <name>'
|
||||||
version: "0.2.0.20240103" # just for humans, typically '1.2+git' or '1.3.2'
|
version: "1.0.0.0" # just for humans, typically '1.2+git' or '1.3.2'
|
||||||
grade: stable # must be 'stable' to release into candidate/stable channels
|
grade: stable # must be 'stable' to release into candidate/stable channels
|
||||||
summary: A lightweight cross-platform IP scanner # 79 char long summary
|
summary: A lightweight cross-platform IP scanner # 79 char long summary
|
||||||
description: |
|
description: |
|
||||||
**Usage**
|
**Usage**
|
||||||
|
|
||||||
1. Download the latest release
|
1. Download the latest release
|
||||||
- Chocolatey (Windows): `choco install arp-scanner --version=0.2.0`
|
- Chocolatey (Windows): `choco install arp-scanner`
|
||||||
- Snapcraft (Linux): `snap install arp-scanner`\
|
- Snapcraft (Linux): `snap install arp-scanner`
|
||||||
and `snap connect arp-scanner:network-control` to give it permission to send arp requests
|
|
||||||
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
|
- GitHub (Windows/Linux): https://github.com/Stone-Red-Code/ARP-Scanner/releases
|
||||||
1. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
|
1. Start `arp-scanner` with the IP range it should scan as parameter (e.g. `192.168.1.0 - 192.168.1.255`)
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ parts:
|
|||||||
dotnet-self-contained-runtime-identifier: linux-x64
|
dotnet-self-contained-runtime-identifier: linux-x64
|
||||||
source: ./src/ARP-Scanner
|
source: ./src/ARP-Scanner
|
||||||
build-packages:
|
build-packages:
|
||||||
- dotnet-sdk-6.0
|
- dotnet-sdk-8.0
|
||||||
stage-packages:
|
stage-packages:
|
||||||
- libicu70
|
- libicu70
|
||||||
|
|
||||||
@@ -42,4 +41,4 @@ apps:
|
|||||||
arp-scanner:
|
arp-scanner:
|
||||||
command: ARP-Scanner
|
command: ARP-Scanner
|
||||||
plugs:
|
plugs:
|
||||||
- network-control
|
- network-observe
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<RootNamespace>ARP_Scanner</RootNamespace>
|
<RootNamespace>ARP_Scanner</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
@@ -10,14 +10,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ArpLookup" Version="2.0.3" />
|
<PackageReference Include="ArpLookup" Version="2.0.3" />
|
||||||
|
<PackageReference Include="CuteUtils" Version="1.0.0" />
|
||||||
<PackageReference Include="IPAddressRange" Version="6.0.0" />
|
<PackageReference Include="IPAddressRange" Version="6.0.0" />
|
||||||
<PackageReference Include="Stone_Red-C-Sharp-Utilities" Version="1.0.3.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="mac-vendors.csv">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace ARP_Scanner;
|
||||||
|
|
||||||
|
internal class MacDatabase
|
||||||
|
{
|
||||||
|
public DateTime LastUpdate { get; set; }
|
||||||
|
public List<MacInformation> MacInformations { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace ARP_Scanner;
|
||||||
|
|
||||||
|
internal class MacInformation
|
||||||
|
{
|
||||||
|
[JsonPropertyName("macPrefix")]
|
||||||
|
public required string MacPrefix { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("vendorName")]
|
||||||
|
public required string VendorName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("private")]
|
||||||
|
public bool? Private { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("blockType")]
|
||||||
|
public required string BlockType { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lastUpdate")]
|
||||||
|
public required string LastUpdate { get; set; }
|
||||||
|
}
|
||||||
@@ -1,57 +1,95 @@
|
|||||||
using System.Text.RegularExpressions;
|
using CuteUtils.Misc;
|
||||||
|
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ARP_Scanner;
|
namespace ARP_Scanner;
|
||||||
|
|
||||||
internal class MacVendorLookup
|
internal partial class MacVendorLookup
|
||||||
{
|
{
|
||||||
public readonly string[] header;
|
private const string macLookupUrl = "https://maclookup.app/downloads/json-database/get-db";
|
||||||
private readonly string[][] fields;
|
private readonly HttpClient httpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(20) };
|
||||||
|
|
||||||
public MacVendorLookup(string csvPath)
|
private MacDatabase macDatabase = new MacDatabase();
|
||||||
|
|
||||||
|
public async Task Initialize()
|
||||||
{
|
{
|
||||||
if (!File.Exists(csvPath))
|
string cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "macDatabase.json");
|
||||||
|
|
||||||
|
// Snap support
|
||||||
|
string? snapUserCommon = Environment.GetEnvironmentVariable("SNAP_USER_COMMON");
|
||||||
|
|
||||||
|
if (snapUserCommon is not null)
|
||||||
{
|
{
|
||||||
header = Array.Empty<string>();
|
cachePath = Path.Combine(snapUserCommon, "macDatabase.json");
|
||||||
fields = Array.Empty<string[]>();
|
}
|
||||||
|
|
||||||
|
if (File.Exists(cachePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
macDatabase = JsonSerializer.Deserialize<MacDatabase>(File.ReadAllText(cachePath)) ?? new MacDatabase();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ConsoleExt.WriteLine($"Failed to read MAC database cache: {ex.Message}", ConsoleColor.Red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update MAC database if it's older than a week
|
||||||
|
if (macDatabase.LastUpdate > DateTime.Now.AddDays(-7))
|
||||||
|
{
|
||||||
|
ConsoleExt.WriteLine("Using cached MAC database...", ConsoleColor.DarkYellow);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] lines = File.ReadAllLines(csvPath);
|
ConsoleExt.WriteLine("Downloading MAC database from maclookup.app...", ConsoleColor.DarkYellow);
|
||||||
|
|
||||||
header = lines[0].Split(',');
|
List<MacInformation>? newMacInformations = null;
|
||||||
fields = lines.Skip(1).Select(l => Regex.Split(l, ",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))")).ToArray();
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newMacInformations = await httpClient.GetFromJsonAsync<List<MacInformation>>(macLookupUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ConsoleExt.WriteLine($"Failed to download MAC database: {ex.Message}", ConsoleColor.Red);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetInformation(string macAdress)
|
if (newMacInformations is null)
|
||||||
{
|
{
|
||||||
string[]? data = Array.Find(fields, f => macAdress.StartsWith(f[0]));
|
if (macDatabase.MacInformations.Count != 0)
|
||||||
|
|
||||||
if (fields.Length == 0 || header.Length == 0)
|
|
||||||
{
|
{
|
||||||
return Array.Empty<string>();
|
ConsoleExt.WriteLine("Failed to download MAC database, using cache...", ConsoleColor.DarkYellow);
|
||||||
}
|
|
||||||
else if (data is null)
|
|
||||||
{
|
|
||||||
string[] result = new string[header.Length - 1];
|
|
||||||
for (int i = 0; i < result.Length; i++)
|
|
||||||
{
|
|
||||||
result[i] = "Unknown";
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
List<string> result = new List<string>();
|
ConsoleExt.WriteLine("Failed to download MAC database and no cache found, using empty database...", ConsoleColor.Red);
|
||||||
for (int i = 1; i < header.Length; i++)
|
|
||||||
{
|
|
||||||
result.Add($"{data[i]}");
|
|
||||||
}
|
}
|
||||||
return result.ToArray();
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConsoleExt.WriteLine("MAC database downloaded successfully!", ConsoleColor.Green);
|
||||||
|
|
||||||
|
_ = Directory.CreateDirectory(Path.GetDirectoryName(cachePath)!);
|
||||||
|
|
||||||
|
macDatabase.MacInformations = newMacInformations;
|
||||||
|
macDatabase.LastUpdate = DateTime.Now;
|
||||||
|
File.WriteAllText(cachePath, JsonSerializer.Serialize(macDatabase));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetHeader()
|
public MacInformation GetInformation(string macAdress)
|
||||||
{
|
{
|
||||||
return header!.Skip(1).ToArray();
|
macAdress = macAdress.Replace("-", ":")[..8].ToUpper();
|
||||||
|
|
||||||
|
return macDatabase.MacInformations.Find(m => m.MacPrefix == macAdress) ?? new MacInformation()
|
||||||
|
{
|
||||||
|
MacPrefix = macAdress,
|
||||||
|
VendorName = "Unknown",
|
||||||
|
BlockType = "Unknown",
|
||||||
|
Private = false,
|
||||||
|
LastUpdate = "Unknown"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
-13
@@ -1,9 +1,8 @@
|
|||||||
using ArpLookup;
|
using ArpLookup;
|
||||||
|
|
||||||
using NetTools;
|
using CuteUtils.Misc;
|
||||||
|
|
||||||
using Stone_Red_Utilities.CollectionExtentions;
|
using NetTools;
|
||||||
using Stone_Red_Utilities.ConsoleExtentions;
|
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -15,7 +14,7 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
private static async Task Main(string[] args)
|
private static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
MacVendorLookup macVendorLookup = new MacVendorLookup("mac-vendors.csv");
|
MacVendorLookup macVendorLookup = new MacVendorLookup();
|
||||||
|
|
||||||
if (!Arp.IsSupported)
|
if (!Arp.IsSupported)
|
||||||
{
|
{
|
||||||
@@ -29,14 +28,16 @@ internal static class Program
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await macVendorLookup.Initialize();
|
||||||
|
|
||||||
long ipAddressesCount = ipAddressRange.Count();
|
long ipAddressesCount = ipAddressRange.Count();
|
||||||
long processedIpAddressesCount = 0;
|
long processedIpAddressesCount = 0;
|
||||||
int numberOfDigits = ipAddressesCount.ToString().Length;
|
int numberOfDigits = ipAddressesCount.ToString().Length;
|
||||||
|
|
||||||
List<string> header = new List<string>() { "IP", "MAC" };
|
List<string> header = ["IP", "MAC"];
|
||||||
ConcurrentBag<string[]> activeHosts = new ConcurrentBag<string[]>();
|
ConcurrentBag<string[]> activeHosts = [];
|
||||||
|
|
||||||
header.AddRange(macVendorLookup.GetHeader());
|
header.AddRange([nameof(MacInformation.VendorName), nameof(MacInformation.BlockType), nameof(MacInformation.Private), nameof(MacInformation.LastUpdate)]);
|
||||||
|
|
||||||
ConsoleExt.WriteLine("Starting scan...", ConsoleColor.DarkYellow);
|
ConsoleExt.WriteLine("Starting scan...", ConsoleColor.DarkYellow);
|
||||||
|
|
||||||
@@ -49,8 +50,9 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
mac = await Arp.LookupAsync(ipAddress);
|
mac = await Arp.LookupAsync(ipAddress);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
ConsoleExt.WriteLine($"Failed to lookup MAC address for {ipAddress}: {ex.Message}", ConsoleColor.Red);
|
||||||
fail = true;
|
fail = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +61,11 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
string formattedMac = BitConverter.ToString(mac.GetAddressBytes());
|
string formattedMac = BitConverter.ToString(mac.GetAddressBytes());
|
||||||
|
|
||||||
List<string> info = new List<string> { ipAddress.ToString(), formattedMac };
|
MacInformation macInformation = macVendorLookup.GetInformation(formattedMac);
|
||||||
info.AddRange(macVendorLookup.GetInformation(formattedMac));
|
|
||||||
|
List<string> info = [ipAddress.ToString(), formattedMac, macInformation.VendorName, macInformation.BlockType, macInformation.Private.ToString() ?? "Unknown", macInformation.LastUpdate];
|
||||||
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Active: {ipAddress}", ConsoleColor.Green);
|
ConsoleExt.WriteLine($"Progress: {localProcessedIpAddressesCount.ToString().PadLeft(numberOfDigits)}/{ipAddressesCount} [{100d / ipAddressesCount * localProcessedIpAddressesCount,6:##0.00}%] | Active: {ipAddress}", ConsoleColor.Green);
|
||||||
activeHosts.Add(info.ToArray());
|
activeHosts.Add([.. info]);
|
||||||
}
|
}
|
||||||
else if (fail)
|
else if (fail)
|
||||||
{
|
{
|
||||||
@@ -78,9 +81,9 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
Console.WriteLine(Environment.NewLine + "Active hosts:");
|
Console.WriteLine(Environment.NewLine + "Active hosts:");
|
||||||
|
|
||||||
List<string[]>? activeHostsTable = activeHosts.ToList();
|
List<string[]>? activeHostsTable = [.. activeHosts];
|
||||||
|
|
||||||
activeHostsTable.Insert(0, header.ToArray());
|
activeHostsTable.Insert(0, [.. header]);
|
||||||
|
|
||||||
activeHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
|
activeHostsTable.ToArray().To2D().PrintTable(TableStyle.List);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user