Public/Add-specToHostsFile.ps1

function Add-specToHostsFile {
    <#
    .SYNOPSIS
    Adds one or more specified IP address and hostname mappings to the system's hosts file.
 
    .DESCRIPTION
    This function allows you to add new entries to the system's hosts file. It reads the hosts file once
    into memory, checks for existing entries (optionally matching on hostname only), and appends any new
    entries in one write operation to avoid file lock issues.
 
    The function supports pipeline input for objects containing `DesiredIP` and `Hostname` properties,
    making it ideal for bulk operations or use within automation workflows.
 
    .PARAMETER DesiredIP
    The IP address to associate with the hostname.
 
    .PARAMETER Hostname
    The hostname (or domain name) to add to the hosts file.
 
    .PARAMETER MatchOnHostnameOnly
    If specified, the function checks only for the hostname when determining if an entry exists.
    When this switch is used, existing entries with the same hostname (regardless of IP) will prevent
    the addition of a new entry.
 
    .PARAMETER HostsFilePath
    Specifies the path to the hosts file. Defaults to the system hosts file location.
 
    .INPUTS
    Accepts pipeline input for objects with `DesiredIP` and `Hostname` properties.
 
    .OUTPUTS
    None
 
    .EXAMPLE
    Add-specToHostsFile -DesiredIP "192.168.1.10" -Hostname "example.com"
    Adds "192.168.1.10 example.com" to the hosts file if it does not already exist.
 
    .EXAMPLE
    Add-specToHostsFile -DesiredIP "192.168.1.10" -Hostname "example.com" -MatchOnHostnameOnly
    Adds the entry only if "example.com" is not already present in the hosts file, regardless of IP address.
 
    .EXAMPLE
    $entries = @(
        [pscustomobject]@{ DesiredIP = "192.168.1.10"; Hostname = "example1.com" },
        [pscustomobject]@{ DesiredIP = "192.168.1.20"; Hostname = "example2.com" }
    )
    $entries | Add-specToHostsFile
    Adds both entries to the hosts file if they do not already exist.
 
    .NOTES
    Author: owen.heaume
    Version 1.0.0 - Initial release
            1.0.1 - Slight refactor to better support unit testing (Create HostFilePath parameter)
            1.0.2 - Improved error handling, renaming of function
            1.0.3 - Updated to avoid file lock issues during multiple additions.
                  - Writes all new entries in a single operation.
    #>


    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$DesiredIP,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string]$Hostname,

        [Parameter()]
        [switch]$MatchOnHostnameOnly,

        [Parameter()]
        [string]$HostsFilePath = "$($Env:WinDir)\system32\Drivers\etc\hosts"
    )

    begin {
        try {
            # Load the hosts file into memory once
            $hostsFile = if (Test-Path $HostsFilePath -ea Stop) {
                Get-Content $HostsFilePath -ea Stop
            } else { @() }

            # Collect new entries to add
            $entriesToAdd = @()
        } catch {
            Write-Error "An error occurred loading the hosts file: $_"
            return
        }
    }

    process {
        try {
            $escapedHostname = [Regex]::Escape($Hostname)
            $patternToMatch = if ($MatchOnHostnameOnly) {
                ".*\s+$escapedHostname.*"
            } else {
                ".*$DesiredIP\s+$escapedHostname.*"
            }

            # Check if the entry already exists
            if ($hostsFile -match $patternToMatch) {
                Write-Host "$DesiredIP".PadRight(20, ' ') "$Hostname - already exists" -ForegroundColor DarkYellow
            } else {
                Write-Host "$DesiredIP".PadRight(20, ' ') "$Hostname - queued for addition" -ForegroundColor Gray
                $entriesToAdd += "$DesiredIP".PadRight(20, ' ') + $Hostname
            }
        } catch {
            Write-Error "An error occurred processing '$Hostname': $_"
        }
    }

    end {
        if ($entriesToAdd.Count -eq 0) {
            Write-Host 'No new entries to add.' -ForegroundColor DarkGray
            return
        }

        try {
            if ($PSCmdlet.ShouldProcess('hosts file', "Add $($entriesToAdd.Count) new entries")) {
                # Write all pending entries at once (atomic)
                Add-Content -Encoding ASCII -Path $HostsFilePath -Value ($entriesToAdd -join "`r`n") -ea Stop
                Write-Host "Successfully added $($entriesToAdd.Count) entries to hosts file." -ForegroundColor Green
            }
        } catch {
            Write-Error "An error occurred writing to the hosts file: $_"
        } finally {
            Write-Host 'Processing complete.' -ForegroundColor Gray
        }
    }
}