Functions/Set-HostsEntry.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function Set-HostsEntry
{
    <#
    .SYNOPSIS
    Sets a hosts entry in a hosts file.
     
    .DESCRIPTION
    Sets the IP address for a given hostname. If the hostname doesn't exist in the hosts file, appends a new entry to the end. If the hostname does exist, its IP address gets updated. If you supply a description, it is appended to the line as a comment.
     
    If any duplicate hosts entries are found, they are commented out; Windows uses the first duplicate entry.
     
    This function scans the entire hosts file. If you have a large hosts file, and are updating multiple entries, this function will be slow.
     
    You can operate on a custom hosts file, too. Pass its path with the `Path` parameter.
 
    Sometimes the system's hosts file is in use and locked when you try to update it. The `Set-HostsEntry` function tries 10 times to set a hosts entry before giving up and writing an error. It waits a random amount of time (from 0 to 1000 milliseconds) between each attempt.
     
    .EXAMPLE
    Set-HostsEntry -IPAddress 10.2.3.4 -HostName 'myserver' -Description "myserver's IP address"
     
    If your hosts file contains the following:
     
        127.0.0.1 localhost
         
    After running this command, it will contain the following:
     
        127.0.0.1 localhost
        10.2.3.4 myserver    # myserver's IP address
 
    .EXAMPLE
    Set-HostsEntry -IPAddress 10.5.6.7 -HostName 'myserver'
     
    If your hosts file contains the following:
     
        127.0.0.1 localhost
        10.2.3.4 myserver    # myserver's IP address
     
    After running this command, it will contain the following:
     
        127.0.0.1 localhost
        10.5.6.7 myserver
     
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [Parameter(Mandatory=$true)]
        [Net.IPAddress]
        # The IP address for the hosts entry.
        $IPAddress,

        [Parameter(Mandatory=$true)]
        [string]
        # The hostname for the hosts entry.
        $HostName,

        [string]
        # An optional description of the hosts entry.
        $Description,

        [string]
        # The path to the hosts file where the entry should be set. Defaults to the local computer's hosts file.
        $Path = (Get-PathToHostsFile)
    )

    Set-StrictMode -Version 'Latest'

    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState
 
    $matchPattern = '^(?<IP>[0-9a-f.:]+)\s+(?<HostName>[^\s#]+)(?<Tail>.*)$'  
    $lineFormat = "{0,-45} {1}{2}"
    
    if(-not (Test-Path $Path))
    {
        Write-Warning "Creating hosts file at: $Path"
        New-Item $Path -ItemType File
    }
    
    [string[]]$lines = Read-File -Path $Path -ErrorVariable 'cmdErrors'
    if( $cmdErrors )
    {
        return
    }    
    
    $outLines = New-Object 'Collections.ArrayList'
    $found = $false
    $lineNum = 0
    $updateHostsFile = $false
     
    foreach($line in $lines)
    {
        $lineNum += 1
        
        if($line.Trim().StartsWith("#") -or ($line.Trim() -eq '') )
        {
            [void] $outlines.Add($line)
        }
        elseif($line -match $matchPattern)
        {
            $ip = $matches["IP"]
            $hn = $matches["HostName"]
            $tail = $matches["Tail"].Trim()
            if( $HostName -eq $hn )
            {
                if($found)
                {
                    #this is a duplicate so, let's comment it out
                    [void] $outlines.Add("#$line")
                    $updateHostsFile = $true
                    continue
                }
                $ip = $IPAddress
                $tail = if( $Description ) { "`t# $Description" } else { '' }
                $found = $true   
            }
            else
            {
                $tail = "`t{0}" -f $tail
            }
           
            if( $tail.Trim() -eq "#" )
            {
                $tail = ""
            }

            $outline = $lineformat -f $ip, $hn, $tail
            $outline = $outline.Trim()
            if( $outline -ne $line )
            {
                $updateHostsFile = $true
            }

            [void] $outlines.Add($outline)
                
        }
        else
        {
            Write-Warning ("Hosts file {0}: line {1}: invalid entry: {2}" -f $Path,$lineNum,$line)
            $outlines.Add( ('# {0}' -f $line) )
        }

    }
     
    if(-not $found)
    {
       #add a new entry
       $tail = "`t# $Description"
       if($tail.Trim() -eq "#")
       {
           $tail = ""
       }
           
       $outline = $lineformat -f $IPAddress, $HostName, $tail
       $outline = $outline.Trim()
       [void] $outlines.Add($outline)
       $updateHostsFile = $true
    }

    if( -not $updateHostsFile )
    {
        return
    }
    
    Write-Verbose -Message ('[HOSTS] [{0}] {1,-45} {2}' -f $Path,$IPAddress,$HostName)
    $outLines | Write-File -Path $Path
}