Tools.ps1
function Invoke-HostsAction { [CmdletBinding()] param ( [Parameter()] [string] $Action, [Parameter()] [string[]] $Value1, [Parameter()] [string[]] $Value2, [Parameter()] [string] $Environment, [Parameter()] [pscredential] $Credentials ) switch ($Action.ToLowerInvariant()) { 'add' { Add-HostsFileEntries -IP (@($Value1) | Select-Object -First 1) -Hostnames $Value2 -Environment $Environment } 'backup' { New-HostsFileBackup -Path (@($Value1) | Select-Object -First 1) -Write } 'browse' { Open-HostsFileEntries -Values $Value1 -Protocol (@($Value2) | Select-Object -First 1) -Environment $Environment } 'clear' { Clear-HostsFile } 'disable' { Disable-HostsFileEntries -Values $Value1 -Environment $Environment } 'diff' { Compare-HostsFiles -Path (@($Value1) | Select-Object -First 1) } 'enable' { Enable-HostsFileEntries -Values $Value1 -Environment $Environment } 'export' { Export-HostsFile -Path (@($Value1) | Select-Object -First 1) -Values $Value2 -Environment $Environment } 'import' { Import-HostsFile -Path (@($Value1) | Select-Object -First 1) -Values $Value2 -Environment $Environment } 'list' { Get-HostsFile -Values $Value1 -Environment $Environment -State All } 'merge' { Merge-HostsFiles -Paths $Value1 } 'open' { Open-HostsFile } 'path' { return (Get-HostsFilePath) } 'rdp' { Invoke-HostsFileEntriesRdp -Values $Value1 -Environment $Environment -Credentials $Credentials } 'remove' { Remove-HostsFileEntries -Values $Value1 -Environment $Environment } 'restore' { Restore-HostsFile -Path (@($Value1) | Select-Object -First 1) } 'set' { Set-HostsFileEntries -IP (@($Value1) | Select-Object -First 1) -Hostnames $Value2 -Environment $Environment } 'show' { Get-Content -Path (Get-HostsFilePath) -Raw } 'test' { Test-HostsFileEntries -Values $Value1 -Ports $Value2 -Environment $Environment } } } function Open-HostsFile { [CmdletBinding()] param() $path = Get-HostsFilePath Write-Verbose "Opening $($path)" if (Test-IsUnix) { vi $path } else { notepad.exe $path } } function Compare-HostsFiles { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $Path ) # ensure the path exists if (!(Test-Path $Path)) { throw "File not found: $($Path)" } # get the hosts file $mainInfo = @{} @(Get-HostsFileEntriesByState -HostsMap (@(ConvertFrom-HostsFile)) -State Enabled) | ForEach-Object { if (!$mainInfo.ContainsKey($_.IP)) { $mainInfo[$_.IP] = @() } $mainInfo[$_.IP] += $_.Hosts } # get the other hosts file $otherInfo = @{} @(Get-HostsFileEntriesByState -HostsMap (@(ConvertFrom-HostsFile -Path $Path)) -State Enabled) | ForEach-Object { if (!$otherInfo.ContainsKey($_.IP)) { $otherInfo[$_.IP] = @() } $otherInfo[$_.IP] += $_.Hosts } # what would be added? $otherInfo.Keys | ForEach-Object { $_key = $_ $_hosts = @() if ($mainInfo.ContainsKey($_key)) { $otherInfo[$_key] | ForEach-Object { if ($mainInfo[$_key] -inotcontains $_) { $_hosts += $_ } } } else { $_hosts = @($otherInfo[$_key]) } if (($_hosts | Measure-Object).Count -gt 0) { Write-Host "+ [$($_key) - $($_hosts -join ' ')]" -ForegroundColor Green } } # what would be removed? $mainInfo.Keys | ForEach-Object { $_key = $_ $_hosts = @() if ($otherInfo.ContainsKey($_key)) { $mainInfo[$_key] | ForEach-Object { if ($otherInfo[$_key] -inotcontains $_) { $_hosts += $_ } } } else { $_hosts = @($mainInfo[$_key]) } if (($_hosts | Measure-Object).Count -gt 0) { Write-Host "- [$($_key) - $($_hosts -join ' ')]" -ForegroundColor Red } } } function Remove-HostsFileEntries { [CmdletBinding()] param ( [Parameter()] [string[]] $Values, [Parameter()] [string] $Environment ) $info = @(ConvertFrom-HostsFile) @(Set-DefaultValueAll -Values $Values) | ForEach-Object { $_value = $_ $_entries = @(Get-HostsFileEntries -HostsMap $info -IP $_value -Environment $Environment -Hostname $_value -State All -Like) if (($_entries | Measure-Object).Count -eq 0) { Write-Verbose "Already removed: [$($_value)] {$(Resolve-HostsEnvironment -Environment $Environment)}" } else { $_entries | ForEach-Object { $_entry = $_ @(Get-HostsFileEntryHosts -Entry $_entry -Value $_value) | ForEach-Object { $info = Remove-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_ -Environment $Environment } } } } # write back to hosts file Out-HostsFile -HostsMap $info } function Enable-HostsFileEntries { [CmdletBinding()] param ( [Parameter()] [string[]] $Values, [Parameter()] [string] $Environment ) $info = @(ConvertFrom-HostsFile) @(Set-DefaultValueAll -Values $Values) | ForEach-Object { $_value = $_ $_entries = @(Get-HostsFileEntries -HostsMap $info -IP $_value -Hostname $_value -Environment $Environment -State Disabled -Like) if (($_entries | Measure-Object).Count -eq 0) { Write-Verbose "Already enabled: [$($_value)] {$(Resolve-HostsEnvironment -Environment $Environment)}" } else { $_entries | ForEach-Object { $_entry = $_ @(Get-HostsFileEntryHosts -Entry $_entry -Value $_value) | ForEach-Object { $info = Add-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_ -Environment $Environment } } } } # write back to hosts file Out-HostsFile -HostsMap $info } function Disable-HostsFileEntries { [CmdletBinding()] param ( [Parameter()] [string[]] $Values, [Parameter()] [string] $Environment ) $info = @(ConvertFrom-HostsFile) @(Set-DefaultValueAll -Values $Values) | ForEach-Object { $_value = $_ $_entries = @(Get-HostsFileEntries -HostsMap $info -IP $_value -Hostname $_value -Environment $Environment -State Enabled -Like) if (($_entries | Measure-Object).Count -eq 0) { Write-Verbose "Already disabled: [$($_value)] {$(Resolve-HostsEnvironment -Environment $Environment)}" } else { $_entries | ForEach-Object { $_entry = $_ @(Get-HostsFileEntryHosts -Entry $_entry -Value $_value) | ForEach-Object { $info = Disable-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_ -Environment $Environment } } } } # write back to hosts file Out-HostsFile -HostsMap $info } function Test-HostsFileEntries { [CmdletBinding()] param ( [Parameter()] [string[]] $Values, [Parameter()] [string[]] $Ports, [Parameter()] [string] $Environment ) # do we have any ports? $hasPorts = (($Ports | Measure-Object).Count -gt 0) # grab all enabled entries in the hosts file for the value passed @(Get-HostsFile -Values $Values -Environment $Environment -State Enabled) | ForEach-Object { $_ip = $_.IP $_name = ($_.Hosts | Select-Object -First 1) # either ping the host, or test a specific port if (!$hasPorts) { Test-HostsFileEntry -IP $_ip -Hostname $_name } else { $Ports | ForEach-Object { Test-HostsFileEntry -IP $_ip -Hostname $_name -Port $_ } } } } function Open-HostsFileEntries { [CmdletBinding()] param ( [Parameter()] [string[]] $Values, [Parameter()] [string] $Protocol, [Parameter()] [string] $Environment ) # set a default HTTPS protocol if ([string]::IsNullOrWhiteSpace($Protocol)) { $Protocol = 'https' } # grab all enabled entries in the hosts file for the value passed @(Get-HostsFile -Values $Values -Environment $Environment -State Enabled) | ForEach-Object { $_name = ($_.Hosts | Select-Object -First 1) $_url = "$($Protocol)://$($_name)" Write-Verbose "Opening: $($_url)" Start-Process "$($_url)" } } function Invoke-HostsFileEntriesRdp { [CmdletBinding()] param ( [Parameter()] [string[]] $Values, [Parameter()] [string] $Environment, [Parameter()] [pscredential] $Credentials ) # assign creds if passed if ($null -ne $Credentials) { $_domain = $Credentials.GetNetworkCredential().Domain $_username = $Credentials.GetNetworkCredential().UserName $_password = $Credentials.GetNetworkCredential().Password if (![string]::IsNullOrWhiteSpace($_domain)) { $_username = "$($_domain)\$($_username)" } } # grab all enabled entries in the hosts file for the value passed @(Get-HostsFile -Values $Values -Environment $Environment -State Enabled) | ForEach-Object { $_ip = $_.IP $_name = ($_.Hosts | Select-Object -First 1) Write-Verbose "Remoting onto $($_name)" # just attempt to open a connection if no credentials if ($null -eq $Credentials) { mstsc.exe /v:$_ip } # otherwise, add credentials to cmdkey temporarily, and then connect else { try { cmdkey.exe /generic:$_ip /user:$_username /pass:$_password | Out-Null mstsc.exe /v:$_ip Start-Sleep -Seconds 4 } finally { cmdkey.exe /delete:$_ip | Out-Null } } } } function Add-HostsFileEntries { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $IP, [Parameter(Mandatory=$true)] [string[]] $Hostnames, [Parameter()] [string] $Environment ) # get the hosts file $info = @(ConvertFrom-HostsFile) # loop through each hostname and add it $Hostnames | ForEach-Object { $info = Add-HostsFileEntry -HostsMap $info -IP $IP -Hostname $_ -Environment $Environment } # write back to hosts file Out-HostsFile -HostsMap $info } function Set-HostsFileEntries { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $IP, [Parameter(Mandatory=$true)] [string[]] $Hostnames, [Parameter()] [string] $Environment ) # get the hosts file $info = @(ConvertFrom-HostsFile) # reset hosts for all the entries for the IP $entries = @(Get-HostsFileEntry -HostsMap $info -Value $IP -Type IP -State Enabled) if (($entries | Measure-Object).Count -eq 0) { $info += (Get-HostsFileEntryObject -IP $IP -Hostnames @() -Environment $Environment -Enabled $true) } else { $entries | ForEach-Object { $_.Hosts = @() $_.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $_.Environment) } } # loop through each hostname and add it $Hostnames | ForEach-Object { $info = Add-HostsFileEntry -HostsMap $info -IP $IP -Hostname $_ -Environment $Environment } # write back to hosts file Out-HostsFile -HostsMap $info } function Clear-HostsFile { [CmdletBinding()] param() # empty the hosts file Out-HostsFile -Content ([string]::Empty) -Message 'Hosts file cleared' } function Restore-HostsFile { [CmdletBinding()] param ( [Parameter()] [string] $Path ) try { $details = Get-HostsFileBackupDetails -BackupPath $Path if (!(Test-Path $details.Backup.Path)) { throw "No $($details.Backup.Name) file found" } Copy-Item -Path $details.Backup.Path -Destination $details.Hosts.Path -Force | Out-Null Write-Verbose "Restored hosts file from $($details.Backup.Name)" } catch { throw "Failed to restore hosts files from $($details.Backup.Name)" } } function Merge-HostsFiles { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string[]] $Paths ) # ensure the paths exist $Paths | ForEach-Object { if (!(Test-Path $_)) { throw "File not found: $($_)" } } # get the hosts file $info = @(ConvertFrom-HostsFile) # loop through each merge path, parsing and importing them $Paths | ForEach-Object { $_path = $_ # loop through each entry in the file @(ConvertFrom-HostsFile -Path $_path) | ForEach-Object { $_entry = $_ # and now loop through each host, removing any occurrences from base file $_entry.Hosts | ForEach-Object { $_host = $_ # if the host exists in the base file against a different IP, then remove it Get-HostsFileEntry -HostsMap $info -Value $_host -Type Hostname -State Enabled | ForEach-Object { if ($_.IP -ine $_entry.IP) { $_.Hosts = @($_.Hosts | Where-Object { $_ -ine $_host }) } } # call either add or disable on IP+host if ($_entry.Enabled) { $info = Add-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_host -Environment $_entry.Environment } else { $info = Disable-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_host -Environment $_entry.Environment } } } } # write back to hosts file Out-HostsFile -HostsMap $info } function Import-HostsFile { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $Path, [Parameter()] [string[]] $Values, [Parameter()] [string] $Environment ) # ensure the path exists if (!(Test-Path $Path)) { throw "File not found: $($Path)" } # store the main hosts file path $_HostsPathTmp = $Script:HostsFilePath # get the relevant entries $Script:HostsFilePath = $Path $info = Get-HostsFile -Values $Values -Environment $Environment -State All # write back to main hosts file $Script:HostsFilePath = $_HostsPathTmp Out-HostsFile -HostsMap $info -Message "Hosts file imported from: $($Path)" } function Export-HostsFile { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $Path, [Parameter()] [string[]] $Values, [Parameter()] [string] $Environment ) # get the relevant entries $info = Get-HostsFile -Values $Values -Environment $Environment -State All # write to export location $Script:HostsFilePath = $Path Out-HostsFile -HostsMap $info -Message "Hosts file exported to: $($Path)" } function Get-HostsFile { [CmdletBinding()] param ( [Parameter()] [string[]] $Values, [Parameter()] [string] $Environment, [Parameter()] [ValidateSet('All', 'Disabled', 'Enabled')] [string] $State ) $info = @(ConvertFrom-HostsFile) $results = @() # filter by environment and state $info = @(Get-HostsFileEntriesByEnvironment -HostsMap $info -Environment $Environment) $info = @(Get-HostsFileEntriesByState -HostsMap $info -State $State) # filter by values if (($Values | Measure-Object).Count -eq 0) { $results = $info } else { $Values | ForEach-Object { @(Get-HostsFileEntries -HostsMap $info -IP $_ -Hostname $_ -State $State -Like) | ForEach-Object { $_tmp = $_ if (($results | Where-Object { $_.Hash -eq $_tmp.Hash } | Measure-Object).Count -eq 0) { $results += $_tmp } } } } return ($results | Select-Object IP, Hosts, Environment, Enabled) } function New-HostsFileBackup { [CmdletBinding()] param ( [Parameter()] [string] $Path, [switch] $Write ) $details = Get-HostsFileBackupDetails -BackupPath $Path # if a backup exists, back that up temporarily if (Test-Path $details.Backup.Path) { Copy-Item -Path $details.Backup.Path -Destination $details.Backup.Temp -Force | Out-Null } # backup the hosts file if (Test-Path $details.Hosts.Path) { Copy-Item -Path $details.Hosts.Path -Destination $details.Backup.Path -Force | Out-Null } # remove tmp backup if (Test-Path $details.Backup.Temp) { Remove-Item -Path $details.Backup.Temp -Force | Out-Null } if ($Write) { Write-Verbose "Hosts file backed up to $($details.Backup.Name)" } } function Get-HostsFileBackupDetails { [CmdletBinding()] param ( [Parameter()] [string] $BackupPath ) $path = Get-HostsFilePath if ([string]::IsNullOrWhiteSpace($BackupPath)) { $basepath = Split-Path -Parent -Path $path if ([string]::IsNullOrWhiteSpace($basepath)) { $basepath = '.' } $backup = Join-Path $basepath "$(Split-Path -Leaf -Path $path).bak" } else { $backup = $BackupPath } return @{ Hosts = @{ Path = $path Name = (Split-Path -Leaf -Path $path) } Backup = @{ Path = $backup Name = (Split-Path -Leaf -Path $backup) Temp = "$($backup).tmp" } } } function Set-DefaultValueAll { [CmdletBinding()] param ( [Parameter()] [string[]] $Values ) if (($Values | Measure-Object).Count -eq 0) { return @('*') } return @($Values) } function Test-AdminUser { [CmdletBinding()] param() # check the current platform, if it's unix then return true if (Test-IsUnix) { return } try { $principal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent()) if ($null -eq $principal) { $admin = $false } else { $admin = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } } catch [exception] { Write-Verbose 'Error checking user administrator priviledges' Write-Verbose $_.Exception.Message $admin = $false } if (!$admin) { throw 'Must be running with administrator priviledges to use the hosts command' } } function Get-PSVersionTable { [CmdletBinding()] param() return $PSVersionTable } function Test-IsUnix { [CmdletBinding()] param() return (Get-PSVersionTable).Platform -ieq 'unix' } function Get-HostsFilePath { [CmdletBinding()] param() # custom path if (![string]::IsNullOrWhiteSpace($Script:HostsFilePath)) { return $Script:HostsFilePath } # unix if (Test-IsUnix) { return '/etc/hosts' } # windows is default return "$($env:windir)\System32\drivers\etc\hosts" } function Get-HostsIPRegex { [CmdletBinding()] param() return "(?<ip>(\[[a-z0-9\:]+\]|((\d+\.){3}\d+)|\:\:\d+))" } function Get-HostsNameRegex { [CmdletBinding()] param() return "(?<hosts>((([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\s*)+)" } function ConvertFrom-HostsFile { [CmdletBinding()] param ( [Parameter()] [string] $Path ) if ([string]::IsNullOrWhiteSpace($Path)) { $Path = Get-HostsFilePath } $map = @() $currentEnv = [string]::Empty # if the path doesn't exist, just return if (!(Test-Path $Path)) { return $map } # parse the file (Get-Content $Path) | ForEach-Object { # check if it's an environment start tag if ($_ -imatch "^\s*\#\s*\<--\s*(?<name>[a-z0-9\-]+)\s*$") { $currentEnv = $Matches['name'] } # check if it's an environment end tag elseif ($_ -imatch "^\s*\#\s*--\>\s*$") { $currentEnv = [string]::Empty } # check if it's a host entry elseif ($_ -imatch "^\s*(?<enabled>[\#]{0,1})\s*$(Get-HostsIPRegex)\s+$(Get-HostsNameRegex)\s*$") { $map += (Get-HostsFileEntryObject ` -IP ($Matches['ip'].Trim()) ` -Hostnames @($Matches['hosts'].Trim() -isplit '\s+') ` -Environment $currentEnv ` -Enabled ([string]::IsNullOrWhiteSpace($Matches['enabled']))) } } $map = Update-HostsFileObject -HostsMap $map return $map } function Get-HostsFileEntryObject { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $IP, [Parameter()] [string[]] $Hostnames, [Parameter()] [string] $Environment, [Parameter()] [bool] $Enabled ) return (New-Object -TypeName psobject | Add-Member -MemberType NoteProperty -Name IP -Value $IP -PassThru | Add-Member -MemberType NoteProperty -Name Hosts $Hostnames -PassThru | Add-Member -MemberType NoteProperty -Name Environment -Value (Resolve-HostsEnvironment -Environment $Environment) -PassThru | Add-Member -MemberType NoteProperty -Name Enabled -Value $Enabled -PassThru | Add-Member -MemberType NoteProperty -Name Hash -Value ([string]::Empty) -PassThru) } function Resolve-HostsEnvironment { [CmdletBinding()] param ( [Parameter()] [string] $Environment, [Parameter()] [string] $Current ) # default empty env to None if ([string]::IsNullOrWhiteSpace($Environment)) { $Environment = 'None' } # if no current env passed, just return the env if ([string]::IsNullOrWhiteSpace($Current)) { return $Environment } # if current env is not None, return current if env is also None if ($Current -ine 'None' -and $Environment -ieq 'None') { return $Current } return $Environment } function Update-HostsFileObject { [CmdletBinding()] param ( [Parameter()] $HostsMap ) $crypto = [System.Security.Cryptography.SHA256]::Create() $HostsMap | ForEach-Object { $str = "$($_.IP)|$($_.Hosts -join '|')|$($_.Environment)" $_.Hash = [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($str))) } return $HostsMap } function ConvertTo-HostsFile { [CmdletBinding()] param ( [Parameter()] $HostsMap ) $str = [string]::Empty $nl = [Environment]::NewLine # if there are no entries, then it's empty if (($HostsMap | Measure-Object).Count -eq 0) { return $str } # get each of the environments $HostsMap | Select-Object -ExpandProperty Environment | Group-Object | ForEach-Object { $_env = $_.Name $str += "#<-- $($_env)$($nl)" # loop through each one, forming each entry line $HostsMap | Where-Object { $_.Environment -ieq $_env } | ForEach-Object { if ($null -ne $_ -and ![string]::IsNullOrWhiteSpace($_.IP) -and ($_.Hosts | Measure-Object).Count -gt 0) { if (!$_.Enabled) { $str += '# ' } $str += "$($_.IP)`t$($_.Hosts -join ' ')$($nl)" } } $str += "#-->$($nl)$($nl)" } return $str } function Get-HostsFileEntriesByEnvironment { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter()] [string] $Environment ) if ([string]::IsNullOrWhiteSpace($Environment)) { return $HostsMap } return @($HostsMap | Where-Object { $_.Environment -ieq $Environment }) } function Get-HostsFileEntry { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter()] [string] $Value, [Parameter()] [ValidateSet('IP', 'Hostname')] [string] $Type, [Parameter()] [ValidateSet('All', 'Disabled', 'Enabled')] [string] $State, [Parameter()] [string] $Environment ) $HostsMap = @(Get-HostsFileEntriesByEnvironment -HostsMap $HostsMap -Environment $Environment) switch ($Type.ToLowerInvariant()) { 'IP' { $HostsMap = @($HostsMap | Where-Object { $_.IP -ieq $Value }) } 'Hostname' { $HostsMap = @($HostsMap | Where-Object { $_.Hosts -icontains $Value }) } } return @(Get-HostsFileEntriesByState -HostsMap $HostsMap -State $State) } function Get-HostsFileEntryHosts { [CmdletBinding()] param ( [Parameter()] [object] $Entry, [Parameter()] [string] $Value ) if ($Entry.IP -ilike $Value) { return @($Entry.Hosts) } $hosts = @() $Entry.Hosts | Where-Object { if ($_ -ilike $Value) { $hosts += $_ } } return @($hosts) } function Get-HostsFileEntriesByState { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter()] [ValidateSet('All', 'Disabled', 'Enabled')] [string] $State ) switch ($State.ToLowerInvariant()) { 'disabled' { $HostsMap = @($HostsMap | Where-Object { !$_.Enabled }) } 'enabled' { $HostsMap = @($HostsMap | Where-Object { $_.Enabled }) } } return @($HostsMap) } function Test-HostnameAgainstDifferentIP { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter(Mandatory=$true)] [string] $IP, [Parameter(Mandatory=$true)] [string] $Hostname, [switch] $Throw ) $h = (Get-HostsFileEntry -HostsMap $HostsMap -Value $Hostname -Type Hostname -State Enabled | Select-Object -First 1) $bound = ($null -ne $h -and $h.IP -ine $IP) if ($Throw -and $bound) { throw "The hostname [$($Hostname)] is bound against a different IP address: [$($h.IP)]" } return $bound } function Get-IPHostString { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $IP, [Parameter(Mandatory=$true)] [string] $Hostname, [Parameter()] [string] $Environment ) return "[$($IP) - $($Hostname)] {$(Resolve-HostsEnvironment -Environment $Environment)}" } function Remove-HostsFileEntry { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter(Mandatory=$true)] [string] $IP, [Parameter(Mandatory=$true)] [string] $Hostname, [Parameter()] [string] $Environment ) # get entries $entries = @(Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State All) # skip if already removed if (($entries | Measure-Object).Count -eq 0) { Write-Verbose "Already removed $(Get-IPHostString $IP $Hostname $Environment)" return $HostsMap } # remove hostname from that entries $entries | ForEach-Object { $_.Hosts = @($_.Hosts | Where-Object { $_ -ine $Hostname }) $_.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $_.Environment) } Write-Verbose "Removing $(Get-IPHostString $IP $Hostname $entries[0].Environment)" return $HostsMap } function Disable-HostsFileEntry { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter(Mandatory=$true)] [string] $IP, [Parameter(Mandatory=$true)] [string] $Hostname, [Parameter()] [string] $Environment ) # see if there's an enabled entry, and remove hostname from that entry Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State Enabled | ForEach-Object { $_.Hosts = @($_.Hosts | Where-Object { $_ -ine $Hostname }) } # skip if already disabled $entries = @(Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State Disabled) if (($entries | Measure-Object).Count -gt 0) { Write-Verbose "Already disabled $(Get-IPHostString $IP $Hostname $Environment)" $entries | ForEach-Object { $_.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $_.Environment) } return $HostsMap } # disable IP+Hostname $entry = (Get-HostsFileEntry -HostsMap $HostsMap -Value $IP -Type IP -State Disabled | Select-Object -First 1) if ($null -eq $entry) { $entry = (Get-HostsFileEntryObject -IP $IP -Hostnames @($Hostname) -Environment $Environment -Enabled $false) $HostsMap += $entry } else { $entry.Hosts = @($entry.Hosts) + $Hostname $entry.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $entry.Environment) } Write-Verbose "Disabling $(Get-IPHostString $IP $Hostname $entry.Environment)" return $HostsMap } function Add-HostsFileEntry { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter(Mandatory=$true)] [string] $IP, [Parameter(Mandatory=$true)] [string] $Hostname, [Parameter()] [string] $Environment ) # is the host being added, or enabled from previously being diabled? $enabling = $false # fail if the hostname or IP address are invalid if ($IP -inotmatch "^$(Get-HostsIPRegex)$") { throw "The IP address [$($IP)] is invalid" } if ($Hostname -inotmatch "^$(Get-HostsNameRegex)$") { throw "The hostname [$($Hostname)] is invalid" } # fail if the hostname is found against a different IP Test-HostnameAgainstDifferentIP -HostsMap $HostsMap -IP $IP -Hostname $Hostname -Throw | Out-Null # see if there's a disabled entry, and remove hostname from that entry Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State Disabled | ForEach-Object { $enabling = $true $_.Hosts = @($_.Hosts | Where-Object { $_ -ine $Hostname }) } # skip if already added/enabled $entries = @(Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State Enabled) if (($entries | Measure-Object).Count -gt 0) { Write-Verbose "Already $(if ($enabling) { 'enabled' } else { 'added' }) [$($IP) - $($Hostname)]" $entries | ForEach-Object { $_.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $_.Environment) } return $HostsMap } # add IP+Hostname $entry = (Get-HostsFileEntry -HostsMap $HostsMap -Value $IP -Type IP -State Enabled | Select-Object -First 1) if ($null -eq $entry) { $entry = (Get-HostsFileEntryObject -IP $IP -Hostnames @($Hostname) -Environment $Environment -Enabled $true) $HostsMap += $entry } else { $entry.Hosts = @($entry.Hosts) + $Hostname $entry.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $entry.Environment) } Write-Verbose "$(if ($enabling) { 'Enabling' } else { 'Adding' }) $(Get-IPHostString $IP $Hostname $entry.Environment)" return $HostsMap } function Get-HostsFileEntries { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter()] [string] $IP, [Parameter()] [string] $Hostname, [Parameter()] [string] $Environment, [Parameter()] [ValidateSet('All', 'Disabled', 'Enabled')] [string] $State, [switch] $Like ) $HostsMap = @(Get-HostsFileEntriesByEnvironment -HostsMap $HostsMap -Environment $Environment) $HostsMap = @($HostsMap | Where-Object { if ($Like) { $_.IP -ilike $IP -or ($_.Hosts | Where-Object { $_ -ilike $Hostname } | Measure-Object).Count -ne 0 } else { $_.IP -ilike $IP -and ($_.Hosts | Where-Object { $_ -ilike $Hostname } | Measure-Object).Count -ne 0 } }) return @(Get-HostsFileEntriesByState -HostsMap $HostsMap -State $State) } function Out-HostsFile { [CmdletBinding()] param ( [Parameter()] [object[]] $HostsMap, [Parameter()] [string] $Content, [Parameter()] [string] $Path, [Parameter()] [string] $Message ) # create backup of current New-HostsFileBackup # set an appropriate output message if ([string]::IsNullOrWhiteSpace($Message)) { $Message = 'Hosts file updated' } # write out to hosts file try { $hosts_path = Get-HostsFilePath New-HostsFilePath -Path (Split-Path -Parent -Path $hosts_path) if ([string]::IsNullOrWhiteSpace($Path)) { if (($HostsMap | Measure-Object).Count -gt 0) { $Content = ConvertTo-HostsFile -HostsMap $HostsMap } $Content | Out-File -FilePath $hosts_path -Encoding ascii -Force -ErrorAction Stop | Out-Null } else { Copy-Item -Path $Path -Destination $hosts_path -Force -ErrorAction Stop | Out-Null } Write-Verbose "$($Message)" } catch { Restore-HostsFile throw $_.Exception } } function New-HostsFilePath { [CmdletBinding()] param ( [Parameter()] [string] $Path ) if (!(Test-Path $Path)) { New-Item -Path $Path -ItemType Directory -Force | Out-Null } } function Test-HostsFileEntry { [CmdletBinding()] param ( [Parameter()] [string] $IP, [Parameter()] [string] $Hostname, [Parameter()] [string] $Port ) # either ping the host, or test a specific port if ([string]::IsNullOrWhiteSpace($Port)) { Write-Host "Testing $($Hostname)>" -NoNewline $result = Test-NetConnection -ComputerName $IP -WarningAction SilentlyContinue -ErrorAction SilentlyContinue } else { Write-Host "Testing $($Hostname):$($Port)>" -NoNewline $result = Test-NetConnection -ComputerName $IP -Port $Port -WarningAction SilentlyContinue -ErrorAction SilentlyContinue } # was the test successful or a failure? if ($null -eq $result -or (!$result.PingSucceeded -and !$result.TcpTestSucceeded)) { Write-Host "`tFailed" -ForegroundColor Red } else { Write-Host "`tSuccess" -ForegroundColor Green } } |