Set-GCHostsFileEntry.ps1

<#
.SYNOPSIS
  Updates an existing hosts file IP address if the host name exists.
  If the host name does not exist in the hosts file it will be added.
.DESCRIPTION
  The hosts file is search to find the host name.
  If the host name is found the IP address is updated.
  If the host name is not found a new entry will be added
  at the bottom of the hosts file.
 
  This cmdlet does not search IP addresses, only host names.
  If you wish to update the host name for an IP address use
  Remove-GCHostsFileEntry followed by Add-GCHostsFileEntry.
 
  WARNING: If you pipe many thousands of InputObjects into this cmdlet
  it will take a long time to process the changes. Be patient.
 
  Type 'Get-Help Set-GCHostsFileEntry -Online' for extra information.
.PARAMETER IPAddress
  The IP address to assign to the host name.
  This can be an IPv4 or IPv6 address.
.PARAMETER HostName
  The name of the host to assign the IP address to.
  This can be a fully qualified name such as server1.domain.com, or a flat name.
  An array of names is supported.
.PARAMETER Comment
  Adds a comment on the same line as the IPAddress and HostName pair.
 
  There is no need to supply the '#' character.
.EXAMPLE
  The following example updates the hosts file entry with a
  host name of 'server1.company.com' to have a new IP address
  of '5.6.7.8'. If the host name 'server1.company.com' does not
  exist in the hosts file it will be added:
 
  Set-GCHostsFileEntry -IPAddress 5.6.7.8 -HostName 'server1.company.com'
  #>

