HostsFile.psm1

#region Internals
$script:hostFilePath = if ($PSEdition -eq 'Desktop' -or $IsWindows)
{
    "$($env:SystemRoot)\System32\drivers\etc\hosts"
}
elseif ($PSEdition -eq 'Core' -and $IsLinux)
{
    '/etc/hosts'
}

$type = @'
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace System.Net
{
    public class HostRecord
    {
        private IPAddress ipAddress;
        private string hostName;

        public IPAddress IpAddress
        {
            get { return ipAddress; }
            set { ipAddress = value; }
        }

        public string HostName
        {
            get { return hostName; }
            set { hostName = value; }
        }

        public HostRecord(IPAddress ipAddress, string hostName)
        {
            this.ipAddress = ipAddress;
            this.hostName = hostName;
        }

        public HostRecord(string ipAddress, string hostName)
        {
            this.ipAddress = IPAddress.Parse(ipAddress);
            this.hostName = hostName;
        }

        public override string ToString()
        {
            return string.Format("{0}\t{1}", this.ipAddress.ToString(), this.hostName);
        }

        public override bool Equals(object obj)
        {
            if (GetType() != obj.GetType())
                return false;

            var otherObject = (HostRecord)obj;

            if (this.hostName != otherObject.hostName)
                return false;

            return this.ipAddress.Equals(otherObject.ipAddress);
        }

        public override int GetHashCode()
        {
            return this.hostName.GetHashCode() ^ this.ipAddress.GetHashCode();
        }
    }
}
'@

#endregion Internals

Add-Type -TypeDefinition $type -PassThru

#region Get-HostFile
function Get-HostFile
{
    [CmdletBinding()]
    param
    (
        [switch]$SuppressOutput,

        [string]$Section
    )

    $hostContent = New-Object -TypeName System.Collections.ArrayList
    $hostEntries = New-Object -TypeName System.Collections.ArrayList

    Write-PSFMessage "Opening file '$script:hostFilePath'"

    $currentHostContent = (Get-Content -Path $script:hostFilePath)
    if ($currentHostContent)
    {
        $currentHostContent = $currentHostContent.ToLower()
    }

    if ($Section)
    {
        $startMark = ("#$Section - start").ToLower()
        $endMark = ("#$Section - end").ToLower()

        if (($currentHostContent | Where-Object { $_ -eq $startMark }) -and ($currentHostContent | Where-Object { $_ -eq $endMark }))
        {
            $startPosition = $currentHostContent.IndexOf($startMark) + 1
            $endPosition = $currentHostContent.IndexOf($endMark) - 1
            $currentHostContent = $currentHostContent[$startPosition..$endPosition]
        }
        else
        {
            $currentHostContent = ''
        }
    }

    if ($currentHostContent)
    {
        $hostContent.AddRange($currentHostContent)

        foreach ($entry in $currentHostContent)
        {
            $hostfileIpAddress = [System.Text.RegularExpressions.Regex]::Matches($entry, '^(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9])[.]){3}(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9]))')[0].Value
            $hostfileHostName = [System.Text.RegularExpressions.Regex]::Matches($entry, '[\w\.-]+$')[0].Value

            if ($entry -notmatch '^(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9])[.]){3}(([2]([0-4][0-9]|[5][0-5])|[0-1]?[0-9]?[0-9]))[\t| ]+[\w\.-]+')
            {
                continue
            }

            if (-not $hostfileIpAddress -or -not $hostfileHostName)
            {
                #could not get the IP address or hostname from current line
                continue
            }

            $newEntry = New-Object System.Net.HostRecord($hostfileIpAddress, $hostfileHostName.ToLower())
            $null = $hostEntries.Add($newEntry)
        }
    }

    Write-PSFMessage "File loaded with $($hostContent.Count) lines"

    $hostContent, $hostEntries
}
#endregion Get-HostFile

#region Get-HostEntry
function Get-HostEntry
{
    [CmdletBinding()]
    param (
        [Parameter(ParameterSetName = 'ByHostName')]
        [ValidateNotNullOrEmpty()][string]$HostName,

        [Parameter(ParameterSetName = 'ByIpAddress')]
        [ValidateNotNullOrEmpty()]
        [System.Net.IPAddress]$IpAddress,

        [Parameter()]
        [string]$Section
    )

    if ($Section)
    {
        $hostContent, $hostEntries = Get-HostFile -Section $Section
    }
    else
    {
        $hostContent, $hostEntries = Get-HostFile
    }

    if ($HostName)
    {
        $results = $hostEntries | Where-Object HostName -eq $HostName

        $hostEntries | Where-Object HostName -eq $HostName
    }
    elseif ($IpAddress)
    {
        $results = $hostEntries | Where-Object IpAddress -contains $IpAddress
        if (($results).count -gt 1)
        {
            Write-ScreenInfo -Message "More than one entry found in hosts file with IP address '$IpAddress' (host names: $($results.Hostname -join ','). Returning the last entry" -Type Warning
        }

        @($hostEntries | Where-Object IpAddress -contains $IpAddress)[-1]
    }
    else
    {
        $hostEntries
    }
}
#endregion Get-HostEntry

