Skip to content

Instantly share code, notes, and snippets.

@joshooaj
Last active October 9, 2021 16:11
Show Gist options
  • Save joshooaj/3539a54f58d0f7bf1cd5a4e27aab4908 to your computer and use it in GitHub Desktop.
Save joshooaj/3539a54f58d0f7bf1cd5a4e27aab4908 to your computer and use it in GitHub Desktop.
My hosts file display and manipulation functions for my profile script
class HostsFileEntry {
[IPAddress] $IP
[string[]] $Hosts
[string] ToString() {
return "$($this.IP) $([string]::Join(' ', $this.Hosts))"
}
}
function Get-HostsFileEntry {
<#
.SYNOPSIS
Gets a list of all hosts file entries
.DESCRIPTION
Gets a list of all hosts file entries and returns them as [HostsFileEntry] objects.
.PARAMETER IP
Not used yet
.PARAMETER Hosts
Not used yet
.EXAMPLE
Get-HostsFileEntry | Foreach-Object { $_.ToString() }
Prints all hosts file entries in a similar way to how they're entered in the hosts file.
#>
[CmdletBinding(DefaultParameterSetName = 'All')]
[OutputType([HostsFileEntry])]
param (
[Parameter(ParameterSetName = 'ByIP')]
[IPAddress[]]
$IP,
[Parameter(ParameterSetName = 'ByHost')]
[string[]]
$Hosts
)
process {
$hostsPath = Join-Path -Path ([environment]::GetFolderPath([environment+specialfolder]::System)) -ChildPath 'drivers\etc\hosts'
Get-Content -Path $hostsPath | Where-Object { ![string]::IsNullOrWhiteSpace($_) -and !$_.Trim().StartsWith('#')} | Foreach-Object {
$entry = @{}
$entry.IP, $entry.Hosts = $_.Split(@(' ', "`t"), [System.StringSplitOptions]::RemoveEmptyEntries)
$obj = [HostsFileEntry]$entry
switch ($PSCmdlet.ParameterSetName) {
'ByIP' {
if ($entry.IP -eq $IP) {
Write-Output $obj
}
}
'ByHost' {
foreach ($h in $Hosts) {
if ($h -in $entry.Hosts) {
Write-Output $obj
break
}
}
}
default {
Write-Output $obj
}
}
}
}
}
function New-HostsFileEntry {
[CmdletBinding()]
[OutputType([HostsFileEntry])]
param (
[Parameter(Mandatory)]
[ValidateScript({
$supportedAddressFamilies = @(
[System.Net.Sockets.AddressFamily]::InterNetwork,
[System.Net.Sockets.AddressFamily]::InterNetworkV6
)
if ($_.AddressFamily -notin $supportedAddressFamilies) {
throw "IPAddress must be IPv4 or IPv6."
}
return $true
})]
[IPAddress]
$IP,
[Parameter(Mandatory)]
[ValidateCount(1, 100)]
[ValidateNotNullOrEmpty()]
[string[]]
$Hosts
)
process {
Write-Output ([HostsFileEntry]@{IP = $IP; Hosts = $Hosts})
}
}
function New-HostsFileContent {
[CmdletBinding()]
[OutputType([string])]
param(
[Parameter(ValueFromPipeline)]
[HostsFileEntry[]]
$InputObject
)
begin {
$header = @"
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
"@
$builder = [text.stringbuilder]::new($header)
}
process {
foreach ($entry in $InputObject) {
$null = $builder.AppendLine($entry.ToString())
}
}
end {
Write-Output $builder.ToString()
}
}
function Add-HostsFileEntry {
<#
.SYNOPSIS
Adds one or more hosts file entries to the hosts file. Requires admin.
.DESCRIPTION
Adds one or more hosts file entries to the hosts file. Note that the hosts file will be
rewritten completely as this command will read all existing entries into memory, add the new
entry, and generate a new hosts file with the old and new rows. Any custom comments or
formatting will be overwritten.
.PARAMETER InputObject
Specifies one or more HostsFileEntry objects from New-HostsFileEntry.
.PARAMETER IP
Specifies the IP address, which is the first value in each row of a hosts file.
.PARAMETER Hosts
Specifies one or more hostnames or fully qualified domain names associated with the IP address.
.PARAMETER Append
Specifies that if an existing entry is found matching the IP, the hosts values provided should
be appended to any existing values. Note that duplicate values will be discarded.
.PARAMETER Force
If an existing entry is found matching the IP address, it can only be updated when specifying
the Force parameter switch.
.EXAMPLE
Add-HostsFileEntry -IP 192.168.1.100 -Hosts mylaptop, mylaptop.domain.com -Force
Specifies that a hosts file entry like "192.168.1.100 mylaptop mylaptop.domain.com" should be
created if it doesn't exist, and if it does exist, the existing entry should be overwritten.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'FromObject')]
[Alias('HostsFileEntry')]
[HostsFileEntry]
$InputObject,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'FromValues')]
[ValidateScript({
$supportedAddressFamilies = @(
[System.Net.Sockets.AddressFamily]::InterNetwork,
[System.Net.Sockets.AddressFamily]::InterNetworkV6
)
if ($_.AddressFamily -notin $supportedAddressFamilies) {
throw "IPAddress must be IPv4 or IPv6."
}
return $true
})]
[IPAddress]
$IP,
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'FromValues')]
[ValidateCount(1, 100)]
[ValidateNotNullOrEmpty()]
[string[]]
$Hosts,
[Parameter()]
[switch]
$Append,
[Parameter()]
[switch]
$Force
)
process {
if ($PSCmdlet.ParameterSetName -eq 'FromValues') {
$InputObject = New-HostsFileEntry -IP $IP -Hosts $Hosts
}
$entries = @(Get-HostsFileEntry)
$existingEntry = $entries | Where-Object { $_.IP -eq $InputObject.IP }
if ($null -ne $existingEntry) {
if (-not $Force) {
Write-Error "There's already a hosts file entry for IP $($InputObject.IP). Use -Force to overwrite existing entries, and add -Append to add new hosts to existing host entries for the IP address."
return
}
if ($Append) {
$existingEntry.Hosts = $InputObject.Hosts + ( $existingEntry.Hosts | Where-Object { $_ -notin $InputObject.Hosts } )
}
else {
$existingEntry.Hosts = $InputObject.Hosts
}
}
else {
$entries += $InputObject
}
$newHostsContent = $entries | New-HostsFileContent
$hostsPath = Join-Path -Path ([environment]::GetFolderPath([environment+specialfolder]::System)) -ChildPath 'drivers\etc\hosts'
[io.file]::WriteAllText($hostsPath, $newHostsContent)
}
}
function Remove-HostsFileEntry {
<#
.SYNOPSIS
Removes a hosts file entry by IP address. Requires admin.
.DESCRIPTION
Removes a hosts file entry by IP address. Requires admin.
.PARAMETER IP
Specifies an IPv4 or IPv6 IP address to remove if it exists in the hosts file.
.EXAMPLE
Remove-HostsFileEntry -IP 192.168.1.100
Removes the hosts file entry for IP 192.168.1.100 or returns an error if the entry does not
exist.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'FromValues')]
[ValidateScript({
$supportedAddressFamilies = @(
[System.Net.Sockets.AddressFamily]::InterNetwork,
[System.Net.Sockets.AddressFamily]::InterNetworkV6
)
if ($_.AddressFamily -notin $supportedAddressFamilies) {
throw "IPAddress must be IPv4 or IPv6."
}
return $true
})]
[IPAddress]
$IP
)
process {
$entries = Get-HostsFileEntry
$dirty = $false
for ($i = 0; $i -lt $entries.Count; $i++) {
if ($entries[$i].IP -eq $IP) {
$entries = $entries | Where-Object { $_.IP -ne $IP}
$dirty = $true
break
}
}
if ($dirty) {
$newHostsContent = $entries | New-HostsFileContent
$hostsPath = Join-Path -Path ([environment]::GetFolderPath([environment+specialfolder]::System)) -ChildPath 'drivers\etc\hosts'
[io.file]::WriteAllText($hostsPath, $newHostsContent)
}
else {
Write-Error "No hosts file entry found for IP $($IP.ToString())"
}
}
}
$hostsEntries = Get-HostsFileEntry
if ($hostsEntries.Count -gt 0) {
Write-Host -Object "You have $($hostsEntries.Count) hosts file entries" -ForegroundColor Green
$hostsEntries | Foreach-Object { Write-Host -Object " - $_" -ForegroundColor Green }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment