A better way to add and remove Windows hosts file entries
Summary
In this article I present a couple of simple PowerShell scripts which will modify the hosts file on one (or more) Windows machines, so that you can route traffic destined for certain domains to specific IP addresses with minimal fuss. The scripts do this by adding (or removing) entries to (or from) the hosts file and they do this idempotently[1], so you don't need to write any checks when adding or removing. In other words, when you add a host to the hosts file, it won't add it if it's already there.
It could be argued that modifying the hosts file is not really how you should be doing things in a production environment, but it's easy to imagine various scenarios in which it could be useful.
I'll also explain a few useful features of PowerShell scripts, such as the proper way to add support for the -WhatIf
parameter.
Additionally, there's an extra script which enables you to copy your modified hosts file to multiple machines. This needs to be used with extreme caution (although it does make a backup of the remote hosts file), but is useful if you are trying to get precisely the same hosts file onto several machines in one step.
All of the scripts are in GitHub at:
Background
If you need to override DNS settings and route traffic for certain domains to specific IP addresses of your choosing then, if you don't want to mess around with DNS servers and/or proxies, the easiest way is to modify your hosts file. But what if you're dealing with a cluster of machines? What if you're not sure if you've already added some of the hosts entries? What if you need a script to automate the process? Don't worry, I've got you covered.
Why would you do that?
A short while ago, I had a problem and I needed to roll out a quick fix in order to change the routing of traffic to certain domains, for reasons which we don't need to go into now[2]. I needed to do this on several machines in a production environment and I wanted to make it as safe as possible. Bear in mind that I've tidied up the scripts quite a lot since I ran them in real life. Scary stuff, eh?
There are other, less exhilarating reasons that you might want to do this, particularly when you are developing software.
How
There are three scripts:
- AddToHosts.ps1
- RemoveFromHosts.ps1
- UpdateMultipleHostsFiles.ps1
They do exactly what you might imagine.
AddToHosts.ps1
This script adds entries to your local hosts file idempotently and can take three parameters:
- -Hostname (this is the hostname you want to add)
- -DesiredIP (this is the IP address you want to associate with that hostname)
- -CheckHostnameOnly (if this is false (which it is by default) then it checks the combination of hostname and IP address is unique. If it's true, then you can only have one entry per hostname (which is probably more sensible). However, setting it to false allows you to add IPv4 and IPv6 entries, but it also allows you to cause yourself difficulties. Approach with caution)
Example usage:
PS C:\> .\AddToHosts.ps1 -Hostname tomssl.local -DesiredIP 127.0.0.1
About to add 127.0.0.1 for tomssl.local to hosts file.
127.0.0.1 tomssl.local - adding to hosts file... done
PS C:\>
You can see the idempotent nature of the script in this screenshot.
RemoveFromHosts.ps1
This script removes entries from your local hosts files and takes one parameter:
- -Hostname (this is the hostname you want to remove)
It removes all entries for a single domain. So if you have both IPv4 and IPv6 entries, or if you have made a mistake and added multiple entries for a single domain, it will remove all of them.
Example usage:
PS C:\> .\RemoveFromHosts.ps1 -Hostname tomssl.local
About to remove tomssl.local from hosts file.
tomssl.local - removing from hosts file... done
PS C:\>
UpdateMultipleHostsFiles.ps1
This script copies your local hosts file to one or more remote machines (it makes a backup called hosts.bak
in the same directory before doing so) and can take two parameters:
- -ServerList (this is the list of remote servers)
- -Timeout (this is how many seconds it should try to copy each hosts file before timing out. Default value is 5 seconds).
Example usage:
PS C:\> .\UpdateMultipleHostsFiles.ps1 -ServerList @("192.168.1.49","192.168.1.50","192.168.1.51") -Timeout 7
I'm going to copy the local hosts file to 3 servers
Couldn't access file at \\192.168.1.49\C$\Windows\System32\drivers\etc\hosts
New hosts file written to \\192.168.1.50\C$\Windows\System32\drivers\etc\hosts
Couldn't access file at \\192.168.1.51\C$\Windows\System32\drivers\etc\hosts
Done!
PS C:\>
As you can see from the screenshot, you get a nice colour-coded error message for any remote machines it can't reach and it will try every machine in the list once.
A note about -WhatIf
In PowerShell there are certain conventions and one of these is the -WhatIf
parameter. In brief, adding -WhatIf
to a cmdlet which supports it will show you what the cmdlet is going to do, without actually doing anything. Think of it like a trial run.
To add -WhatIf
support to your own cmdlet, add the following to the top of your script:
[CmdletBinding(SupportsShouldProcess=$true)]
If you've written a proper function (and not just a simple script), add it just after the definition, but before the parameters, like this:
Function DoAThing {
[CmdletBinding(SupportsShouldProcess=$true)]
param([string[]]$ServerList,
[int]$TimeOut=5)
....
There are a couple of ways to check if you're executing in -Whatif
mode.
The most common way is like this:
if ($PSCmdlet.ShouldProcess("My Target", "My Operation")) {
# Do real thing
Write-Host "I did a real thing"
}
Which gives the following output (which is somewhat fixed, as you will see). Note that it doesn't execute the code contained in the curly braces:
What if: Performing the operation "My Operation" on target "My Target".
You can also check it like this ($WhatIfPreference
is set automatically):
if ($WhatIfPreference) {
# Do your what if stuff
} else {
# Do your real stuff
}
Or even by doing this (which is a bit strange, hence why I did it in this script):
if ($PSCmdlet.ShouldProcess($null)){
# Do your real stuff
Write-Host "Doing real stuff"
} else {
# Do your what if stuff
Write-Host "Just doing 'what if' stuff"
}
Checking in this way might be handy as you could optionally leave out the else clause or tailor your output (even omitting it completely if desired).
The really clever thing about -WhatIf
The really clever thing about the
-WhatIf
parameter is that, if you call any cmdlets in your script which also support the-WhatIf
parameter, then you don't need to check for it explicitly, as long as you remembered to add[CmdletBinding(SupportsShouldProcess=$true)]
to the top of your script.
Conclusion
There are various reasons (not all of them nefarious) why you might want to edit your Windows hosts file to change the routing of certain domains. These scripts are a safe, easy way to do that, especially in the case where you need to update multiple machines at the same time. A little while ago, I used a somewhat less-sophisticated version of these scripts in real life to fix a major problem. I hope they can help you in a somewhat less stressful scenario.
Follow me on Twitter for more frequent updates. Follow @TomChantler
In a computing context, an idempotent operation can be performed multiple times and always achieve the same result. So in our example of adding an entry to the hosts file, we could perhaps better call it "Ensure Entry Occurs Once In Hosts File" as that's what it's really doing. This is the sort of thing people get asked in job interviews, although it's one thing to know what it means and another to know when it's a good idea to do it. ↩︎
If I told you, I'd probably get killed myself ;-) ↩︎