DSCResources/Grani_HostsFile/Grani_HostsFile.psm1

#region Initialize

function Initialize {
    # hosts location
    $script:hostsLocation = "$env:SystemRoot\System32\drivers\etc\hosts";
    $script:encoding = "UTF8";

    # Enum for Ensure
    Add-Type -TypeDefinition @"
        public enum EnsureType
        {
            Present,
            Absent
        }
"@
 -ErrorAction SilentlyContinue; 

    # Enum for Reference
    Add-Type -TypeDefinition @"
        public enum ReferenceType
        {
            DnsServer,
            StaticIp
        }
"@
 -ErrorAction SilentlyContinue; 
}

. Initialize;

#endregion

#region Message Definition

Data VerboseMessages {
    ConvertFrom-StringData -StringData @"
        CheckHostsEntry = Check hosts entry is exists.
        CreateHostsEntry = Create hosts entry {0} : {1}.
        HostsEntryFound = Found a hosts entry {0} : {1}.
        HostsEntryNotFound = Did not find a hosts entry {0} : {1}.
        ReferenceDnsServer = Reference is DnsServer. Trying to connect to DnsServer and get first A record. {0}
        ReferenceStaticIp = Reference is StaticIp. IPAddress will directly used. {0}
        RemoveHostsEntry = Remove hosts entry {0} : {1}.
        RemoveHostsEntryBeforeAdd = Remove duplicate hostname entry before adding host entry. This will ignore IPAddress because correct host entry will add right after remove. hostname : {0}
        RemovedEntryIP = Removed Entry for {0} : {1}.{2}.{3}.{4}
"@

}

Data DebugMessages {
    ConvertFrom-StringData -StringData @"
"@

}

Data ErrorMessages {
    ConvertFrom-StringData -StringData @"
        CouldNotResolveIpWithDnsServer = Could not resolve A revord with DnsServer. IpAddress : {0}
"@

}

#endregion

#region *-TargetResource

function Get-TargetResource {
    [OutputType([Hashtable])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [string]$HostName,
        
        [parameter(Mandatory = $true)]
        [ValidateSet('Present', 'Absent')]
        [string]$Ensure = [EnsureType]::Present,

        [parameter(Mandatory = $true)]
        [string]$IpAddress,

        [parameter(Mandatory = $false)]
        [ValidateSet("DnsServer", "StaticIp")]
        [String]$Reference = "DnsServer"
    )  
    
    $Configuration = @{
        HostName  = $HostName
        IPAddress = $IpAddress
        Reference = $Reference
    };

    Write-Verbose $VerboseMessages.CheckHostsEntry;

    try {
        $ipEntry = ResolveIpAddressReference -IpAddress $IpAddress -HostName $HostName -Reference $Reference;
        if (TestIsHostEntryExists -IpAddress $ipEntry -HostName $HostName) {
            Write-Verbose ($VerboseMessages.HostsEntryFound -f $HostName, $ipEntry);
            $Configuration.Ensure = [EnsureType]::Present;
        }
        else {
            Write-Verbose ($VerboseMessages.HostsEntryNotFound -f $HostName, $ipEntry);
            $Configuration.Ensure = [EnsureType]::Absent;
        }
    }
    catch {
        Write-Error $_;
    }

    return $Configuration;
}

function Set-TargetResource {
    [OutputType([Void])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [string]$HostName,
        
        [parameter(Mandatory = $true)]
        [ValidateSet('Present', 'Absent')]
        [string]$Ensure = [EnsureType]::Present,

        [parameter(Mandatory = $true)]
        [string]$IpAddress,

        [parameter(Mandatory = $false)]
        [ValidateSet("DnsServer", "StaticIp")]
        [String]$Reference = "DnsServer"
    )  

    try {
        $ipEntry = ResolveIpAddressReference -IpAddress $IpAddress -HostName $HostName -Reference $Reference
        $hostEntry = "`n{0}`t{1}" -f $ipEntry, $HostName

        # Absent
        if ($Ensure -eq [EnsureType]::Absent.ToString()) {
            Write-Verbose ($VerboseMessages.RemoveHostsEntry -f $HostName, $ipEntry);
            ((Get-Content $script:hostsLocation) -notmatch "^\s*$") -notmatch "^[^#]*$ipEntry\s+$HostName" | Set-Content -Path $script:hostsLocation -Force -Encoding $script:encoding;
            return;
        }
        else {
            # Present
            Write-Verbose ($VerboseMessages.RemoveHostsEntryBeforeAdd -f $HostName);
            ((Get-Content $script:hostsLocation) -notmatch "^\s*$") -notmatch "^[^#]*(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s+$HostName" | Set-Content -Path $script:hostsLocation -Force -Encoding $script:encoding;

            Write-Verbose ($VerboseMessages.CreateHostsEntry -f $HostName, $ipEntry);
            Add-Content -Path $script:hostsLocation -Value $hostEntry -Force -Encoding $script:encoding;
        }
    }
    catch {
        throw $_;
    }
}

function Test-TargetResource {
    [OutputType([boolean])]
    [CmdletBinding()]
    param
    (
        [parameter(Mandatory = $true)]
        [string]$HostName,
        
        [parameter(Mandatory = $true)]
        [ValidateSet('Present', 'Absent')]
        [string]$Ensure = [EnsureType]::Present,

        [parameter(Mandatory = $true)]
        [string]$IpAddress,

        [parameter(Mandatory = $false)]
        [ValidateSet("DnsServer", "StaticIp")]
        [String]$Reference = "DnsServer"
    )  

    return (Get-TargetResource -HostName $HostName -IpAddress $IpAddress -Ensure $Ensure -Reference $Reference).Ensure -eq $Ensure;
}

#endregion

#region Helper Function

function TestIsHostEntryExists ([string]$IpAddress, [string] $HostName) {
    return ((Get-Content -Path $script:hostsLocation -Encoding $script:encoding) -match "^[^#]*$ipAddress\s+$HostName" | measure).Count -ne 0;
}

function ResolveIpAddressReference ([string]$IpAddress, [string]$HostName, [ReferenceType]$Reference) {
    $ipEntry = if ($Reference -eq [ReferenceType]::StaticIp) {
        # Reference is StaticIp
        Write-Verbose ($VerboseMessages.ReferenceStaticIp -f $IpAddress);
        $IpAddress;
    }
    elseif ($Reference -eq [ReferenceType]::DnsServer) {
        # Reference is DnsServer
        Write-Verbose ($VerboseMessages.ReferenceDnsServer -f $IpAddress);
        $resolveIp = Resolve-DnsName -Name $HostName -Server $IpAddress -DnsOnly | where Type -eq A | sort IPAddress;
        if ($null -eq $resolveIp) {
            throw New-Object System.NullReferenceException ($ErrorMessages.CouldNotResolveIpWithDnsServer -f $IpAddress);
        }
        ($resolveIp | select -First 1).IPAddress;
    }

    return $ipEntry;
}

#endregion

Export-ModuleMember -Function *-TargetResource;