Private/Network.psm1

using module .\Config.psm1
using module .\Console.psm1
using module .\Models.psm1
using module .\Utilities.psm1

class HostsEntry {
  [int]$LineNumber
  [string]$IPAddress
  [bool]$IsValidIP
  [string]$Hostname
  [string]$Comment

  HostsEntry([int]$LineNumber, [string]$IPAddress, [string]$Hostname, [string]$Comment) {
    $this.LineNumber = $LineNumber
    $this.IPAddress = $IPAddress
    $this.Hostname = $Hostname
    $this.Comment = $Comment.Trim()
    $testIP = $null
    $this.IsValidIP = [Net.IPAddress]::TryParse($IPAddress, [ref]$testIP)
  }

  [string] ToString() {
    if ([string]::IsNullOrWhiteSpace($this.Comment)) {
      return "$($this.IPAddress) $($this.Hostname)"
    }
    return "$($this.IPAddress) $($this.Hostname) # $($this.Comment)"
  }
}


class HostsFile {
  static [string] $DefaultPath = (Join-Path $Env:SystemRoot 'System32\drivers\etc\hosts')
  [string] $Path
  [System.Collections.Generic.List[HostsEntry]] $Entries
  hidden [string[]] $RawLines

  HostsFile([string]$Path) {
    if (!(Test-Path $Path)) {
      throw "Hosts file does not exist: $Path"
    }
    $this.Path = $Path
    $this.Entries = [System.Collections.Generic.List[HostsEntry]]::new()
    $this.Load()
  }

  static [HostsFile] Create() {
    return [HostsFile]::new([HostsFile]::DefaultPath)
  }

  static [HostsFile] Create([string]$Path) {
    return [HostsFile]::new($Path)
  }

  hidden [void] Load() {
    $this.RawLines = Get-Content $this.Path -ErrorAction Stop
    $commentLine = '^\s*#'
    $hostLine = '^\s*(?<IPAddress>\S+)\s+(?<Hostname>\S+)(\s*|\s+#(?<Comment>.*))$'
    for ($i = 0; $i -lt $this.RawLines.Length; $i++) {
      $line = $this.RawLines[$i]
      if (!($line -match $commentLine) -and ($line -match $hostLine)) {
        $comment = ''
        if ($Matches['Comment']) { $comment = $Matches['Comment'] }
        $entry = [HostsEntry]::new($i, $Matches['IPAddress'], $Matches['Hostname'], $comment)
        $this.Entries.Add($entry)
      }
    }
  }

  [HostsEntry[]] GetEntries() { return $this.Entries.ToArray() }

  [HostsEntry] GetByHostname([string]$Hostname) {
    foreach ($entry in $this.Entries) {
      if ($entry.Hostname -eq $Hostname) { return $entry }
    }
    return $null
  }

  [HostsEntry[]] GetByIPAddress([string]$IPAddress) {
    $results = [System.Collections.Generic.List[HostsEntry]]::new()
    foreach ($entry in $this.Entries) {
      if ($entry.IPAddress -eq $IPAddress) { $results.Add($entry) }
    }
    return $results.ToArray()
  }

  [void] Add([string]$IPAddress, [string]$Hostname) { $this.Add($IPAddress, $Hostname, "") }

  [void] Add([string]$IPAddress, [string]$Hostname, [string]$Comment) {
    $existing = $this.GetByHostname($Hostname)
    $newEntry = [HostsEntry]::new(-1, $IPAddress, $Hostname, $Comment)
    if ($existing) {
      $this.RawLines[$existing.LineNumber] = $newEntry.ToString()
      $existing.IPAddress = $IPAddress
      $existing.Comment = $Comment
      $existing.IsValidIP = [Net.IPAddress]::TryParse($IPAddress, [ref]([ipaddress]::Any))
    } else {
      $this.RawLines += $newEntry.ToString()
      $newEntry.LineNumber = $this.RawLines.Length - 1
      $this.Entries.Add($newEntry)
    }
  }

  [void] RemoveByHostname([string]$Hostname) {
    $remainingLines = [System.Collections.Generic.List[string]]::new()
    $newEntries = [System.Collections.Generic.List[HostsEntry]]::new()
    $lineNumber = 0
    foreach ($entry in $this.Entries) {
      if ($entry.Hostname -ne $Hostname) {
        $remainingLines.Add($entry.ToString())
        $entry.LineNumber = $lineNumber
        $newEntries.Add($entry)
        $lineNumber++
      }
    }
    $this.RawLines = $remainingLines.ToArray()
    $this.Entries = $newEntries
  }

  [void] RemoveByIPAddress([string]$IPAddress) {
    $remainingLines = [System.Collections.Generic.List[string]]::new()
    $newEntries = [System.Collections.Generic.List[HostsEntry]]::new()
    $lineNumber = 0
    foreach ($entry in $this.Entries) {
      if ($entry.IPAddress -ne $IPAddress) {
        $remainingLines.Add($entry.ToString())
        $entry.LineNumber = $lineNumber
        $newEntries.Add($entry)
        $lineNumber++
      }
    }
    $this.RawLines = $remainingLines.ToArray()
    $this.Entries = $newEntries
  }

