Saturday, May 28, 2011

Blackhole DNS powershell script

####################################################################################
#.Synopsis
# Creates and deletes blackhole domains in Windows DNS servers.
#
#.Description
# Script takes fully-qualified domain names (FQDNs) and/or simple domain
# names, then uses them to create primary zones (not Active Directory
# integrated) in a Windows DNS server, setting all zones to resolve to a
# single chosen IP address. The IP address might be "0.0.0.0" or the IP
# of an internal server configured for logging. The intention is to
# prevent clients from resolving the correct IP address of unwanted FQDNs
# and domain names, such as for malware and phishing sites. Such names
# are said to be "blackholed" since they often resolve to 0.0.0.0.
#
#.Parameter InputFile
# Path to a file which contains the FQDNs and domain names to blackhole.
# File can have blank lines, comment lines (# or ;), multiple FQDNs or
# domains per line (space- or comma-delimited), and can be a hosts file
# with IP addresses too (addresses and localhost entires will be ignored).
# You can also include wildcards to input multiple files, e.g.,
# "*bad*.txt", or pass in an array of file objects instead of a string.
#
#.Parameter Domain
# One or more FQDNs or domains to blackhole, separated by spaces or commas.
#
#.Parameter BlackHoleIP
# The IP address to which all blackholed names will resolve. The
# default is "0.0.0.0", but perhaps is better set to an internal server.
# Remember, there is only one IP for ALL the blackholed domains.
#
#.Parameter DnsServerName
# FQDN of the Windows DNS server. Defaults to localhost. If specified,
# please always use a fully-qualified domain name (FQDN), especially if
# the DNS server is a stand-alone or in a different domain.
#
#.Parameter IncludeWildCard
# Will add a wildcard host record (*) to every blackhole domain, which
# will match all possible hostnames within that domain. Keep in mind
# that blackholing "www.sans.org" will treat the "www" as a domain
# name, so a wildcard is not needed to match it; but blackholing just
# "sans.org" will not match "www.sans.org" or "ftp.sans.org" without
# the wildcard. If you only want to blackhole the exact FQDN or domain
# name supplied to the script, then don't include a wildcard record.
# If you are certain that you do not want to resolve anything whatsoever
# under the blackholed domains, then include the wildcard DNS record.
#
#.Parameter ReloadBlackHoleDomains
# Will cause every blackholed domain in your DNS server to re-read the
# one shared zone file they all use. This is the zone file for the
# 000-blackholed-domain.local domain. Keep in mind that the DNS
# graphical management tool shows you what is cached in memory, not
# what is in the zone file. Reload the blackhole domains if you,
# for example, change the blackhole IP address. Using this switch
# causes any other parameters to be ignored.
#
#.Parameter DeleteBlackHoleDomains
# Will delete all blackhole domains, but will not delete any regular
# non-blackhole domains. Strictly speaking, this deletes any domain
# which uses a zone file named "000-blackholed-domain.local.dns".
# The zone file itself is not deleted, but it only 1KB in size.
# Using this switch causes any other parameters to be ignored.
#
#.Parameter RemoveLeadingWWW
# Some lists of blackhole names are simple domain names, while other
# lists might prepend "www." to the beginning of many of the names.
# Use this switch to remove the "www." from the beginning of any
# name to be blackholed, then consider using -IncludeWildCard too.
# Note that "www.", "www1.", "www2." ... "www9." will be cut too,
# but only for a single digit after the "www" part (1-9 only).
#
#.Parameter Credential
# An "authority\username" string to explicitly authenticate to the
# DNS server instead of using single sign-on with the current
# identity. The authority is either a server name or a domain name.
# You will be prompted for the passphrase. You can also pass in
# a variable with a credential object from Get-Credential.
#
#.Example
# .\Blackhole-DNS.ps1 -Domain "www.sans.org"
#
# This will create a primary DNS domain named "www.sans.org"
# which will resolve to "0.0.0.0". DNS server is local.
#
#.Example
# .\Blackhole-DNS.ps1 -Domain "www.sans.org" -BlackHoleIP "10.1.1.1"
#
# This will create a primary DNS domain named "www.sans.org"
# which will resolve to "10.1.1.1". DNS server is local.
#
#.Example
# .\Blackhole-DNS.ps1 -InputFile file.txt -IncludeWildCard
#
# This will create DNS domains out of all the FQDNs and domain
# names listed in file.txt, plus add a wildcard (*) record.
#
#.Example
# .\Blackhole-DNS.ps1 -ReloadBlackHoleDomains
#
# Perhaps after changing the blackhole IP address, this will cause
# all blackholed domains to re-read their shared zone file.
#
#.Example
# .\Blackhole-DNS.ps1 -DeleteBlackHoleDomains
#
# This will delete all blackholed domains, but will not delete
# any other domains. This does not delete the blackhole zone file.
#
#.Example
# .\Blackhole-DNS.ps1 -InputFile file.txt -DnsServerName `
# "server7.sans.org" -Credential "server7\administrator"
#
# This will create blackholed domains from file.txt on a remote
# DNS server named "server7.sans.org" with explicit credentials.
# You will be prompted for the passphrase.
#
#.Example
# $Cred = Get-Credential -Credential "server7\administrator"
#
# .\Blackhole-DNS.ps1 -InputFile *evil*.txt `
# -DnsServerName "server7.sans.org" -Credential $Cred
#
# This will create blackholed domains from *evil*.txt on a remote
# DNS server named "server7.sans.org" with explicit credentials
# supplied in a credential object ($Cred) which can be reused again.
# Multiple input files may match "*evil*.txt".
#
#Requires -Version 2.0
#
#.Notes
# Author: Jason Fossen (http://blogs.sans.org/windows-security/)
# Version: 1.0
# Updated: 30.Aug.2010
# LEGAL: PUBLIC DOMAIN. SCRIPT PROVIDED "AS IS" WITH NO WARRANTIES OR
# GUARANTEES OF ANY KIND, INCLUDING BUT NOT LIMITED TO
# MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. ALL
# RISKS OF DAMAGE REMAINS WITH THE USER, EVEN IF THE AUTHOR,
# SUPPLIER OR DISTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF
# ANY SUCH DAMAGE. IF YOUR STATE DOES NOT PERMIT THE COMPLETE
# LIMITATION OF LIABILITY, THEN DELETE THIS FILE SINCE YOU ARE
# NOW PROHIBITED TO HAVE IT. TEST ON NON-PRODUCTION SERVERS.
####################################################################################


