DSCResources/DSC_HostsFile/DSC_HostsFile.psm1

$modulePath = Join-Path -Path (Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent) -ChildPath 'Modules'

# Import the Networking Common Modules
Import-Module -Name (Join-Path -Path $modulePath `
        -ChildPath (Join-Path -Path 'NetworkingDsc.Common' `
            -ChildPath 'NetworkingDsc.Common.psm1'))

Import-Module -Name (Join-Path -Path $modulePath -ChildPath 'DscResource.Common')

# Import Localization Strings
$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'

<#
    .SYNOPSIS
    Returns the current state of a hosts file entry.
 
    .PARAMETER HostName
    Specifies the name of the computer that will be mapped to an IP address.
 
    .PARAMETER IPAddress
    Specifies the IP Address that should be mapped to the host name.
 
    .PARAMETER Ensure
    Specifies if the hosts file entry should be created or deleted.
#>

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $HostName,

        [Parameter()]
        [System.String]
        $IPAddress,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    Write-Verbose -Message ($script:localizedData.StartingGet -f $HostName)

    $result = Get-HostEntry -HostName $HostName

    if ($null -ne $result)
    {
        return @{
            HostName  = $result.HostName
            IPAddress = $result.IPAddress
            Ensure    = 'Present'
        }
    }
    else
    {
        return @{
            HostName  = $HostName
            IPAddress = $null
            Ensure    = 'Absent'
        }
    }
}

<#
    .SYNOPSIS
    Adds, updates or removes a hosts file entry.
 
    .PARAMETER HostName
    Specifies the name of the computer that will be mapped to an IP address.
 
    .PARAMETER IPAddress
    Specifies the IP Address that should be mapped to the host name.
 
    .PARAMETER Ensure
    Specifies if the hosts file entry should be created or deleted.
#>

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $HostName,

        [Parameter()]
        [System.String]
        $IPAddress,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    $hostPath = "$env:windir\System32\drivers\etc\hosts"
    $currentValues = Get-TargetResource @PSBoundParameters

    Write-Verbose -Message ($script:localizedData.StartingSet -f $HostName)

    if ($Ensure -eq 'Present' -and $PSBoundParameters.ContainsKey('IPAddress') -eq $false)
    {
        New-InvalidArgumentException `
            -Message $($($script:localizedData.UnableToEnsureWithoutIP) -f $Address, $AddressFamily) `
            -ArgumentName 'IPAddress'
    }

    if ($currentValues.Ensure -eq 'Absent' -and $Ensure -eq 'Present')
    {
        Write-Verbose -Message ($script:localizedData.CreateNewEntry -f $HostName)
        Add-Content -Path $hostPath -Value "`r`n$IPAddress`t$HostName"
    }
    else
    {
        $hosts = Get-Content -Path $hostPath
        $replace = $hosts | Where-Object -FilterScript {
            [System.String]::IsNullOrEmpty($_) -eq $false -and $_.StartsWith('#') -eq $false -and $_ -like "*$HostName*"
        }

        $multiLineEntry = $false
        $data = $replace -split '\s+'

        if ($data.Length -gt 2)
        {
            $multiLineEntry = $true
        }

        if ($Ensure -eq 'Present')
        {
            Write-Verbose -Message ($script:localizedData.UpdateExistingEntry -f $HostName)

            if ($multiLineEntry -eq $true)
            {
                $newReplaceLine = $replace -replace $HostName, ''
                $hosts = $hosts -replace $replace, $newReplaceLine
                $hosts += "$IPAddress`t$HostName"
            }
            else
            {
                $hosts = $hosts -replace $replace, "$IPAddress`t$HostName"
            }
        }
        else
        {
            Write-Verbose -Message ($script:localizedData.RemoveEntry -f $HostName)

            if ($multiLineEntry -eq $true)
            {
                $newReplaceLine = $replace -replace $HostName, ''
                $hosts = $hosts -replace $replace, $newReplaceLine
            }
            else
            {
                $hosts = $hosts -replace $replace, ''
            }
        }

        Set-Content -Path $hostPath -Value $hosts
    }
}

<#
    .SYNOPSIS
    Tests the current state of a hosts file entry.
 
    .PARAMETER HostName
    Specifies the name of the computer that will be mapped to an IP address.
 
    .PARAMETER IPAddress
    Specifies the IP Address that should be mapped to the host name.
 
    .PARAMETER Ensure
    Specifies if the hosts file entry should be created or deleted.
#>

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $HostName,

        [Parameter()]
        [System.String]
        $IPAddress,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    $currentValues = Get-TargetResource @PSBoundParameters

    Write-Verbose -Message ($script:localizedData.StartingTest -f $HostName)

    if ($Ensure -ne $currentValues.Ensure)
    {
        return $false
    }

    if ($Ensure -eq 'Present' -and $IPAddress -ne $currentValues.IPAddress)
    {
        return $false
    }

    return $true
}

function Get-HostEntry
{
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $HostName
    )

    $hostPath = "$env:windir\System32\drivers\etc\hosts"

    $allHosts = Get-Content -Path $hostPath | Where-Object -FilterScript {
        [System.String]::IsNullOrEmpty($_) -eq $false -and $_.StartsWith('#') -eq $false
    }

    foreach ($hosts in $allHosts)
    {
        $data = $hosts -split '\s+'

        if ($data.Length -gt 2)
        {
            # Account for host entries that have multiple entries on a single line
            $result = @()
            $array = @()

            for ($i = 1; $i -lt $data.Length; $i++)
            {
                <#
                    Filter commments on the line.
                    Example: 0.0.0.0 s.gateway.messenger.live.com # breaks Skype GH-183
                    becomes:
                    0.0.0.0 s.gateway.messenger.live.com
                #>

                if ($data[$i] -eq '#')
                {
                    break
                }

                $array += $data[$i]
            }

            $result = @{
                Host      = $array
                IPAddress = $data[0]
            }
        }
        else
        {
            $result = @{
                Host      = $data[1]
                IPAddress = $data[0]
            }
        }

        if ($result.Host -eq $HostName)
        {
            return @{
                HostName  = $result.Host
                IPAddress = $result.IPAddress
            }
        }
    }
}