#region Add-HostEntry
function Add-HostEntry
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ParameterSetName = 'ByString')]
        [System.Net.IPAddress]$IpAddress,

        [Parameter(Mandatory, ParameterSetName = 'ByString')]
        $HostName,

        [Parameter(Mandatory, ParameterSetName = 'ByHostEntry')]
        $InputObject,

        [Parameter(Mandatory)]
        [string]$Section
    )

    if (-not $InputObject)
    {
        $InputObject = New-Object System.Net.HostRecord $IpAddress, $HostName.ToLower()
    }

    $hostContent, $hostEntries = Get-HostFile


    if ($hostEntries.Contains($InputObject))
    {
        return $false
    }

    if (($hostEntries | Where-Object HostName -eq $HostName) -and ($hostEntries | Where-Object HostName -eq $HostName).IpAddress.IPAddressToString -ne $IpAddress)
    {
        throw "Trying to add entry to hosts file with name '$HostName'. There is already another entry with this name pointing to another IP address."
    }

    $startMark = ("#$Section - start").ToLower()
    $endMark = ("#$Section - end").ToLower()

    if (-not ($hostContent | Where-Object { $_ -eq $startMark }))
    {
        $hostContent.Add($startMark) | Out-Null
        $hostContent.Add($endMark) | Out-Null
    }

    $hostContent.Insert($hostContent.IndexOf($endMark), $InputObject.ToString().ToLower())
    $hostEntries.Add($InputObject.ToString().ToLower()) | Out-Null

    $hostContent | Out-File -FilePath $script:hostFilePath

    return $true
}
#endregion Add-HostEntry

#region Remove-HostEntry
function Remove-HostEntry
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ParameterSetName = 'ByIpAddress')]
        [System.Net.IPAddress]$IpAddress,

        [Parameter(Mandatory, ParameterSetName = 'ByHostName')]
        $HostName,

        [Parameter(Mandatory, ParameterSetName = 'ByHostEntry')]
        $InputObject,

        [Parameter(Mandatory)]
        [string]$Section
    )

    if (-not $InputObject -and -not $IpAddress -and -not $HostName)
    {
        return
    }

    if ($InputObject)
    {
        $entriesToRemove = $InputObject
    }
    else
    {
        if (-not $InputObject -and ($IpAddress -or $HostName))
        {
            $entriesToRemove = Get-HostEntry @PSBoundParameters
        }
    }

    if (-not $entriesToRemove)
    {
        Write-Error "Trying to remove entry '$HostName' from hosts file. However, there is no entry by that name in this file"
    }

    $hostContent, $hostEntries = Get-HostFile -SuppressOutput

    $startMark = ("#$Section - start").ToLower()
    if (-not ($hostContent | Where-Object { $_ -eq $startMark }))
    {
        Write-Error "Trying to remove entry '$HostName' from hosts file. However, there is no section named '$Section' defined in the hosts file which is a requirement for removing entries from this."
        return
    }
    elseif ($entriesToRemove.Count -gt 1)
    {
        Write-Error "Trying to remove entry '$HostName' from hosts file. However, there are more than one entry with this name in the hosts file. Please remove this entry manually."
        return
    }

    if ($entriesToRemove)
    {
        $entryToRemove = ($hostContent -match "^($($entriesToRemove.IpAddress))[\t| ]+$($entriesToRemove.HostName)")[0]
        $entryToRemoveIndex = $hostContent.IndexOf($entryToRemove)

        $hostContent.RemoveAt($entryToRemoveIndex)
        $hostEntries.Remove($entriesToRemove)

        $hostContent | Out-File -FilePath $script:hostFilePath
    }
}
#endregion Remove-HostEntry

#region function Clear-HostFile
function Clear-HostFile
{
    [CmdletBinding()]

    param
    (
        [Parameter(Mandatory)]
        [string]$Section
    )

    $hostContent, $hostEntries = Get-HostFile

    $startMark = ("#$Section - start").ToLower()
    $endMark = ("#$Section - end").ToLower()

    $startPosition = $hostContent.IndexOf($startMark)
    $endPosition = $hostContent.IndexOf($endMark)
    if ($startPosition -eq -1 -and $endPosition - 1)
    {
        Write-Error "Trying to remove all entries for lab from host file. However, there is no section named '$Section' defined in the hosts file which is a requirement for removing entries from this."
        return
    }

    $hostContent.RemoveRange($startPosition, $endPosition - $startPosition + 1)
    $hostContent | Out-File -FilePath $script:hostFilePath
}
#endregion function Clear-HostFile