Param ($InputFile, [String] $Domain, [String] $BlackHoleIP = "0.0.0.0", [String] $DnsServerName = ".", [Switch] $IncludeWildCard,
[Switch] $ReloadBlackHoleDomains, [Switch] $DeleteBlackHoleDomains, [Switch] $RemoveLeadingWWW, $Credential)


# Check for common help switches.
if (($InputFile -ne $Null) -and ($InputFile.GetType().Name -eq "String") -and ($InputFile -match "/\?|-help|--h|--help"))
{
If ($Host.Version.Major -ge 2) { get-help -full .\blackhole-dns.ps1 }
Else {"`nPlease read this script's header in Notepad for the help information."}
Exit
}


# Confirm PowerShell 2.0 or later.
If ($Host.Version.Major -lt 2) { "This script requires PowerShell 2.0 or later.`nDownload the latest version from http://www.microsoft.com/powershell`n" ; Exit }


# If necessary, prompt user for domain\username and a passphrase.
If ($Credential) { $Cred = Get-Credential -Credential $Credential }


Function Main
{

# Test access to WMI at target server ($ZoneClass is used later too).
$ZoneClass = GetWMI -Query "SELECT * FROM META_CLASS WHERE __CLASS = 'MicrosoftDNS_Zone'"
If (-not $? -or $ZoneClass.Name -ne "MicrosoftDNS_Zone") { Throw("Failed to connect to WMI service or the WMI DNS_Zone namespace!") ; Exit }


##### Parse input domains, but exclude the following: localhost, any IP addresses, blank lines.
#Process any -Domain args.
[Object[]] $Domains = @($Domain -Split "[\s\;\,]")

#Process any -InputFile arguments and expand any wildcards.
If (($InputFile -ne $Null) -and ($InputFile.GetType().Name -eq "String")) { $InputFile = dir $InputFile }
If ($InputFile -ne $Null) { $InputFile | ForEach { $Domains += Get-Content $_ | Where { $_ -notmatch "^[\#\;\<]" } | ForEach { $_ -Split "[\s\;\,]" } } }

#If -RemoveLeadingWWW was used, edit out those "www." strings.
If ($RemoveLeadingWWW) { 0..$([Int] $Domains.Count - 1) | ForEach { $Domains[$_] = $Domains[$_] -Replace "^www[1-9]{0,1}\.","" } }

#Convert to lowercase, remove redundants, exclude blank lines, exclude IPs, exclude anything with a colon in it, e.g., IPv6.
$Domains = $Domains | ForEach { $_.Trim().ToLower() } | Sort -Unique | Where { $_.Length -ne 0 -and $_ -notmatch "^localhost$|^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\:" }
If ($Domains.Count -le 0) { "No domains specified!" ; exit } Else { "`n" + [String] $Domains.Count + " domain(s) to blackhole to $BlackHoleIP." }
#####


##### Get or create the master blackhole zone: 000-blackholed-domain.local.
$WmiPath = "Root\MicrosoftDNS:MicrosoftDNS_Zone.ContainerName='000-blackholed-domain.local',DnsServerName='" + $DnsServerName + "',Name='000-blackholed-domain.local'"

If (InvokeWmiMethod -ObjectPath $WmiPath -MethodName GetDistinguishedName) #Zone exists.
{
"`nThe 000-blackholed-domain.local zone already exists, deleting its existing DNS records."
$ExistingRecords = @(GetWMI -Query "SELECT * FROM MicrosoftDNS_AType WHERE ContainerName = '000-blackholed-domain.local'")
If (-not $?) { Throw("Failed to query the A records of the 000-blackholed-domain.local domain!") ; exit }
If ($ExistingRecords.Count -gt 0) { $ExistingRecords | ForEach { $_.Delete() } }
}
Else #Zone does not exist.
{
"`nCreating the 000-blackholed-domain.local zone and its zone file."
$RR = $ZoneClass.CreateZone("000-blackholed-domain.local",0,$false,$null,$null,"only-edit.000-blackholed-domain.local.")
If (-not $? -or $RR.__CLASS -ne "__PARAMETERS")
{
If ($Credential -And ($DnsServerName.Length -ne 0) -And ($DnsServerName -NotLike "*.*")) { "Did you forget to use a FQDN for the DNS server name?" }
Throw("Failed to create the 000-blackholed-domain.local domain!")
Exit
}
}
#####


##### Create DNS records in master blackhole zone.
# Create the default A record with the blackhole IP address.
$RecordClass = $Null # Defaults to "IN" class of record.
$TTL = 120 # Seconds. Defaults to zone default if you set this to $null.
$ATypeRecords = GetWMI -Query "SELECT * FROM META_CLASS WHERE __CLASS = 'MicrosoftDNS_AType'"
If (-not $? -or $ATypeRecords.Name -ne "MicrosoftDNS_AType") { "`nFailed to query A type records, but continuing..." }
$ARecord = $ATypeRecords.CreateInstanceFromPropertyData($DnsServerName,"000-blackholed-domain.local","000-blackholed-domain.local",$RecordClass,$TTL,$BlackHoleIP)
If ($?) { "Created default DNS record for the 000-blackholed-domain.local zone ($BlackHoleIP)." }
Else { "Failed to create default A record for the 000-blackholed-domain.local zone, but continuing..." }

# Create the wildcard A record if the -IncludeWildCard switch was used.
If ($IncludeWildCard)
{
$ARecord = $ATypeRecords.CreateInstanceFromPropertyData($DnsServerName,"000-blackholed-domain.local","*.000-blackholed-domain.local",$RecordClass,$TTL,$BlackHoleIP)
If ($?) { "Created the wildcard (*) record for the 000-blackholed-domain.local zone ($BlackHoleIP)." }
Else { "Failed to create the wildcard (*) record for the 000-blackholed-domain.local zone, but continuing..." }
}

# Update zone data file on disk after adding the A record(s).
If (InvokeWmiMethod -ObjectPath $WmiPath -MethodName WriteBackZone)
{ "Updated the zone file for 000-blackholed-domain.local." }
Else
{
Start-Sleep -Seconds 2 #Just seems to help...
$ItWorked = InvokeWmiMethod -ObjectPath $WmiPath -MethodName WriteBackZone
If ($ItWorked) { "Updated the zone file for 000-blackholed-domain.local." }
Else {"`nFailed to update the server data file for the 000-blackholed-domain.local zone, but continuing..." }
}
#####


##### Create the blackholed domains using the 000-blackholed-domain.local.dns zone file.
$Created = $NotCreated = 0
$CurrentErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "SilentlyContinue"
$Domains | ForEach `
{
$ZoneClass.CreateZone($_,0,$false,"000-blackholed-domain.local.dns",$null,"only-edit.000-blackholed-domain.local.") | Out-Null
If ($?) { $Created++ } Else { $NotCreated++ }
}
$ErrorActionPreference = $CurrentErrorActionPreference
"`nBlackhole domains created at the DNS server: $Created"
"`nDomains NOT created (maybe already existed): $NotCreated`n"

} #End of Main



