├── .gitignore ├── appveyor.yml ├── packers └── choco │ ├── VERIFICATION.txt │ ├── tools │ ├── ChocolateyUninstall.ps1 │ └── ChocolateyInstall.ps1 │ └── poshhosts.nuspec ├── .travis.yml ├── tests └── Tools.Tests.ps1 ├── LICENSE.txt ├── hosts.build.ps1 ├── src ├── PoshHosts.psd1 ├── PoshHosts.psm1 └── Tools.ps1 └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # MISC 2 | *.bak 3 | *.hosts 4 | 5 | # NUNIT 6 | *.VisualState.xml 7 | TestResult.xml 8 | TestResults.xml -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.1.{build} 2 | image: Visual Studio 2017 3 | 4 | branches: 5 | except: 6 | - gh-pages 7 | 8 | skip_tags: true 9 | 10 | configuration: Release 11 | 12 | install: 13 | - ps: Install-Module -Name InvokeBuild -RequiredVersion '5.4.1' -Force 14 | 15 | build: off 16 | 17 | test_script: 18 | - ps: Invoke-Build Test -------------------------------------------------------------------------------- /packers/choco/VERIFICATION.txt: -------------------------------------------------------------------------------- 1 | VERIFICATION 2 | Verification is intended to assist the Chocolatey moderators and community 3 | in verifying that this package's contents are trustworthy. 4 | 5 | This embedded PowerShell module is packaged and distributed by the author. 6 | 7 | The contents of which can be found on the releases pages at . 8 | 9 | To verify contents, either: 10 | 1. Compare the Checksum on the release notes against the Chocolatey source. 11 | 2. Download the zip from the release, run 'checksum -t sha256' on it, and compare. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | 3 | branches: 4 | except: 5 | - gh-pages 6 | 7 | matrix: 8 | include: 9 | - os: linux 10 | dist: trusty 11 | sudo: true 12 | addons: 13 | apt: 14 | sources: 15 | - sourceline: "deb [arch=amd64] https://packages.microsoft.com/ubuntu/14.04/prod trusty main" 16 | key_url: "https://packages.microsoft.com/keys/microsoft.asc" 17 | packages: 18 | - powershell 19 | fast_finish: true 20 | 21 | install: 22 | - pwsh -c "Install-Module -Name InvokeBuild -RequiredVersion '5.4.1' -Scope CurrentUser -Force" 23 | 24 | script: 25 | - pwsh -c "Invoke-Build Test" -------------------------------------------------------------------------------- /tests/Tools.Tests.ps1: -------------------------------------------------------------------------------- 1 | $path = $MyInvocation.MyCommand.Path 2 | $src = (Split-Path -Parent -Path $path) -ireplace '[\\/]tests', '/src' 3 | Get-ChildItem "$($src)/*.ps1" | Resolve-Path | ForEach-Object { . $_ } 4 | 5 | Describe 'Get-PSVersionTable' { 6 | It 'Returns valid hashtable' { 7 | $table = Get-PSVersionTable 8 | $table | Should Not Be $null 9 | $table | Should BeOfType System.Collections.Hashtable 10 | } 11 | } 12 | 13 | Describe 'Test-IsUnix' { 14 | It 'Returns false for non-unix' { 15 | Mock Get-PSVersionTable { return @{ 'Platform' = 'Windows' } } 16 | Test-IsUnix | Should Be $false 17 | Assert-MockCalled Get-PSVersionTable -Times 1 18 | } 19 | 20 | It 'Returns true for unix' { 21 | Mock Get-PSVersionTable { return @{ 'Platform' = 'Unix' } } 22 | Test-IsUnix | Should Be $true 23 | Assert-MockCalled Get-PSVersionTable -Times 1 24 | } 25 | } -------------------------------------------------------------------------------- /packers/choco/tools/ChocolateyUninstall.ps1: -------------------------------------------------------------------------------- 1 | function Remove-PoshHostsModule($path) 2 | { 3 | $path = Join-Path $path 'PoshHosts' 4 | if (Test-Path $path) 5 | { 6 | Write-Host "Deleting PoshHosts module directory: $($path)" 7 | Remove-Item -Path $path -Recurse -Force | Out-Null 8 | if (!$?) { 9 | throw "Failed to delete: $path" 10 | } 11 | } 12 | } 13 | 14 | 15 | # Determine which Program Files path to use 16 | $progFiles = [string]$env:ProgramFiles 17 | 18 | # Remove PS Module 19 | # Set the module path 20 | $modulePath = Join-Path $progFiles (Join-Path 'WindowsPowerShell' 'Modules') 21 | 22 | # Delete PoshHosts module 23 | Remove-PoshHostsModule $modulePath 24 | 25 | 26 | # Remove PS-Core Module 27 | $def = (Get-Command pwsh -ErrorAction SilentlyContinue).Definition 28 | 29 | if (![string]::IsNullOrWhiteSpace($def)) 30 | { 31 | # Set the module path 32 | $modulePath = Join-Path $progFiles (Join-Path 'PowerShell' 'Modules') 33 | 34 | # Delete PoshHosts module 35 | Remove-PoshHostsModule $modulePath 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [2018-2019] [Matthew Kelly (Badgerati)] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hosts.build.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | # Dependencies 3 | #> 4 | 5 | # Synopsis: Install dependencies for running tests 6 | task TestDeps { 7 | if (((Get-Module -ListAvailable Pester) | Where-Object { $_.Version -ieq '4.4.2' }) -eq $null) { 8 | Write-Host 'Installing Pester' 9 | Install-Module -Name Pester -Scope CurrentUser -RequiredVersion '4.4.2' -Force -SkipPublisherCheck 10 | } 11 | } 12 | 13 | 14 | <# 15 | # Testing 16 | #> 17 | 18 | # Synopsis: Run the tests 19 | task Test TestDeps, { 20 | $p = (Get-Command Invoke-Pester) 21 | if ($null -eq $p -or $p.Version -ine '4.4.2') { 22 | Import-Module Pester -Force -RequiredVersion '4.4.2' 23 | } 24 | 25 | $Script:TestResultFile = "$($pwd)/TestResults.xml" 26 | $Script:TestStatus = Invoke-Pester './tests' -OutputFormat NUnitXml -OutputFile $TestResultFile -PassThru 27 | }, PushAppVeyorTests, CheckFailedTests 28 | 29 | # Synopsis: Check if any of the tests failed 30 | task CheckFailedTests { 31 | if ($TestStatus.FailedCount -gt 0) { 32 | throw "$($TestStatus.FailedCount) tests failed" 33 | } 34 | } 35 | 36 | # Synopsis: If AppVeyor, push result artifacts 37 | task PushAppVeyorTests -If (![string]::IsNullOrWhiteSpace($env:APPVEYOR_JOB_ID)) { 38 | $url = "https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)" 39 | (New-Object 'System.Net.WebClient').UploadFile($url, $TestResultFile) 40 | Push-AppveyorArtifact $TestResultFile 41 | } -------------------------------------------------------------------------------- /src/PoshHosts.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'PoshHosts' 3 | # 4 | # Generated by: Matthew Kelly (Badgerati) 5 | # 6 | # Generated on: 18/11/2018 7 | # 8 | 9 | @{ 10 | # Script module or binary module file associated with this manifest. 11 | RootModule = 'PoshHosts.psm1' 12 | 13 | # Version number of this module. 14 | ModuleVersion = '0.2.2' 15 | 16 | # ID used to uniquely identify this module 17 | GUID = 'f3aa217d-ec3d-306b-95d3-130dab0ac6af' 18 | 19 | # Author of this module 20 | Author = 'Matthew Kelly (Badgerati)' 21 | 22 | # Copyright statement for this module 23 | Copyright = 'Copyright (c) 2018-2019 Matthew Kelly (Badgerati), licensed under the MIT License.' 24 | 25 | # Description of the functionality provided by this module 26 | Description = 'PoshHosts is a Cross-Platform module that allows you to control the hosts file from the Command Line' 27 | 28 | # Minimum version of the Windows PowerShell engine required by this module 29 | PowerShellVersion = '3.0' 30 | 31 | # Functions to export from this Module 32 | FunctionsToExport = @( 33 | 'Hosts' 34 | ) 35 | 36 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 37 | PrivateData = @{ 38 | PSData = @{ 39 | 40 | # Tags applied to this module. These help with module discovery in online galleries. 41 | Tags = @('powershell', 'hosts', 'powershell-core', 'windows', 'unix', 'linux', 'PSEdition_Core', 'cross-platform', 'environments', 'profiles') 42 | 43 | # A URL to the license for this module. 44 | LicenseUri = 'https://raw.githubusercontent.com/Badgerati/PoshHosts/master/LICENSE.txt' 45 | 46 | # A URL to the main website for this project. 47 | ProjectUri = 'https://github.com/Badgerati/PoshHosts' 48 | 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /packers/choco/tools/ChocolateyInstall.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | 3 | 4 | # create the module directory, and copy files over 5 | function Install-PoshHostsModule($path, $version) 6 | { 7 | # Create PoshHosts module 8 | $path = Join-Path $path 'PoshHosts' 9 | if (![string]::IsNullOrWhiteSpace($version)) { 10 | $path = Join-Path $path $version 11 | } 12 | 13 | if (!(Test-Path $path)) 14 | { 15 | Write-Host "Creating PoshHosts module directory: $($path)" 16 | New-Item -ItemType Directory -Path $path -Force | Out-Null 17 | if (!$?) { 18 | throw "Failed to create: $path" 19 | } 20 | } 21 | 22 | # Copy contents to module 23 | Write-Host 'Copying PoshHosts to module path' 24 | 25 | try 26 | { 27 | Push-Location (Join-Path $env:ChocolateyPackageFolder 'src') 28 | Copy-Item -Path ./* -Destination $path -Force | Out-Null 29 | } 30 | finally { 31 | Pop-Location 32 | } 33 | } 34 | 35 | 36 | 37 | # Determine which Program Files path to use 38 | $progFiles = [string]$env:ProgramFiles 39 | 40 | # Install PS Module 41 | # Set the module path 42 | $modulePath = Join-Path $progFiles (Join-Path 'WindowsPowerShell' 'Modules') 43 | 44 | # Check to see if Modules path is in PSModulePaths 45 | $psModules = $env:PSModulePath 46 | if (!$psModules.Contains($modulePath)) 47 | { 48 | Write-Host 'Adding module path to PSModulePaths' 49 | $psModules += ";$modulePath" 50 | Install-ChocolateyEnvironmentVariable -VariableName 'PSModulePath' -VariableValue $psModules -VariableType Machine 51 | $env:PSModulePath = $psModules 52 | } 53 | 54 | # create the module 55 | if ($PSVersionTable.PSVersion.Major -ge 5) { 56 | Install-PoshHostsModule $modulePath '$version$' 57 | } 58 | else { 59 | Install-PoshHostsModule $modulePath 60 | } 61 | 62 | 63 | # Install PS-Core Module 64 | $def = (Get-Command pwsh -ErrorAction SilentlyContinue).Definition 65 | 66 | if (![string]::IsNullOrWhiteSpace($def)) 67 | { 68 | # Set the module path 69 | $modulePath = Join-Path $progFiles (Join-Path 'PowerShell' 'Modules') 70 | 71 | # create the module 72 | Install-PoshHostsModule $modulePath '$version$' 73 | } 74 | -------------------------------------------------------------------------------- /src/PoshHosts.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The hosts commands allows you to control the hosts file 4 | 5 | .DESCRIPTION 6 | The hosts commands allows you to control the hosts file, by adding/removing entries; as well enabling/disabling them. 7 | 8 | Hosts also supports profiles, so you can have a developer hosts file in your repo and import/merge it for developers. 9 | 10 | You can also test the entries by pinging them, either using the normal ping or by passing specific ports. 11 | 12 | .EXAMPLE 13 | hosts add 127.0.0.3 dev.test.local 14 | 15 | .EXAMPLE 16 | hosts export ./local.hosts 17 | 18 | .EXAMPLE 19 | hosts test *.local 80, 443 20 | 21 | .EXAMPLE 22 | hosts list -e dev 23 | #> 24 | function Hosts 25 | { 26 | [CmdletBinding()] 27 | param ( 28 | [Parameter(Position=0, Mandatory=$true)] 29 | [ValidateSet('add', 'backup', 'browse', 'clear', 'diff', 'disable', 'enable', 'export', 'import', 30 | 'list', 'merge', 'open', 'path', 'rdp', 'remove', 'restore', 'set', 'show', 'test')] 31 | [Alias('a')] 32 | [string] 33 | $Action, 34 | 35 | [Parameter(Position=1)] 36 | [Alias('v1')] 37 | [string[]] 38 | $Value1, 39 | 40 | [Parameter(Position=2)] 41 | [Alias('v2')] 42 | [string[]] 43 | $Value2, 44 | 45 | [Parameter()] 46 | [Alias('p')] 47 | [string] 48 | $HostsPath, 49 | 50 | [Parameter()] 51 | [Alias('e')] 52 | [string] 53 | $Environment, 54 | 55 | [Parameter()] 56 | [Alias('c')] 57 | [pscredential] 58 | $Credentials 59 | ) 60 | 61 | if (@('diff', 'list', 'path', 'rdp', 'show', 'test') -inotcontains $Action) { 62 | Test-AdminUser 63 | } 64 | 65 | try { 66 | $Script:HostsFilePath = $HostsPath 67 | Invoke-HostsAction -Action $Action -Value1 $Value1 -Value2 $Value2 -Environment $Environment -Credentials $Credentials 68 | } 69 | finally { 70 | $Script:HostsFilePath = [string]::Empty 71 | } 72 | } 73 | 74 | # load other functions 75 | $root = Split-Path -Parent -Path $MyInvocation.MyCommand.Path 76 | Get-ChildItem "$($root)\Tools.ps1" | Resolve-Path | ForEach-Object { . $_ } 77 | 78 | # Export the Hosts function only 79 | Export-ModuleMember -Function Hosts -------------------------------------------------------------------------------- /packers/choco/poshhosts.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | poshhosts 6 | PoshHosts 7 | $version$ 8 | Badgerati 9 | Badgerati 10 | PoshHosts is a Cross-Platform module that allows you to control the hosts file from the Command Line 11 | 12 | PoshHosts is a Cross-Platform module that allows you to control the hosts file from the Command Line. 13 | 14 | Installation will install the module for PowerShell, and PowerShell Core (if it is installed). 15 | 16 | ### Features 17 | 18 | * Control the hosts file from the command line 19 | * Support for host profiles, useful for local environments 20 | * Test entries in a hosts file by pinging them - even with specific ports 21 | * Display a diff between two host files 22 | * Support for environment sections in host files 23 | * Support for RDPing onto servers via host entries 24 | * Ability to open the hosts file from CLI (notepad on Windows, Vi on Unix) 25 | 26 | 27 | https://github.com/Badgerati/PoshHosts 28 | https://github.com/Badgerati/PoshHosts/tree/master/packers 29 | https://github.com/Badgerati/PoshHosts 30 | https://github.com/Badgerati/PoshHosts/issues 31 | powershell hosts powershell-core windows unix linux cross-platform environments profiles 32 | Copyright 2018 33 | https://github.com/Badgerati/PoshHosts/blob/master/LICENSE.txt 34 | false 35 | https://github.com/Badgerati/PoshHosts/releases 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hosts 2 | 3 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Badgerati/PoshHosts/master/LICENSE.txt) 4 | [![AppVeyor](https://img.shields.io/appveyor/ci/Badgerati/PoshHosts/master.svg?label=AppVeyor)](https://ci.appveyor.com/project/Badgerati/poshhosts/branch/master) 5 | [![Travis CI](https://img.shields.io/travis/Badgerati/PoshHosts/master.svg?label=Travis%20CI)](https://travis-ci.org/Badgerati/PoshHosts) 6 | 7 | [![PowerShell](https://img.shields.io/powershellgallery/dt/poshhosts.svg?label=PowerShell&colorB=085298)](https://www.powershellgallery.com/packages/PoshHosts) 8 | 9 | Module that introduces a new `hosts` command on your terminal, that allows you to control the hosts file from the command line - on Windows, Linux and MacOS. 10 | 11 | The `hosts` commands allows you to add/remove entries; as well enable/disable them. It also supports profiles, so you can have a developer hosts file in your repo and import/merge it for developers. 12 | 13 | The `hosts` command also lets you test entries by pinging them, either using the normal ping or by passing specific ports. 14 | 15 | ## Features 16 | 17 | * Control the hosts file from the command line 18 | * Support for host profiles, useful for local environments 19 | * Test entries in a hosts file by pinging them - even with specific ports 20 | * Display a diff between two host files 21 | * Support for environment sections in host files 22 | * Support for RDPing onto servers via host entries 23 | * Ability to open the hosts file from CLI (notepad on Windows, Vi on Unix) 24 | 25 | ## Install 26 | 27 | You can install PoshHosts from the PowerShell Gallery: 28 | 29 | ```powershell 30 | # powershell gallery 31 | Install-Module -Name PoshHosts 32 | ``` 33 | 34 | ## Commands 35 | 36 | Format: `hosts [] [] [-p ] [-e ] [-c ]` 37 | 38 | * `-v1` and `-v2` supply the main data to commands: such as IP addresses, host names, paths and ports. 39 | * `-p` allows you to override the default main hosts file path with a custom one. 40 | * `-e` allows you to control specific environments, such as add an entry or remove all entries for an environment. 41 | * `-c` allows you to specify credentials - is only used for `rdp` currently 42 | 43 | > Actions that alter data in the hosts file will always create a `.bak` first; so if the command fails, then the hosts are restored from this `.bak`. If you mess-up and need to restore, the `.bak` is always left in place, calling `hosts restore` will solve your problems! 44 | 45 | ```powershell 46 | # adds new entries 47 | hosts add 127.0.0.2 dev.test.local 48 | hosts add 192.168.0.1 build.office, build 49 | hosts add 10.10.1.3 site.test -e staging 50 | 51 | # sets entries, removing any previous settings 52 | hosts set 127.0.0.3 qa.test.local 53 | hosts set 10.10.1.2 private.software.live, private.website.live 54 | 55 | # removes entries 56 | hosts remove 127.0.0.2 57 | hosts remove *.office 58 | hosts remove 192.168.*, *.local 59 | hosts remove 192.* -e office 60 | hosts remove -e dev 61 | 62 | # disables an entry (by commenting it out) 63 | hosts disable dev.test.local 64 | hosts disable 192.168.*, *.local 65 | hosts disable *.local -e dev 66 | 67 | # enables an entry (by uncommenting it out) 68 | hosts enable dev.test.local 69 | hosts enable 192.168.*, *.local 70 | hosts enable *.local -e dev 71 | 72 | # completely clears all entries 73 | hosts clear 74 | 75 | # displays the path to the hosts file 76 | hosts path 77 | 78 | # lists entries 79 | hosts list 80 | hosts list *.office 81 | hosts list 192.168.*, *.local 82 | hosts list -e live 83 | 84 | # tests entries by pinging - can also use specific ports 85 | hosts test 86 | hosts test * 443 87 | hosts test dev.test.local 80, 443 88 | hosts test * -e dev 89 | hosts test * 80, 443 -e live 90 | 91 | # rdp onto entries 92 | hosts rdp 10.21.* 93 | hosts rdp -e test 94 | hosts rdp qa.test -c (Get-Credential) 95 | 96 | # open entries in default browser (default protocol is https) 97 | hosts browse *.local 98 | hosts browse qa.test http 99 | hosts browse -e live 100 | 101 | # creates a backup of the hosts file - can also specify custom file path 102 | hosts backup 103 | hosts backup ./dev.hosts.bak 104 | 105 | # restores the hosts file from the backup - can also specify custom file path 106 | hosts restore 107 | hosts restore ./dev.hosts.bak 108 | 109 | # exports the hosts file to the specified path - useful for profiles 110 | hosts export ./dev.profile.hosts 111 | hosts export ./profile.hosts *.local 112 | hosts export ./qa.profile.hosts -e qa 113 | 114 | # imports a hosts profile, replacing the main hosts file 115 | hosts import ./dev.profile.hosts 116 | hosts import ./profile.hosts *.local 117 | hosts import ./qa.profile.hosts -e qa 118 | 119 | # merges the hosts file with host profiles (profile has precendence) 120 | hosts merge ./dev.profile.hosts 121 | hosts merge ./dev.profile.hosts, ./qa.profile.hosts 122 | 123 | # displays the diff of the hosts file to a hosts profile 124 | hosts diff ./dev.profile.hosts 125 | 126 | # displays the contents of the hosts file on the command line 127 | hosts show 128 | 129 | # open the hosts file for editting 130 | hosts open 131 | ``` 132 | -------------------------------------------------------------------------------- /src/Tools.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-HostsAction 2 | { 3 | [CmdletBinding()] 4 | param ( 5 | [Parameter()] 6 | [string] 7 | $Action, 8 | 9 | [Parameter()] 10 | [string[]] 11 | $Value1, 12 | 13 | [Parameter()] 14 | [string[]] 15 | $Value2, 16 | 17 | [Parameter()] 18 | [string] 19 | $Environment, 20 | 21 | [Parameter()] 22 | [pscredential] 23 | $Credentials 24 | ) 25 | 26 | switch ($Action.ToLowerInvariant()) 27 | { 28 | 'add' { 29 | Add-HostsFileEntries -IP (@($Value1) | Select-Object -First 1) -Hostnames $Value2 -Environment $Environment 30 | } 31 | 32 | 'backup' { 33 | New-HostsFileBackup -Path (@($Value1) | Select-Object -First 1) -Write 34 | } 35 | 36 | 'browse' { 37 | Open-HostsFileEntries -Values $Value1 -Protocol (@($Value2) | Select-Object -First 1) -Environment $Environment 38 | } 39 | 40 | 'clear' { 41 | Clear-HostsFile 42 | } 43 | 44 | 'disable' { 45 | Disable-HostsFileEntries -Values $Value1 -Environment $Environment 46 | } 47 | 48 | 'diff' { 49 | Compare-HostsFiles -Path (@($Value1) | Select-Object -First 1) 50 | } 51 | 52 | 'enable' { 53 | Enable-HostsFileEntries -Values $Value1 -Environment $Environment 54 | } 55 | 56 | 'export' { 57 | Export-HostsFile -Path (@($Value1) | Select-Object -First 1) -Values $Value2 -Environment $Environment 58 | } 59 | 60 | 'import' { 61 | Import-HostsFile -Path (@($Value1) | Select-Object -First 1) -Values $Value2 -Environment $Environment 62 | } 63 | 64 | 'list' { 65 | Get-HostsFile -Values $Value1 -Environment $Environment -State All 66 | } 67 | 68 | 'merge' { 69 | Merge-HostsFiles -Paths $Value1 70 | } 71 | 72 | 'open' { 73 | Open-HostsFile 74 | } 75 | 76 | 'path' { 77 | return (Get-HostsFilePath) 78 | } 79 | 80 | 'rdp' { 81 | Invoke-HostsFileEntriesRdp -Values $Value1 -Environment $Environment -Credentials $Credentials 82 | } 83 | 84 | 'remove' { 85 | Remove-HostsFileEntries -Values $Value1 -Environment $Environment 86 | } 87 | 88 | 'restore' { 89 | Restore-HostsFile -Path (@($Value1) | Select-Object -First 1) 90 | } 91 | 92 | 'set' { 93 | Set-HostsFileEntries -IP (@($Value1) | Select-Object -First 1) -Hostnames $Value2 -Environment $Environment 94 | } 95 | 96 | 'show' { 97 | Get-Content -Path (Get-HostsFilePath) -Raw 98 | } 99 | 100 | 'test' { 101 | Test-HostsFileEntries -Values $Value1 -Ports $Value2 -Environment $Environment 102 | } 103 | } 104 | } 105 | 106 | function Open-HostsFile 107 | { 108 | [CmdletBinding()] 109 | param() 110 | 111 | $path = Get-HostsFilePath 112 | Write-Verbose "Opening $($path)" 113 | 114 | if (Test-IsUnix) { 115 | vi $path 116 | } 117 | else { 118 | notepad.exe $path 119 | } 120 | } 121 | 122 | function Compare-HostsFiles 123 | { 124 | [CmdletBinding()] 125 | param ( 126 | [Parameter(Mandatory=$true)] 127 | [string] 128 | $Path 129 | ) 130 | 131 | # ensure the path exists 132 | if (!(Test-Path $Path)) { 133 | throw "File not found: $($Path)" 134 | } 135 | 136 | # get the hosts file 137 | $mainInfo = @{} 138 | @(Get-HostsFileEntriesByState -HostsMap (@(ConvertFrom-HostsFile)) -State Enabled) | ForEach-Object { 139 | if (!$mainInfo.ContainsKey($_.IP)) { 140 | $mainInfo[$_.IP] = @() 141 | } 142 | 143 | $mainInfo[$_.IP] += $_.Hosts 144 | } 145 | 146 | # get the other hosts file 147 | $otherInfo = @{} 148 | @(Get-HostsFileEntriesByState -HostsMap (@(ConvertFrom-HostsFile -Path $Path)) -State Enabled) | ForEach-Object { 149 | if (!$otherInfo.ContainsKey($_.IP)) { 150 | $otherInfo[$_.IP] = @() 151 | } 152 | 153 | $otherInfo[$_.IP] += $_.Hosts 154 | } 155 | 156 | # what would be added? 157 | $otherInfo.Keys | ForEach-Object { 158 | $_key = $_ 159 | $_hosts = @() 160 | 161 | if ($mainInfo.ContainsKey($_key)) { 162 | $otherInfo[$_key] | ForEach-Object { 163 | if ($mainInfo[$_key] -inotcontains $_) { 164 | $_hosts += $_ 165 | } 166 | } 167 | } 168 | else { 169 | $_hosts = @($otherInfo[$_key]) 170 | } 171 | 172 | if (($_hosts | Measure-Object).Count -gt 0) { 173 | Write-Host "+ [$($_key) - $($_hosts -join ' ')]" -ForegroundColor Green 174 | } 175 | } 176 | 177 | # what would be removed? 178 | $mainInfo.Keys | ForEach-Object { 179 | $_key = $_ 180 | $_hosts = @() 181 | 182 | if ($otherInfo.ContainsKey($_key)) { 183 | $mainInfo[$_key] | ForEach-Object { 184 | if ($otherInfo[$_key] -inotcontains $_) { 185 | $_hosts += $_ 186 | } 187 | } 188 | } 189 | else { 190 | $_hosts = @($mainInfo[$_key]) 191 | } 192 | 193 | if (($_hosts | Measure-Object).Count -gt 0) { 194 | Write-Host "- [$($_key) - $($_hosts -join ' ')]" -ForegroundColor Red 195 | } 196 | } 197 | } 198 | 199 | function Remove-HostsFileEntries 200 | { 201 | [CmdletBinding()] 202 | param ( 203 | [Parameter()] 204 | [string[]] 205 | $Values, 206 | 207 | [Parameter()] 208 | [string] 209 | $Environment 210 | ) 211 | 212 | $info = @(ConvertFrom-HostsFile) 213 | 214 | @(Set-DefaultValueAll -Values $Values) | ForEach-Object { 215 | $_value = $_ 216 | $_entries = @(Get-HostsFileEntries -HostsMap $info -IP $_value -Environment $Environment -Hostname $_value -State All -Like) 217 | 218 | if (($_entries | Measure-Object).Count -eq 0) { 219 | Write-Verbose "Already removed: [$($_value)] {$(Resolve-HostsEnvironment -Environment $Environment)}" 220 | } 221 | else { 222 | $_entries | ForEach-Object { 223 | $_entry = $_ 224 | @(Get-HostsFileEntryHosts -Entry $_entry -Value $_value) | ForEach-Object { 225 | $info = Remove-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_ -Environment $Environment 226 | } 227 | } 228 | } 229 | } 230 | 231 | # write back to hosts file 232 | Out-HostsFile -HostsMap $info 233 | } 234 | 235 | function Enable-HostsFileEntries 236 | { 237 | [CmdletBinding()] 238 | param ( 239 | [Parameter()] 240 | [string[]] 241 | $Values, 242 | 243 | [Parameter()] 244 | [string] 245 | $Environment 246 | ) 247 | 248 | $info = @(ConvertFrom-HostsFile) 249 | 250 | @(Set-DefaultValueAll -Values $Values) | ForEach-Object { 251 | $_value = $_ 252 | $_entries = @(Get-HostsFileEntries -HostsMap $info -IP $_value -Hostname $_value -Environment $Environment -State Disabled -Like) 253 | 254 | if (($_entries | Measure-Object).Count -eq 0) { 255 | Write-Verbose "Already enabled: [$($_value)] {$(Resolve-HostsEnvironment -Environment $Environment)}" 256 | } 257 | else { 258 | $_entries | ForEach-Object { 259 | $_entry = $_ 260 | @(Get-HostsFileEntryHosts -Entry $_entry -Value $_value) | ForEach-Object { 261 | $info = Add-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_ -Environment $Environment 262 | } 263 | } 264 | } 265 | } 266 | 267 | # write back to hosts file 268 | Out-HostsFile -HostsMap $info 269 | } 270 | 271 | function Disable-HostsFileEntries 272 | { 273 | [CmdletBinding()] 274 | param ( 275 | [Parameter()] 276 | [string[]] 277 | $Values, 278 | 279 | [Parameter()] 280 | [string] 281 | $Environment 282 | ) 283 | 284 | $info = @(ConvertFrom-HostsFile) 285 | 286 | @(Set-DefaultValueAll -Values $Values) | ForEach-Object { 287 | $_value = $_ 288 | $_entries = @(Get-HostsFileEntries -HostsMap $info -IP $_value -Hostname $_value -Environment $Environment -State Enabled -Like) 289 | 290 | if (($_entries | Measure-Object).Count -eq 0) { 291 | Write-Verbose "Already disabled: [$($_value)] {$(Resolve-HostsEnvironment -Environment $Environment)}" 292 | } 293 | else { 294 | $_entries | ForEach-Object { 295 | $_entry = $_ 296 | @(Get-HostsFileEntryHosts -Entry $_entry -Value $_value) | ForEach-Object { 297 | $info = Disable-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_ -Environment $Environment 298 | } 299 | } 300 | } 301 | } 302 | 303 | # write back to hosts file 304 | Out-HostsFile -HostsMap $info 305 | } 306 | 307 | function Test-HostsFileEntries 308 | { 309 | [CmdletBinding()] 310 | param ( 311 | [Parameter()] 312 | [string[]] 313 | $Values, 314 | 315 | [Parameter()] 316 | [string[]] 317 | $Ports, 318 | 319 | [Parameter()] 320 | [string] 321 | $Environment 322 | ) 323 | 324 | # do we have any ports? 325 | $hasPorts = (($Ports | Measure-Object).Count -gt 0) 326 | 327 | # grab all enabled entries in the hosts file for the value passed 328 | @(Get-HostsFile -Values $Values -Environment $Environment -State Enabled) | ForEach-Object { 329 | $_ip = $_.IP 330 | $_name = ($_.Hosts | Select-Object -First 1) 331 | 332 | # either ping the host, or test a specific port 333 | if (!$hasPorts) { 334 | Test-HostsFileEntry -IP $_ip -Hostname $_name 335 | } 336 | else { 337 | $Ports | ForEach-Object { 338 | Test-HostsFileEntry -IP $_ip -Hostname $_name -Port $_ 339 | } 340 | } 341 | } 342 | } 343 | 344 | function Open-HostsFileEntries 345 | { 346 | [CmdletBinding()] 347 | param ( 348 | [Parameter()] 349 | [string[]] 350 | $Values, 351 | 352 | [Parameter()] 353 | [string] 354 | $Protocol, 355 | 356 | [Parameter()] 357 | [string] 358 | $Environment 359 | ) 360 | 361 | # set a default HTTPS protocol 362 | if ([string]::IsNullOrWhiteSpace($Protocol)) { 363 | $Protocol = 'https' 364 | } 365 | 366 | # grab all enabled entries in the hosts file for the value passed 367 | @(Get-HostsFile -Values $Values -Environment $Environment -State Enabled) | ForEach-Object { 368 | $_name = ($_.Hosts | Select-Object -First 1) 369 | $_url = "$($Protocol)://$($_name)" 370 | 371 | Write-Verbose "Opening: $($_url)" 372 | Start-Process "$($_url)" 373 | } 374 | } 375 | 376 | function Invoke-HostsFileEntriesRdp 377 | { 378 | [CmdletBinding()] 379 | param ( 380 | [Parameter()] 381 | [string[]] 382 | $Values, 383 | 384 | [Parameter()] 385 | [string] 386 | $Environment, 387 | 388 | [Parameter()] 389 | [pscredential] 390 | $Credentials 391 | ) 392 | 393 | # assign creds if passed 394 | if ($null -ne $Credentials) { 395 | $_domain = $Credentials.GetNetworkCredential().Domain 396 | $_username = $Credentials.GetNetworkCredential().UserName 397 | $_password = $Credentials.GetNetworkCredential().Password 398 | 399 | if (![string]::IsNullOrWhiteSpace($_domain)) { 400 | $_username = "$($_domain)\$($_username)" 401 | } 402 | } 403 | 404 | # grab all enabled entries in the hosts file for the value passed 405 | @(Get-HostsFile -Values $Values -Environment $Environment -State Enabled) | ForEach-Object { 406 | $_ip = $_.IP 407 | $_name = ($_.Hosts | Select-Object -First 1) 408 | Write-Verbose "Remoting onto $($_name)" 409 | 410 | # just attempt to open a connection if no credentials 411 | if ($null -eq $Credentials) { 412 | mstsc.exe /v:$_ip 413 | } 414 | 415 | # otherwise, add credentials to cmdkey temporarily, and then connect 416 | else { 417 | try { 418 | cmdkey.exe /generic:$_ip /user:$_username /pass:$_password | Out-Null 419 | mstsc.exe /v:$_ip 420 | Start-Sleep -Seconds 4 421 | } 422 | finally { 423 | cmdkey.exe /delete:$_ip | Out-Null 424 | } 425 | } 426 | } 427 | } 428 | 429 | function Add-HostsFileEntries 430 | { 431 | [CmdletBinding()] 432 | param ( 433 | [Parameter(Mandatory=$true)] 434 | [string] 435 | $IP, 436 | 437 | [Parameter(Mandatory=$true)] 438 | [string[]] 439 | $Hostnames, 440 | 441 | [Parameter()] 442 | [string] 443 | $Environment 444 | ) 445 | 446 | # get the hosts file 447 | $info = @(ConvertFrom-HostsFile) 448 | 449 | # loop through each hostname and add it 450 | $Hostnames | ForEach-Object { 451 | $info = Add-HostsFileEntry -HostsMap $info -IP $IP -Hostname $_ -Environment $Environment 452 | } 453 | 454 | # write back to hosts file 455 | Out-HostsFile -HostsMap $info 456 | } 457 | 458 | function Set-HostsFileEntries 459 | { 460 | [CmdletBinding()] 461 | param ( 462 | [Parameter(Mandatory=$true)] 463 | [string] 464 | $IP, 465 | 466 | [Parameter(Mandatory=$true)] 467 | [string[]] 468 | $Hostnames, 469 | 470 | [Parameter()] 471 | [string] 472 | $Environment 473 | ) 474 | 475 | # get the hosts file 476 | $info = @(ConvertFrom-HostsFile) 477 | 478 | # reset hosts for all the entries for the IP 479 | $entries = @(Get-HostsFileEntry -HostsMap $info -Value $IP -Type IP -State Enabled) 480 | if (($entries | Measure-Object).Count -eq 0) { 481 | $info += (Get-HostsFileEntryObject -IP $IP -Hostnames @() -Environment $Environment -Enabled $true) 482 | } 483 | else { 484 | $entries | ForEach-Object { 485 | $_.Hosts = @() 486 | $_.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $_.Environment) 487 | } 488 | } 489 | 490 | # loop through each hostname and add it 491 | $Hostnames | ForEach-Object { 492 | $info = Add-HostsFileEntry -HostsMap $info -IP $IP -Hostname $_ -Environment $Environment 493 | } 494 | 495 | # write back to hosts file 496 | Out-HostsFile -HostsMap $info 497 | } 498 | 499 | function Clear-HostsFile 500 | { 501 | [CmdletBinding()] 502 | param() 503 | 504 | # empty the hosts file 505 | Out-HostsFile -Content ([string]::Empty) -Message 'Hosts file cleared' 506 | } 507 | 508 | function Restore-HostsFile 509 | { 510 | [CmdletBinding()] 511 | param ( 512 | [Parameter()] 513 | [string] 514 | $Path 515 | ) 516 | 517 | try { 518 | $details = Get-HostsFileBackupDetails -BackupPath $Path 519 | 520 | if (!(Test-Path $details.Backup.Path)) { 521 | throw "No $($details.Backup.Name) file found" 522 | } 523 | 524 | Copy-Item -Path $details.Backup.Path -Destination $details.Hosts.Path -Force | Out-Null 525 | Write-Verbose "Restored hosts file from $($details.Backup.Name)" 526 | } 527 | catch { 528 | throw "Failed to restore hosts files from $($details.Backup.Name)" 529 | } 530 | } 531 | 532 | function Merge-HostsFiles 533 | { 534 | [CmdletBinding()] 535 | param ( 536 | [Parameter(Mandatory=$true)] 537 | [string[]] 538 | $Paths 539 | ) 540 | 541 | # ensure the paths exist 542 | $Paths | ForEach-Object { 543 | if (!(Test-Path $_)) { 544 | throw "File not found: $($_)" 545 | } 546 | } 547 | 548 | # get the hosts file 549 | $info = @(ConvertFrom-HostsFile) 550 | 551 | # loop through each merge path, parsing and importing them 552 | $Paths | ForEach-Object { 553 | $_path = $_ 554 | 555 | # loop through each entry in the file 556 | @(ConvertFrom-HostsFile -Path $_path) | ForEach-Object { 557 | $_entry = $_ 558 | 559 | # and now loop through each host, removing any occurrences from base file 560 | $_entry.Hosts | ForEach-Object { 561 | $_host = $_ 562 | 563 | # if the host exists in the base file against a different IP, then remove it 564 | Get-HostsFileEntry -HostsMap $info -Value $_host -Type Hostname -State Enabled | ForEach-Object { 565 | if ($_.IP -ine $_entry.IP) { 566 | $_.Hosts = @($_.Hosts | Where-Object { $_ -ine $_host }) 567 | } 568 | } 569 | 570 | # call either add or disable on IP+host 571 | if ($_entry.Enabled) { 572 | $info = Add-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_host -Environment $_entry.Environment 573 | } 574 | else { 575 | $info = Disable-HostsFileEntry -HostsMap $info -IP $_entry.IP -Hostname $_host -Environment $_entry.Environment 576 | } 577 | } 578 | } 579 | } 580 | 581 | # write back to hosts file 582 | Out-HostsFile -HostsMap $info 583 | } 584 | 585 | function Import-HostsFile 586 | { 587 | [CmdletBinding()] 588 | param ( 589 | [Parameter(Mandatory=$true)] 590 | [string] 591 | $Path, 592 | 593 | [Parameter()] 594 | [string[]] 595 | $Values, 596 | 597 | [Parameter()] 598 | [string] 599 | $Environment 600 | ) 601 | 602 | # ensure the path exists 603 | if (!(Test-Path $Path)) { 604 | throw "File not found: $($Path)" 605 | } 606 | 607 | # store the main hosts file path 608 | $_HostsPathTmp = $Script:HostsFilePath 609 | 610 | # get the relevant entries 611 | $Script:HostsFilePath = $Path 612 | $info = Get-HostsFile -Values $Values -Environment $Environment -State All 613 | 614 | # write back to main hosts file 615 | $Script:HostsFilePath = $_HostsPathTmp 616 | Out-HostsFile -HostsMap $info -Message "Hosts file imported from: $($Path)" 617 | } 618 | 619 | function Export-HostsFile 620 | { 621 | [CmdletBinding()] 622 | param ( 623 | [Parameter(Mandatory=$true)] 624 | [string] 625 | $Path, 626 | 627 | [Parameter()] 628 | [string[]] 629 | $Values, 630 | 631 | [Parameter()] 632 | [string] 633 | $Environment 634 | ) 635 | 636 | # get the relevant entries 637 | $info = Get-HostsFile -Values $Values -Environment $Environment -State All 638 | 639 | # write to export location 640 | $Script:HostsFilePath = $Path 641 | Out-HostsFile -HostsMap $info -Message "Hosts file exported to: $($Path)" 642 | } 643 | 644 | function Get-HostsFile 645 | { 646 | [CmdletBinding()] 647 | param ( 648 | [Parameter()] 649 | [string[]] 650 | $Values, 651 | 652 | [Parameter()] 653 | [string] 654 | $Environment, 655 | 656 | [Parameter()] 657 | [ValidateSet('All', 'Disabled', 'Enabled')] 658 | [string] 659 | $State 660 | ) 661 | 662 | $info = @(ConvertFrom-HostsFile) 663 | $results = @() 664 | 665 | # filter by environment and state 666 | $info = @(Get-HostsFileEntriesByEnvironment -HostsMap $info -Environment $Environment) 667 | $info = @(Get-HostsFileEntriesByState -HostsMap $info -State $State) 668 | 669 | # filter by values 670 | if (($Values | Measure-Object).Count -eq 0) { 671 | $results = $info 672 | } 673 | else { 674 | $Values | ForEach-Object { 675 | @(Get-HostsFileEntries -HostsMap $info -IP $_ -Hostname $_ -State $State -Like) | ForEach-Object { 676 | $_tmp = $_ 677 | if (($results | Where-Object { $_.Hash -eq $_tmp.Hash } | Measure-Object).Count -eq 0) { 678 | $results += $_tmp 679 | } 680 | } 681 | } 682 | } 683 | 684 | return ($results | Select-Object IP, Hosts, Environment, Enabled) 685 | } 686 | 687 | 688 | function New-HostsFileBackup 689 | { 690 | [CmdletBinding()] 691 | param ( 692 | [Parameter()] 693 | [string] 694 | $Path, 695 | 696 | [switch] 697 | $Write 698 | ) 699 | 700 | $details = Get-HostsFileBackupDetails -BackupPath $Path 701 | 702 | # if a backup exists, back that up temporarily 703 | if (Test-Path $details.Backup.Path) { 704 | Copy-Item -Path $details.Backup.Path -Destination $details.Backup.Temp -Force | Out-Null 705 | } 706 | 707 | # backup the hosts file 708 | if (Test-Path $details.Hosts.Path) { 709 | Copy-Item -Path $details.Hosts.Path -Destination $details.Backup.Path -Force | Out-Null 710 | } 711 | 712 | # remove tmp backup 713 | if (Test-Path $details.Backup.Temp) { 714 | Remove-Item -Path $details.Backup.Temp -Force | Out-Null 715 | } 716 | 717 | if ($Write) { 718 | Write-Verbose "Hosts file backed up to $($details.Backup.Name)" 719 | } 720 | } 721 | 722 | function Get-HostsFileBackupDetails 723 | { 724 | [CmdletBinding()] 725 | param ( 726 | [Parameter()] 727 | [string] 728 | $BackupPath 729 | ) 730 | 731 | $path = Get-HostsFilePath 732 | 733 | if ([string]::IsNullOrWhiteSpace($BackupPath)) { 734 | $basepath = Split-Path -Parent -Path $path 735 | if ([string]::IsNullOrWhiteSpace($basepath)) { 736 | $basepath = '.' 737 | } 738 | 739 | $backup = Join-Path $basepath "$(Split-Path -Leaf -Path $path).bak" 740 | } 741 | else { 742 | $backup = $BackupPath 743 | } 744 | 745 | return @{ 746 | Hosts = @{ 747 | Path = $path 748 | Name = (Split-Path -Leaf -Path $path) 749 | } 750 | Backup = @{ 751 | Path = $backup 752 | Name = (Split-Path -Leaf -Path $backup) 753 | Temp = "$($backup).tmp" 754 | } 755 | } 756 | } 757 | 758 | function Set-DefaultValueAll 759 | { 760 | [CmdletBinding()] 761 | param ( 762 | [Parameter()] 763 | [string[]] 764 | $Values 765 | ) 766 | 767 | if (($Values | Measure-Object).Count -eq 0) { 768 | return @('*') 769 | } 770 | 771 | return @($Values) 772 | } 773 | 774 | function Test-AdminUser 775 | { 776 | [CmdletBinding()] 777 | param() 778 | 779 | # check the current platform, if it's unix then return true 780 | if (Test-IsUnix) { 781 | return 782 | } 783 | 784 | try { 785 | $principal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent()) 786 | 787 | if ($null -eq $principal) { 788 | $admin = $false 789 | } 790 | else { 791 | $admin = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) 792 | } 793 | } 794 | catch [exception] { 795 | Write-Verbose 'Error checking user administrator priviledges' 796 | Write-Verbose $_.Exception.Message 797 | $admin = $false 798 | } 799 | 800 | if (!$admin) { 801 | throw 'Must be running with administrator priviledges to use the hosts command' 802 | } 803 | } 804 | 805 | function Get-PSVersionTable 806 | { 807 | [CmdletBinding()] 808 | param() 809 | 810 | return $PSVersionTable 811 | } 812 | 813 | function Test-IsUnix 814 | { 815 | [CmdletBinding()] 816 | param() 817 | 818 | return (Get-PSVersionTable).Platform -ieq 'unix' 819 | } 820 | 821 | function Get-HostsFilePath 822 | { 823 | [CmdletBinding()] 824 | param() 825 | 826 | # custom path 827 | if (![string]::IsNullOrWhiteSpace($Script:HostsFilePath)) { 828 | return $Script:HostsFilePath 829 | } 830 | 831 | # unix 832 | if (Test-IsUnix) { 833 | return '/etc/hosts' 834 | } 835 | 836 | # windows is default 837 | return "$($env:windir)\System32\drivers\etc\hosts" 838 | } 839 | 840 | function Get-HostsIPRegex 841 | { 842 | [CmdletBinding()] 843 | param() 844 | 845 | return "(?(\[[a-z0-9\:]+\]|((\d+\.){3}\d+)|\:\:\d+))" 846 | } 847 | 848 | function Get-HostsNameRegex 849 | { 850 | [CmdletBinding()] 851 | param() 852 | 853 | return "(?((([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*)+)" 854 | } 855 | 856 | function ConvertFrom-HostsFile 857 | { 858 | [CmdletBinding()] 859 | param ( 860 | [Parameter()] 861 | [string] 862 | $Path 863 | ) 864 | 865 | if ([string]::IsNullOrWhiteSpace($Path)) { 866 | $Path = Get-HostsFilePath 867 | } 868 | 869 | $map = @() 870 | $currentEnv = [string]::Empty 871 | 872 | # if the path doesn't exist, just return 873 | if (!(Test-Path $Path)) { 874 | return $map 875 | } 876 | 877 | # parse the file 878 | (Get-Content $Path) | ForEach-Object { 879 | # check if it's an environment start tag 880 | if ($_ -imatch "^\s*\#\s*\<--\s*(?[a-z0-9\-]+)\s*$") { 881 | $currentEnv = $Matches['name'] 882 | } 883 | 884 | # check if it's an environment end tag 885 | elseif ($_ -imatch "^\s*\#\s*--\>\s*$") { 886 | $currentEnv = [string]::Empty 887 | } 888 | 889 | # check if it's a host entry 890 | elseif ($_ -imatch "^\s*(?[\#]{0,1})\s*$(Get-HostsIPRegex)\s+$(Get-HostsNameRegex)\s*$") { 891 | $map += (Get-HostsFileEntryObject ` 892 | -IP ($Matches['ip'].Trim()) ` 893 | -Hostnames @($Matches['hosts'].Trim() -isplit '\s+') ` 894 | -Environment $currentEnv ` 895 | -Enabled ([string]::IsNullOrWhiteSpace($Matches['enabled']))) 896 | } 897 | } 898 | 899 | $map = Update-HostsFileObject -HostsMap $map 900 | return $map 901 | } 902 | 903 | function Get-HostsFileEntryObject 904 | { 905 | [CmdletBinding()] 906 | param ( 907 | [Parameter(Mandatory=$true)] 908 | [string] 909 | $IP, 910 | 911 | [Parameter()] 912 | [string[]] 913 | $Hostnames, 914 | 915 | [Parameter()] 916 | [string] 917 | $Environment, 918 | 919 | [Parameter()] 920 | [bool] 921 | $Enabled 922 | ) 923 | 924 | return (New-Object -TypeName psobject | 925 | Add-Member -MemberType NoteProperty -Name IP -Value $IP -PassThru | 926 | Add-Member -MemberType NoteProperty -Name Hosts $Hostnames -PassThru | 927 | Add-Member -MemberType NoteProperty -Name Environment -Value (Resolve-HostsEnvironment -Environment $Environment) -PassThru | 928 | Add-Member -MemberType NoteProperty -Name Enabled -Value $Enabled -PassThru | 929 | Add-Member -MemberType NoteProperty -Name Hash -Value ([string]::Empty) -PassThru) 930 | } 931 | 932 | function Resolve-HostsEnvironment 933 | { 934 | [CmdletBinding()] 935 | param ( 936 | [Parameter()] 937 | [string] 938 | $Environment, 939 | 940 | [Parameter()] 941 | [string] 942 | $Current 943 | ) 944 | 945 | # default empty env to None 946 | if ([string]::IsNullOrWhiteSpace($Environment)) { 947 | $Environment = 'None' 948 | } 949 | 950 | # if no current env passed, just return the env 951 | if ([string]::IsNullOrWhiteSpace($Current)) { 952 | return $Environment 953 | } 954 | 955 | # if current env is not None, return current if env is also None 956 | if ($Current -ine 'None' -and $Environment -ieq 'None') { 957 | return $Current 958 | } 959 | 960 | return $Environment 961 | } 962 | 963 | function Update-HostsFileObject 964 | { 965 | [CmdletBinding()] 966 | param ( 967 | [Parameter()] 968 | $HostsMap 969 | ) 970 | 971 | $crypto = [System.Security.Cryptography.SHA256]::Create() 972 | 973 | $HostsMap | ForEach-Object { 974 | $str = "$($_.IP)|$($_.Hosts -join '|')|$($_.Environment)" 975 | $_.Hash = [System.Convert]::ToBase64String($crypto.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($str))) 976 | } 977 | 978 | return $HostsMap 979 | } 980 | 981 | function ConvertTo-HostsFile 982 | { 983 | [CmdletBinding()] 984 | param ( 985 | [Parameter()] 986 | $HostsMap 987 | ) 988 | 989 | $str = [string]::Empty 990 | $nl = [Environment]::NewLine 991 | 992 | # if there are no entries, then it's empty 993 | if (($HostsMap | Measure-Object).Count -eq 0) { 994 | return $str 995 | } 996 | 997 | # get each of the environments 998 | $HostsMap | Select-Object -ExpandProperty Environment | Group-Object | ForEach-Object { 999 | $_env = $_.Name 1000 | $str += "#<-- $($_env)$($nl)" 1001 | 1002 | # loop through each one, forming each entry line 1003 | $HostsMap | Where-Object { $_.Environment -ieq $_env } | ForEach-Object { 1004 | if ($null -ne $_ -and ![string]::IsNullOrWhiteSpace($_.IP) -and ($_.Hosts | Measure-Object).Count -gt 0) { 1005 | if (!$_.Enabled) { 1006 | $str += '# ' 1007 | } 1008 | 1009 | $str += "$($_.IP)`t$($_.Hosts -join ' ')$($nl)" 1010 | } 1011 | } 1012 | 1013 | $str += "#-->$($nl)$($nl)" 1014 | } 1015 | 1016 | return $str 1017 | } 1018 | 1019 | function Get-HostsFileEntriesByEnvironment 1020 | { 1021 | [CmdletBinding()] 1022 | param ( 1023 | [Parameter()] 1024 | [object[]] 1025 | $HostsMap, 1026 | 1027 | [Parameter()] 1028 | [string] 1029 | $Environment 1030 | ) 1031 | 1032 | if ([string]::IsNullOrWhiteSpace($Environment)) { 1033 | return $HostsMap 1034 | } 1035 | 1036 | return @($HostsMap | Where-Object { $_.Environment -ieq $Environment }) 1037 | } 1038 | 1039 | function Get-HostsFileEntry 1040 | { 1041 | [CmdletBinding()] 1042 | param ( 1043 | [Parameter()] 1044 | [object[]] 1045 | $HostsMap, 1046 | 1047 | [Parameter()] 1048 | [string] 1049 | $Value, 1050 | 1051 | [Parameter()] 1052 | [ValidateSet('IP', 'Hostname')] 1053 | [string] 1054 | $Type, 1055 | 1056 | [Parameter()] 1057 | [ValidateSet('All', 'Disabled', 'Enabled')] 1058 | [string] 1059 | $State, 1060 | 1061 | [Parameter()] 1062 | [string] 1063 | $Environment 1064 | ) 1065 | 1066 | $HostsMap = @(Get-HostsFileEntriesByEnvironment -HostsMap $HostsMap -Environment $Environment) 1067 | 1068 | switch ($Type.ToLowerInvariant()) 1069 | { 1070 | 'IP' { 1071 | $HostsMap = @($HostsMap | Where-Object { $_.IP -ieq $Value }) 1072 | } 1073 | 1074 | 'Hostname' { 1075 | $HostsMap = @($HostsMap | Where-Object { $_.Hosts -icontains $Value }) 1076 | } 1077 | } 1078 | 1079 | return @(Get-HostsFileEntriesByState -HostsMap $HostsMap -State $State) 1080 | } 1081 | 1082 | function Get-HostsFileEntryHosts 1083 | { 1084 | [CmdletBinding()] 1085 | param ( 1086 | [Parameter()] 1087 | [object] 1088 | $Entry, 1089 | 1090 | [Parameter()] 1091 | [string] 1092 | $Value 1093 | ) 1094 | 1095 | if ($Entry.IP -ilike $Value) { 1096 | return @($Entry.Hosts) 1097 | } 1098 | 1099 | $hosts = @() 1100 | 1101 | $Entry.Hosts | Where-Object { 1102 | if ($_ -ilike $Value) { 1103 | $hosts += $_ 1104 | } 1105 | } 1106 | 1107 | return @($hosts) 1108 | } 1109 | 1110 | function Get-HostsFileEntriesByState 1111 | { 1112 | [CmdletBinding()] 1113 | param ( 1114 | [Parameter()] 1115 | [object[]] 1116 | $HostsMap, 1117 | 1118 | [Parameter()] 1119 | [ValidateSet('All', 'Disabled', 'Enabled')] 1120 | [string] 1121 | $State 1122 | ) 1123 | 1124 | switch ($State.ToLowerInvariant()) 1125 | { 1126 | 'disabled' { 1127 | $HostsMap = @($HostsMap | Where-Object { !$_.Enabled }) 1128 | } 1129 | 1130 | 'enabled' { 1131 | $HostsMap = @($HostsMap | Where-Object { $_.Enabled }) 1132 | } 1133 | } 1134 | 1135 | return @($HostsMap) 1136 | } 1137 | 1138 | function Test-HostnameAgainstDifferentIP 1139 | { 1140 | [CmdletBinding()] 1141 | param ( 1142 | [Parameter()] 1143 | [object[]] 1144 | $HostsMap, 1145 | 1146 | [Parameter(Mandatory=$true)] 1147 | [string] 1148 | $IP, 1149 | 1150 | [Parameter(Mandatory=$true)] 1151 | [string] 1152 | $Hostname, 1153 | 1154 | [switch] 1155 | $Throw 1156 | ) 1157 | 1158 | $h = (Get-HostsFileEntry -HostsMap $HostsMap -Value $Hostname -Type Hostname -State Enabled | Select-Object -First 1) 1159 | $bound = ($null -ne $h -and $h.IP -ine $IP) 1160 | 1161 | if ($Throw -and $bound) { 1162 | throw "The hostname [$($Hostname)] is bound against a different IP address: [$($h.IP)]" 1163 | } 1164 | 1165 | return $bound 1166 | } 1167 | 1168 | function Get-IPHostString 1169 | { 1170 | [CmdletBinding()] 1171 | param ( 1172 | [Parameter(Mandatory=$true)] 1173 | [string] 1174 | $IP, 1175 | 1176 | [Parameter(Mandatory=$true)] 1177 | [string] 1178 | $Hostname, 1179 | 1180 | [Parameter()] 1181 | [string] 1182 | $Environment 1183 | ) 1184 | 1185 | return "[$($IP) - $($Hostname)] {$(Resolve-HostsEnvironment -Environment $Environment)}" 1186 | } 1187 | 1188 | function Remove-HostsFileEntry 1189 | { 1190 | [CmdletBinding()] 1191 | param ( 1192 | [Parameter()] 1193 | [object[]] 1194 | $HostsMap, 1195 | 1196 | [Parameter(Mandatory=$true)] 1197 | [string] 1198 | $IP, 1199 | 1200 | [Parameter(Mandatory=$true)] 1201 | [string] 1202 | $Hostname, 1203 | 1204 | [Parameter()] 1205 | [string] 1206 | $Environment 1207 | ) 1208 | 1209 | # get entries 1210 | $entries = @(Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State All) 1211 | 1212 | # skip if already removed 1213 | if (($entries | Measure-Object).Count -eq 0) { 1214 | Write-Verbose "Already removed $(Get-IPHostString $IP $Hostname $Environment)" 1215 | return $HostsMap 1216 | } 1217 | 1218 | # remove hostname from that entries 1219 | $entries | ForEach-Object { 1220 | $_.Hosts = @($_.Hosts | Where-Object { $_ -ine $Hostname }) 1221 | $_.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $_.Environment) 1222 | } 1223 | 1224 | Write-Verbose "Removing $(Get-IPHostString $IP $Hostname $entries[0].Environment)" 1225 | return $HostsMap 1226 | } 1227 | 1228 | function Disable-HostsFileEntry 1229 | { 1230 | [CmdletBinding()] 1231 | param ( 1232 | [Parameter()] 1233 | [object[]] 1234 | $HostsMap, 1235 | 1236 | [Parameter(Mandatory=$true)] 1237 | [string] 1238 | $IP, 1239 | 1240 | [Parameter(Mandatory=$true)] 1241 | [string] 1242 | $Hostname, 1243 | 1244 | [Parameter()] 1245 | [string] 1246 | $Environment 1247 | ) 1248 | 1249 | # see if there's an enabled entry, and remove hostname from that entry 1250 | Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State Enabled | ForEach-Object { 1251 | $_.Hosts = @($_.Hosts | Where-Object { $_ -ine $Hostname }) 1252 | } 1253 | 1254 | # skip if already disabled 1255 | $entries = @(Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State Disabled) 1256 | if (($entries | Measure-Object).Count -gt 0) { 1257 | Write-Verbose "Already disabled $(Get-IPHostString $IP $Hostname $Environment)" 1258 | 1259 | $entries | ForEach-Object { 1260 | $_.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $_.Environment) 1261 | } 1262 | 1263 | return $HostsMap 1264 | } 1265 | 1266 | # disable IP+Hostname 1267 | $entry = (Get-HostsFileEntry -HostsMap $HostsMap -Value $IP -Type IP -State Disabled | Select-Object -First 1) 1268 | if ($null -eq $entry) { 1269 | $entry = (Get-HostsFileEntryObject -IP $IP -Hostnames @($Hostname) -Environment $Environment -Enabled $false) 1270 | $HostsMap += $entry 1271 | } 1272 | else { 1273 | $entry.Hosts = @($entry.Hosts) + $Hostname 1274 | $entry.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $entry.Environment) 1275 | } 1276 | 1277 | Write-Verbose "Disabling $(Get-IPHostString $IP $Hostname $entry.Environment)" 1278 | return $HostsMap 1279 | } 1280 | 1281 | function Add-HostsFileEntry 1282 | { 1283 | [CmdletBinding()] 1284 | param ( 1285 | [Parameter()] 1286 | [object[]] 1287 | $HostsMap, 1288 | 1289 | [Parameter(Mandatory=$true)] 1290 | [string] 1291 | $IP, 1292 | 1293 | [Parameter(Mandatory=$true)] 1294 | [string] 1295 | $Hostname, 1296 | 1297 | [Parameter()] 1298 | [string] 1299 | $Environment 1300 | ) 1301 | 1302 | # is the host being added, or enabled from previously being diabled? 1303 | $enabling = $false 1304 | 1305 | # fail if the hostname or IP address are invalid 1306 | if ($IP -inotmatch "^$(Get-HostsIPRegex)$") { 1307 | throw "The IP address [$($IP)] is invalid" 1308 | } 1309 | 1310 | if ($Hostname -inotmatch "^$(Get-HostsNameRegex)$") { 1311 | throw "The hostname [$($Hostname)] is invalid" 1312 | } 1313 | 1314 | # fail if the hostname is found against a different IP 1315 | Test-HostnameAgainstDifferentIP -HostsMap $HostsMap -IP $IP -Hostname $Hostname -Throw | Out-Null 1316 | 1317 | # see if there's a disabled entry, and remove hostname from that entry 1318 | Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State Disabled | ForEach-Object { 1319 | $enabling = $true 1320 | $_.Hosts = @($_.Hosts | Where-Object { $_ -ine $Hostname }) 1321 | } 1322 | 1323 | # skip if already added/enabled 1324 | $entries = @(Get-HostsFileEntries -HostsMap $HostsMap -IP $IP -Hostname $Hostname -State Enabled) 1325 | if (($entries | Measure-Object).Count -gt 0) { 1326 | Write-Verbose "Already $(if ($enabling) { 'enabled' } else { 'added' }) [$($IP) - $($Hostname)]" 1327 | 1328 | $entries | ForEach-Object { 1329 | $_.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $_.Environment) 1330 | } 1331 | 1332 | return $HostsMap 1333 | } 1334 | 1335 | # add IP+Hostname 1336 | $entry = (Get-HostsFileEntry -HostsMap $HostsMap -Value $IP -Type IP -State Enabled | Select-Object -First 1) 1337 | if ($null -eq $entry) { 1338 | $entry = (Get-HostsFileEntryObject -IP $IP -Hostnames @($Hostname) -Environment $Environment -Enabled $true) 1339 | $HostsMap += $entry 1340 | } 1341 | else { 1342 | $entry.Hosts = @($entry.Hosts) + $Hostname 1343 | $entry.Environment = (Resolve-HostsEnvironment -Environment $Environment -Current $entry.Environment) 1344 | } 1345 | 1346 | Write-Verbose "$(if ($enabling) { 'Enabling' } else { 'Adding' }) $(Get-IPHostString $IP $Hostname $entry.Environment)" 1347 | return $HostsMap 1348 | } 1349 | 1350 | function Get-HostsFileEntries 1351 | { 1352 | [CmdletBinding()] 1353 | param ( 1354 | [Parameter()] 1355 | [object[]] 1356 | $HostsMap, 1357 | 1358 | [Parameter()] 1359 | [string] 1360 | $IP, 1361 | 1362 | [Parameter()] 1363 | [string] 1364 | $Hostname, 1365 | 1366 | [Parameter()] 1367 | [string] 1368 | $Environment, 1369 | 1370 | [Parameter()] 1371 | [ValidateSet('All', 'Disabled', 'Enabled')] 1372 | [string] 1373 | $State, 1374 | 1375 | [switch] 1376 | $Like 1377 | ) 1378 | 1379 | $HostsMap = @(Get-HostsFileEntriesByEnvironment -HostsMap $HostsMap -Environment $Environment) 1380 | 1381 | $HostsMap = @($HostsMap | Where-Object { 1382 | if ($Like) { 1383 | $_.IP -ilike $IP -or ($_.Hosts | Where-Object { $_ -ilike $Hostname } | Measure-Object).Count -ne 0 1384 | } 1385 | else { 1386 | $_.IP -ilike $IP -and ($_.Hosts | Where-Object { $_ -ilike $Hostname } | Measure-Object).Count -ne 0 1387 | } 1388 | }) 1389 | 1390 | return @(Get-HostsFileEntriesByState -HostsMap $HostsMap -State $State) 1391 | } 1392 | 1393 | function Out-HostsFile 1394 | { 1395 | [CmdletBinding()] 1396 | param ( 1397 | [Parameter()] 1398 | [object[]] 1399 | $HostsMap, 1400 | 1401 | [Parameter()] 1402 | [string] 1403 | $Content, 1404 | 1405 | [Parameter()] 1406 | [string] 1407 | $Path, 1408 | 1409 | [Parameter()] 1410 | [string] 1411 | $Message 1412 | ) 1413 | 1414 | # create backup of current 1415 | New-HostsFileBackup 1416 | 1417 | # set an appropriate output message 1418 | if ([string]::IsNullOrWhiteSpace($Message)) { 1419 | $Message = 'Hosts file updated' 1420 | } 1421 | 1422 | # write out to hosts file 1423 | try { 1424 | $hosts_path = Get-HostsFilePath 1425 | New-HostsFilePath -Path (Split-Path -Parent -Path $hosts_path) 1426 | 1427 | if ([string]::IsNullOrWhiteSpace($Path)) { 1428 | if (($HostsMap | Measure-Object).Count -gt 0) { 1429 | $Content = ConvertTo-HostsFile -HostsMap $HostsMap 1430 | } 1431 | 1432 | $Content | Out-File -FilePath $hosts_path -Encoding ascii -Force -ErrorAction Stop | Out-Null 1433 | } 1434 | else { 1435 | Copy-Item -Path $Path -Destination $hosts_path -Force -ErrorAction Stop | Out-Null 1436 | } 1437 | 1438 | Write-Verbose "$($Message)" 1439 | } 1440 | catch { 1441 | Restore-HostsFile 1442 | throw $_.Exception 1443 | } 1444 | } 1445 | 1446 | function New-HostsFilePath 1447 | { 1448 | [CmdletBinding()] 1449 | param ( 1450 | [Parameter()] 1451 | [string] 1452 | $Path 1453 | ) 1454 | 1455 | if (!(Test-Path $Path)) { 1456 | New-Item -Path $Path -ItemType Directory -Force | Out-Null 1457 | } 1458 | } 1459 | 1460 | function Test-HostsFileEntry 1461 | { 1462 | [CmdletBinding()] 1463 | param ( 1464 | [Parameter()] 1465 | [string] 1466 | $IP, 1467 | 1468 | [Parameter()] 1469 | [string] 1470 | $Hostname, 1471 | 1472 | [Parameter()] 1473 | [string] 1474 | $Port 1475 | ) 1476 | 1477 | # either ping the host, or test a specific port 1478 | if ([string]::IsNullOrWhiteSpace($Port)) { 1479 | Write-Host "Testing $($Hostname)>" -NoNewline 1480 | $result = Test-NetConnection -ComputerName $IP -WarningAction SilentlyContinue -ErrorAction SilentlyContinue 1481 | } 1482 | else { 1483 | Write-Host "Testing $($Hostname):$($Port)>" -NoNewline 1484 | $result = Test-NetConnection -ComputerName $IP -Port $Port -WarningAction SilentlyContinue -ErrorAction SilentlyContinue 1485 | } 1486 | 1487 | # was the test successful or a failure? 1488 | if ($null -eq $result -or (!$result.PingSucceeded -and !$result.TcpTestSucceeded)) { 1489 | Write-Host "`tFailed" -ForegroundColor Red 1490 | } 1491 | else { 1492 | Write-Host "`tSuccess" -ForegroundColor Green 1493 | } 1494 | } --------------------------------------------------------------------------------