function Set-GCHostsFileEntry {
  [CmdletBinding(DefaultParameterSetName='HostEntry',
                HelpUri = 'https://github.com/grantcarthew/GCPowerShell')]
  [OutputType([String])]
  Param (
    [Parameter(Mandatory=$false,
              Position=0,
              ValueFromPipeline=$true,
              ValueFromPipelineByPropertyName=$true,
              ParameterSetName='HostObject')]
    [PSCustomObject[]]
    $InputObject,

    [Parameter(Mandatory=$true,
              Position=0,
              ValueFromPipeline=$false,
              ParameterSetName='HostEntry')]
    [String]
    $IPAddress,

    [Parameter(Mandatory=$true,
              Position=1,
              ValueFromPipeline=$false,
              ParameterSetName='HostEntry')]
    [String[]]
    $HostName,

    [Parameter(Mandatory=$false,
              Position=2,
              ValueFromPipeline=$false,
              ParameterSetName='HostEntry')]
    [String]
    $Comment
  )

  begin {
    Write-Verbose -Message "Function initiated: $($MyInvocation.MyCommand)"
    Import-Module -Name GCTest

    $hostsFilePath = Join-Path -Path $env:SystemRoot -ChildPath '\System32\drivers\etc\hosts'
    Write-Verbose -Message "Hosts file path set to: $hostsFilePath"

    if (-not (Test-GCFileWrite -Path $hostsFilePath)) {
      Write-Error -Message "Can't write to the hosts file. Check it exists and you have write permissions."
      Exit
    }

    $hostsFileEntries = Get-GCHostsFileEntry 
    $newEntries = New-Object -TypeName System.Collections.ArrayList
    $updateEntries = New-Object -TypeName System.Collections.ArrayList
    
    $allHostNames = New-Object -TypeName System.Collections.ArrayList
    foreach ($entry in $hostsFileEntries) {
      foreach ($name in $entry.HostNames) {
        $allHostNames.Add($name.ToUpper().Trim()) | Out-Null
      }
    }
    function TestNewHostsIP ($NewIP) {
      # The following will cause an error if the IPAddress is invalid.
      # The error will be reported to the console.
      Write-Verbose -Message "Type casting IPAddress: $NewIP"
      [IPAddress]$NewIP | Out-Null
    }
    function TestHostNameExists ($TestHostName) {
      Write-Verbose -Message "Testing for existing entry: $TestHostName" 
      $exists = $false
      foreach ($eachName in $TestHostName) {
        if ($eachName.ToUpper().Trim() -in $allHostNames) {
          $exists = $true 
          break
        }  
      }
      Write-Output -InputObject $exists
    }
    function QueueHostsEntry ($IP, $Names, $Comment, [Switch]$Update) {
      Write-Verbose -Message "Queuing entry: $IP,$Names,$Comment,$Update"
      $tab1 = 17
      $entry = $IP.PadRight($tab1)
      foreach ($qname in $Names) {
        $entry += " $qname"
      }
      if ($Comment) {
        $commentString = $Comment.TrimStart('# ').Insert(0, '# ')
        $entry += " $commentString" 
      }
      if ($Update) {
        $queueItem = [PSCustomObject]@{
          HostNames=$Names;
          NewLine=$entry;
        }
        $updateEntries.Add($queueItem) | Out-Null
      } else {
        $newEntries.Add($entry) | Out-Null
      }
    }
  }

  process {
    if ($InputObject) {
      foreach ($obj in $InputObject) {
        TestNewHostsIP -NewIP $obj.IPAddress.IPAddressToString
        if (TestHostNameExists -TestHostName $obj.HostNames) {
          Write-Verbose -Message "Host name exists: $($obj.HostNames)"
          QueueHostsEntry -IP $obj.IPAddress.IPAddressToString `
                          -Names $obj.HostNames `
                          -Comment $obj.Comment `
                          -Update
        } else {
          Write-Verbose -Message "Host name does not exist: $($obj.HostNames)"
          QueueHostsEntry -IP $obj.IPAddress.IPAddressToString `
                             -Names $obj.HostNames `
                             -Comment $obj.$Comment
        }
      }
    } else {
      TestNewHostsIP -NewIP $IPAddress

      if (TestHostNameExists -TestHostName $HostName) {
        Write-Verbose -Message "Host name exists: $HostName"
        QueueHostsEntry -IP $IPAddress `
                        -Names $HostName `
                        -Comment $Comment `
                        -Update
      } else {
        Write-Verbose -Message "Host name does not exist: $HostName"
        QueueHostsEntry -IP $IPAddress `
                        -Names $HostName `
                        -Comment $Comment `
        QueueNewHostsEntry -NewIp $IPAddress -NewNames $HostName -NewComment $Comment
      }
    }
  }

  end {
    Write-Verbose -Message "Getting content from hosts file."
    $lines = Get-Content -Path $hostsFilePath
    $output = New-Object -TypeName System.Collections.ArrayList
    if ($updateEntries.Count -gt 0) {
      Write-Verbose -Message "Prestaging update entries."
      foreach ($updateEntry in $updateEntries) {
        Write-Verbose -Message "Updating host entry: $($updateEntry.HostNames)"
        foreach ($line in $lines) {
          $lineMatch = $false
          foreach ($updateName in $updateEntry.HostNames) {
            if ($line -match $updateName) {
              $lineMatch = $true
            }
          }
          if ($lineMatch) {
            $output.Add($updateEntry.NewLine) | Out-Null
          } else {
            $output.Add($line) | Out-Null
          }
        }
      }
    } else {
      if ($lines) {
        $output.AddRange($lines)
      }
    }
    if ($newEntries.Count -gt 0) {
      Write-Verbose -Message "Prestaging new entries."
      $output.AddRange($newEntries)
    }
    if ($output.Count -lt 1) {
      Write-Verbose -Message "Nothing to write to the hosts file. No change."
    } else {
      Write-Verbose -Message "Writing new hosts file"
      Set-Content -Path $hostsFilePath -Value $output -Force
    }
    Write-Verbose -Message "Total updated entries: $($updateEntries.Count)"
    Write-Verbose -Message "Total new entries : $($newEntries.Count)"
    Write-Verbose -Message "Total lines changed : $($updateEntries.Count + $newEntries.Count)"
    Write-Verbose -Message "Total lines written : $($output.Count)"

    Write-Verbose -Message "Function completed: $($MyInvocation.MyCommand)"
  }


}