Function GetWMI ([String] $Query)
{
# This is a helper function for the sake of -Credential.
If ($Credential) { Get-WmiObject -Query $Query -Namespace "Root/MicrosoftDNS" -ComputerName $DnsServerName -Credential $Cred }
Else { Get-WmiObject -Query $Query -Namespace "Root/MicrosoftDNS" -ComputerName $DnsServerName }
}



Function InvokeWmiMethod ([String] $ObjectPath, [String] $MethodName)
{
# This is a helper function for the sake of -Credential.
$ErrorActionPreference = "SilentlyContinue"
If ($Credential) { Invoke-WmiMethod -Path $ObjectPath -Name $MethodName -ComputerName $DnsServerName -Credential $Cred }
Else { Invoke-WmiMethod -Path $ObjectPath -Name $MethodName -ComputerName $DnsServerName }
$? #Returns
}



Function Reload-BlackHoledDomains
{
# Function causes blackholed domains to be reloaded from the shared master zone file, perhaps after a -BlackHoleIP change.
# You may get errors if zone is temporarily locked for updates, but the DNS server's default lock is only two minutes.
"`nReloading blackholed domains from the 000-blackholed-domain.local.dns zone file, `nwhich may take a few minutes if you have 10K+ domains..."
$BHDomains = @(GetWMI -Query "SELECT * FROM MicrosoftDNS_Zone WHERE DataFile = '000-blackholed-domain.local.dns'")
If (-not $?) { Throw("Failed to connect to WMI service!") ; exit }
"`nBlackholed domains to be reloaded from the zone file: " + [String] $BHDomains.Count
$Locked = @() #Index numbers of zones which are temporarily locked and cannot be reloaded yet.
$i = 0
$ErrorActionPreference = "SilentlyContinue"
If ($BHDomains.Count -gt 0) { $BHDomains | ForEach { $_.ReloadZone() ; If (-not $?) { $Locked += $i } ; $i++ } }
If ($Locked.Count -gt 0)
{
"`n" + [String] $Locked.Count + " zone(s) are still temporarily locked, will try those again in two minutes.`nPlease wait two minutes or hit Ctrl-C to cancel..."
Start-Sleep -Seconds 60
"Just one more minute... Thank you for holding, your function call is appreciated."
Start-Sleep -Seconds 30
"Just 30 more seconds... Your patience is admirable, and you're good looking too!"
Start-Sleep -Seconds 35
$Locked | ForEach { $BHDomains[$_].ReloadZone() ; if (-not $?) { "`n" + [String] $BHDomains[$_].ContainerName + " is still locked and has not reloaded yet." } }
}

"`nThe other blackholed domains were successfully reloaded.`n"
} #End