  [void] Save() { $this.RawLines | Out-File -Encoding ascii -FilePath $this.Path -ErrorAction Stop }
  [void] Show() { notepad $this.Path }
  [string] ToString() { return $this.Path }
}


class NetworkManager {
  [string] $HostName
  static [System.Net.IPAddress[]] $IPAddresses

  static [string] $caller

  NetworkManager([string]$HostName) {
    $this.HostName = $HostName
    $this::IPAddresses = [System.Net.Dns]::GetHostAddresses($HostName)
  }

  static [string] GetResponse([string]$URL) {
    [System.Net.HttpWebRequest]$Request = [System.Net.HttpWebRequest]::Create($URL)
    $Request.Method = "GET"
    $Request.Timeout = 10000
    [System.Net.HttpWebResponse]$Response = [System.Net.HttpWebResponse]$Request.GetResponse()
    if ($Response.StatusCode -eq [System.Net.HttpStatusCode]::OK) {
      [System.IO.Stream]$ReceiveStream = $Response.GetResponseStream()
      [System.IO.StreamReader]$ReadStream = [System.IO.StreamReader]::new($ReceiveStream)
      [string]$Content = $ReadStream.ReadToEnd()
      $ReadStream.Close()
      $Response.Close()
      return $Content
    } else {
      throw "The request failed with status code: $($Response.StatusCode)"
    }
  }

  static [void] BlockAllOutbound() {
    if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
      & sudo iptables -P OUTPUT DROP
    } else {
      netsh advfirewall set allprofiles firewallpolicy blockinbound, blockoutbound
    }
  }

  static [void] UnblockAllOutbound() {
    if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux)) {
      & sudo iptables -P OUTPUT ACCEPT
    } else {
      netsh advfirewall set allprofiles firewallpolicy blockinbound, allowoutbound
    }
  }

  static [void] UploadFile([string]$SourcePath, [string]$DestinationURL) {
    Invoke-RestMethod -Uri $DestinationURL -Method Post -InFile $SourcePath
  }

  static [bool] TestConnection([string]$HostName) {
    [ValidateNotNullOrEmpty()][string]$HostName = $HostName
    if (![bool]("System.Net.NetworkInformation.Ping" -as 'type')) { Add-Type -AssemblyName System.Net.NetworkInformation }
    $cs = $null; $cc = [NetworkManager]::caller
    $re = @{ true = @{ m = "Success"; c = "Green" }; false = @{ m = "Failed"; c = "Red" } }
    Write-Host "$cc Testing Connection ... " -ForegroundColor Blue -NoNewline
    try {
      [System.Net.NetworkInformation.PingReply]$PingReply = [System.Net.NetworkInformation.Ping]::new().Send($HostName)
      $cs = $PingReply.Status -eq [System.Net.NetworkInformation.IPStatus]::Success
    } catch [System.Net.Sockets.SocketException], [System.Net.NetworkInformation.PingException] {
      $cs = $false
    } catch {
      $cs = $false
      Write-Error $_
    }
    $re = $re[$cs.ToString()]
    Write-Host $re.m -ForegroundColor $re.c
    return $cs
  }

  static [bool] IsIPv6AddressValid([string]$IP) {
    $IPv4Regex = '(((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))'
    $G = '[a-f\d]{1,4}'
    $Tail = @(":",
      "(:($G)?|$IPv4Regex)",
      ":($IPv4Regex|$G(:$G)?|)",
      "(:$IPv4Regex|:$G(:$IPv4Regex|(:$G){0,2})|:)",
      "((:$G){0,2}(:$IPv4Regex|(:$G){1,2})|:)",
      "((:$G){0,3}(:$IPv4Regex|(:$G){1,2})|:)",
      "((:$G){0,4}(:$IPv4Regex|(:$G){1,2})|:)")
    [string] $IPv6RegexString = $G
    $Tail | ForEach-Object { $IPv6RegexString = "${G}:($IPv6RegexString|$_)" }
    $IPv6RegexString = ":(:$G){0,5}((:$G){1,2}|:$IPv4Regex)|$IPv6RegexString"
    $IPv6RegexString = $IPv6RegexString -replace '\(' , '(?:'
    [regex] $IPv6Regex = $IPv6RegexString
    return ($IP -imatch "^$IPv6Regex$")
  }

  static [bool] IsMACAddressValid([string]$mac) {
    $RegEx = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9A-Fa-f]{2}){6}$"
    return ($mac -match $RegEx)
  }

  static [bool] IsSubNetMaskValid([string]$IP) {
    $RegEx = "^(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 ($IP -match $RegEx)
  }

  static [bool] IsIPv4AddressValid([string]$IP) {
    $RegEx = "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
    return ($IP -match $RegEx)
  }
}