Function Delete-BlackHoledDomains
{
# Delete all blackholed zones, including 000-blackholed-domain.local, but
# note that this does not delete the (tiny) zone file on the drive.
"`nDeleting all blackholed domains, which may take a few minutes if you have 10K+ domains..."
$BHDomains = @(GetWMI -Query "SELECT * FROM MicrosoftDNS_Zone WHERE DataFile = '000-blackholed-domain.local.dns'")
If (-not $?) { Throw("Failed to connect to WMI service!") ; exit }
"`nBlackhole domains to be deleted: " + [String] $BHDomains.Count
$i = 0
If ($BHDomains.Count -gt 0) { $BHDomains | ForEach { $_.Delete() ; If ($?){$i++} } }
"Blackhole domains deleted count: $i`n"
} #End




########################
# MAIN #
########################
If ($ReloadBlackHoleDomains) { Reload-BlackHoledDomains ; Exit }
If ($DeleteBlackHoleDomains) { Delete-BlackHoledDomains ; Exit }
Main

host file updater

This was posted over at sans.org but seems to have disappeared. so I posted it here for others.


####################################################################################
#.Synopsis
# Adds domain names to the HOSTS file for blocking. Find the HOSTS
# file at $env:systemroot\system32\drivers\etc\hosts
#
#.Description
# The HOSTS text file is typically used for name resolution before any
# DNS queries are performed. This script can import the names from one
# or multiple text files into the local HOSTS file for blocking correct
# resolution of those names. The input file(s) can be on the local drive,
# in shared folders, or on HTTP servers. By default, names will resolve
# to "0.0.0.0", but a different IP address can be specified. You must be
# a member of the local Administrators group to use the script (or at
# least be granted NTFS write access to the HOSTS file itself).
#
#.Parameter FilePathOrURL
# Path to a file which contains the FQDNs and domain names to blackhole.
# File can have blank lines, comment lines (# or ;), multiple FQDNs or
# domains per line (space- or comma-delimited), and can be a HOSTS file
# with IP addresses too (addresses and localhost entires will be ignored).
# You can also include wildcards to input multiple files (e.g., "bad*.txt"),
# use a UNC path to an SMB shared folder (e.g., "\\server\share\file.txt")
# or an HTTP path; in fact, you can mix all these path together into one
# long space-delimited string for this parameter. The parameter will
# default to "http://www.malwaredomainlist.com/hostslist/hosts.txt" if
# left unspecified.
#
#.Parameter AddDuplicateWWW
# When adding names to the HOSTS file, if any name does not begin with
# "www.", then this switch will add that name twice to the HOSTS file:
# the original unaltered name and a second copy with "www." prepended.
# Many browsers will automatically prepend "www." to any name which
# results in an error or cannot be resolved correctly.
#
#.Parameter ResetToDefaultHostsFile
# Will erase the HOSTS file and add only two entries back to it:
# 127.0.0.1 localhost
# ::1 localhost
#
#.Parameter EditHostsFile
# Opens the HOSTS file in Notepad.exe.
#
#.Parameter ShowHostnameCount
# Displays a count of the number of names in the HOSTS file, not
# including the localhost entries.
#
#.Example
# Update-HostsFile.ps1
#
# Adds all the names from www.MalwareDomainList.com to your HOSTS file
# and makes them all resolve to "0.0.0.0".
#
#.Example
# Update-HostsFile.ps1 -FilePathOrURL "c:\folder\file.txt \\server\ `
# share\remotefile.txt http://www.malwaredomainlist.com/hostslist `
# /hosts.txt" -BlackHoleIP "10.1.1.1"
#
# Blackholes all the names listed in file.txt, remotefile.txt, and all the
# names from the URL shown, then makes them all resolve to "10.1.1.1".
# Notice that each path is separated by a space character.
#
#.Example
# Update-HostsFile.ps1 -ResetToDefaultHostsFile
#
# Erase the HOSTS file and then add back only the following:
# 127.0.0.1 localhost
# ::1 localhost
#
#.Notes
# Author: Jason Fossen (http://blogs.sans.org/windows-security/)
# Version: 1.1
# Updated: 14.Mar.2011
# LEGAL: PUBLIC DOMAIN. SCRIPT PROVIDED "AS IS" WITH NO WARRANTIES OR
# GUARANTEES OF ANY KIND, INCLUDING BUT NOT LIMITED TO
# MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. ALL
# RISKS OF DAMAGE REMAINS WITH THE USER, EVEN IF THE AUTHOR,
# SUPPLIER OR DISTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF
# ANY SUCH DAMAGE. IF YOUR STATE DOES NOT PERMIT THE COMPLETE
# LIMITATION OF LIABILITY, THEN DELETE THIS FILE SINCE YOU ARE
# NOW PROHIBITED TO HAVE IT. TEST ON NON-PRODUCTION SERVERS.
#Requires -Version 2.0
####################################################################################


Param ($FilePathOrURL = "http://www.malwaredomainlist.com/hostslist/hosts.txt",
[String] $BlackholeIP = "0.0.0.0",
[Switch] $ResetToDefaultHostsFile,
[Switch] $AddDuplicateWWW,
[Switch] $EditHostsFile,
[Switch] $ShowHostnameCount)


function update-hostsfile
{
Param ($FilePathOrURL = "http://www.malwaredomainlist.com/hostslist/hosts.txt",
[String] $BlackholeIP = "0.0.0.0",
[Switch] $ResetToDefaultHostsFile,
[Switch] $AddDuplicateWWW,
[Switch] $EditHostsFile,
[Switch] $ShowHostnameCount)

$HostsFilePath = "$env:systemroot\system32\drivers\etc\hosts"
$webclient = new-object System.Net.WebClient
$names = @() #Array of names to add to HOSTS file.

# Check for common help switches and show help text.
if (($FilePathOrURL -ne $null) -and ($FilePathOrURL.GetType().Name -eq "String") -and ($FilePathOrURL -match "/\?|/help|-help|--h|--help"))
{
If ($Host.Version.Major -ge 2) { get-help -full .\update-hostsfile.ps1 }
Else {"`nPlease read this script's header in Notepad for the help information."}
return
}

# Confirm PowerShell 2.0 or later.
If ($Host.Version.Major -lt 2) { "This script requires PowerShell 2.0 or later.`nDownload the latest version from http://www.microsoft.com/powershell`n" ; return }

#Edit hosts file in notepad?
if ($EditHostsFile) { notepad.exe $HostsFilePath ; return }

#Show count of names in hosts file? Does not include localhost entries, but will include non-blackhole names.
if ($ShowHostnameCount)
{ "`nCount of names in hosts file = " + ($(get-content $HostsFilePath) -split " " |
where { $_ -notmatch '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\:|localhost|^\s*$|^\#' }).count ; "`n" ; return }


#Check if we're just resetting the hosts file to the default localhosts.
if ($ResetToDefaultHostsFile)
{
#Sometimes a full CRLF newline (0x0D,0x0A) is not appended
#unless we do these two lines separately (weird).
"127.0.0.1 localhost" | set-content $HostsFilePath -force
"::1 localhost" | add-content $HostsFilePath -force
if (-not $?) { "Error writing to hosts file!" }
return
}

#Get one or more space-delimited input files, split up into the $names array.
#If the path contains spaces, use the 8.3 DOS short name, e.g., PROGRA~1.
$FilePathOrURL = @(( $FilePathOrURL -split '\s+' ) | foreach { $_.trim() } | where { $_.length -gt 1 } )
if ($FilePathOrURL.Count -eq 0) { "No valid paths to input files, quitting." ; return }

$FilePathOrURL | foreach { `
if ($_ -like "http*")
{
$string = $webclient.DownloadString("$_")
#This check might be too crude...
if ($string.contains(" {
"`nDownload failure: " + $_ + "`n"
}
else
{
"`nDownloaded: " + $_ + "`n"
$names += $string -split '[\r\n]+'
}
}
else
{
if (test-path $_)
{
$names += get-content -path $_
if ($?) { "`nImported: " + $_ + "`n" }
else { "`nImport failure: " + $_ + "`n" }
}
else
{ "`nInvalid path: " + $_ + "`n" }
}
}

#Confirm that at least one line was imported.
if ($names.count -lt 1) { "`nNothing to add to the hosts file, quitting." ; return }

#Remove anything after a comment char (# or ;) on each line, including the comment char.
$names = $names | foreach { if ($_.IndexOfAny("#;") -ne -1) { $_.Remove($_.IndexOfAny("#;")) } else { $_ } }

#Split the IPs from the names; maybe this file has no IPs in it.
$names = $names -split '[\,\s]+'

#Filter out blanks, IPv4/IPv6 addresses and localhost, then remove duplicates
$names = $names | foreach {$_.Trim()} | where { $_.Length -gt 1 } | where { $_ -notmatch '\:|^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$|^localhost$' } | sort-object -unique

#If requested, add "www.*" entries for names which don't begin with "www" already.
if ($AddDuplicateWWW){ $names = $names += ($names | where {$_ -notmatch '^www\.'} | foreach {"www." + $_ }) }

#In the hosts file, each line should have a max of nine hostnames, ending with a CRLF.
#Lookup performance is maximized by having nine hostnames per line and by
#resolving to 0.0.0.0 instead of 127.0.0.1, especially if you're listening on TCP/80.
$size = $names.count - 1
for ($i = 0 ; $i -lt $size ; $i = $i + 9)
{ $output += "$BlackholeIP " + ($names[$i..$($i + 8)] -join " ") + [char]13 + [char]10 }

#Sometimes a full CRLF newline (0x0D,0x0A) is not appended
#unless we do these two lines separately (weird).
"127.0.0.1 localhost" | set-content $HostsFilePath -force
"::1 localhost" | add-content $HostsFilePath -force
if (-not $?) { "Error writing to hosts file!" ; return }
$output | add-content $HostsFilePath -force
}



# Main
if ($ResetToDefaultHostsFile) { update-hostsfile -ResetToDefaultHostsFile }
elseif ($EditHostsFile) { update-hostsfile -EditHostsFile }
elseif ($ShowHostnameCount) { update-hostsfile -ShowHostnameCount }
else
{
if ($AddDuplicateWWW) { update-hostsfile -FilePathOrURL $FilePathOrURL -BlackholeIP $BlackholeIP -AddDuplicateWWW }
else {update-hostsfile -FilePathOrURL $FilePathOrURL -BlackholeIP $BlackholeIP }
}