DSCResources/MSFT_xHostsFile/MSFT_xHostsFile.psm1
$Script:hostsFilePath = "${env:windir}\system32\drivers\etc\hosts" Import-LocalizedData -BindingVariable LocalizedData -FileName MSFT_xHostsFile.psd1 -BaseDirectory $PSScriptRoot -Verbose function Get-TargetResource { [OutputType([Hashtable])] param ( [parameter(Mandatory = $true)] [string] $HostName, [parameter(Mandatory = $true)] [string] $IPAddress ) $configuration = @{ HostName = $HostName IPAddress = $IPAddress } Write-Verbose -Message $localizedData.checkingHostsFileEntry try { if (Test-HostEntry -IPAddress $IPAddress -HostName $HostName) { Write-Verbose -Message ($localizedData.hostsFileEntryFound -f $HostName, $IPAddress) $configuration.Add('Ensure','Present') } else { Write-Verbose -Message ($localizedData.hostsFileEntryNotFound -f $HostName, $IPAddress) $configuration.Add('Ensure','Absent') } return $configuration } catch { $exception = $_ Write-Verbose -Message ($LocalizedData.anErrorOccurred -f $name, $exception.message) while ($null -ne $exception.InnerException) { $exception = $exception.innerException Write-Verbose -Message ($LocalizedData.innerException -f $name, $exception.message) } } } function Set-TargetResource { param ( [parameter(Mandatory = $true)] [string] $HostName, [parameter(Mandatory = $true)] [string] $IPAddress, [parameter()] [ValidateSet('Present','Absent')] [string] $Ensure = 'Present' ) try { if ($Ensure -eq 'Present') { Write-Verbose -Message ($localizedData.creatingHostsFileEntry -f $HostName, $IPAddress) Add-HostEntry -IPAddress $IPAddress -HostName $HostName Write-Verbose -Message ($localizedData.hostsFileEntryAdded -f $HostName, $IPAddress) } else { Write-Verbose -Message ($localizedData.removingHostsFileEntry -f $HostName, $IPAddress) Remove-HostEntry -IPAddress $IPAddress -HostName $HostName Write-Verbose -Message ($localizedData.hostsFileEntryRemoved -f $HostName, $IPAddress) } } catch { $errorId = 'HostsFileUpdateError' $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation $errorMessage = ($LocalizedData.anErrorOccurred -f $name, $exception.message) $exception = New-Object -TypeName System.InvalidOperationException ` -ArgumentList $errorMessage $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord ` -ArgumentList $exception, $errorId, $errorCategory, $null } } function Test-TargetResource { [OutputType([boolean])] param ( [parameter(Mandatory = $true)] [string] $HostName, [parameter(Mandatory = $true)] [string] $IPAddress, [parameter()] [ValidateSet('Present','Absent')] [string] $Ensure = 'Present' ) try { Write-Verbose -Message $localizedData.checkingHostsFileEntry $entryExist = Test-HostEntry -IPAddress $IPAddress -HostName $HostName if ($Ensure -eq "Present") { if ($entryExist) { Write-Verbose -Message ($localizedData.hostsFileEntryFound -f $HostName, $IPAddress) return $true } else { Write-Verbose -Message ($localizedData.hostsFileEntryShouldExist -f $HostName, $IPAddress) return $false } } else { if ($entryExist) { Write-Verbose -Message $localizedData.hostsFileShouldNotExist return $false } else { Write-Verbose -Message $localizedData.hostsFileEntryNotFound return $true } } } catch { $exception = $_ Write-Verbose -Message ($LocalizedData.anErrorOccurred -f $name, $exception.message) while ($null -ne $exception.innerException) { $exception = $exception.innerException Write-Verbose -Message ($LocalizedData.innerException -f $name, $exception.message) } } } function Test-HostEntry { param ( [string] $IPAddress, [string] $HostName ) foreach ($line in (Get-Content -Path $script:HostsFilePath)) { $parsed = Convert-EntryLine -Line $line if ($parsed.IPAddress -eq $IPAddress) { return $parsed.HostNames -contains $HostName } } return $false } function Add-HostEntry { param ( [string] $IPAddress, [string] $HostName ) $content = @(Get-Content -Path $script:HostsFilePath) $length = $content.Length $foundMatch = $false $dirty = $false for ($i = 0; $i -lt $length; $i++) { $parsed = Convert-EntryLine -Line $content[$i] if ($parsed.IPAddress -ne $IPAddress) { continue } $foundMatch = $true if ($parsed.HostNames -notcontains $HostName) { $parsed.HostNames += $HostName $content[$i] = Prepare-Line -ParsedLine $parsed $dirty = $true # Hosts files shouldn't strictly have the same IP address on multiple lines; should we just break here? # Or is it better to search for all matching lines in a malformed file, and modify all of them? } } if (-not $foundMatch) { $content += "$IPAddress $HostName" $dirty = $true } if ($dirty) { Set-Content -Path $script:HostsFilePath -Value $content } } function Remove-HostEntry { param ( [string] $IPAddress, [string] $HostName ) $content = @(Get-Content -Path $script:HostsFilePath) $length = $content.Length $placeholder = New-Object psobject $dirty = $false for ($i = 0; $i -lt $length; $i++) { $parsed = Convert-EntryLine -Line $content[$i] if ($parsed.IPAddress -ne $IPAddress) { continue } if ($parsed.HostNames -contains $HostName) { $dirty = $true if ($parsed.HostNames.Count -eq 1) { # We're removing the only HostName from this line; just remove the whole line $content[$i] = $placeholder } else { $parsed.HostNames = $parsed.HostNames -ne $HostName $content[$i] = Prepare-Line -ParsedLine $parsed } } } if ($dirty) { $content = $content -ne $placeholder Set-Content -Path $script:HostsFilePath -Value $content } } function Convert-EntryLine { param ([string] $Line) $indent = '' $ipAddress = '' $hostNames = @() $comment = '' $regex = '^' + '(?<indent>\s*)' + '(?<IPAddress>\S+)' + '(?:' + '\s+' + '(?<HostNames>[^#]*)' + '(?:#\s*(?<comment>.*))?' + ')?' + '\s*' + '$' if ($line -match $regex) { $indent = $matches['indent'] $ipAddress = $matches['ipAddress'] $hostNames = $matches['hostNames'] -split '\s+' -match '\S' $comment = $matches['comment'] } return [pscustomobject] @{ Indent = $indent IPAddress = $IPAddress HostNames = $HostNames Comment = $comment } } function Prepare-Line { param ([object] $ParsedLine) if ($ParsedLine.Comment) { $comment = " # $($ParsedLine.Comment)" } else { $comment = '' } return '{0}{1} {2}{3}' -f $ParsedLine.Indent, $ParsedLine.IPAddress, ($ParsedLine.HostNames -join ' '), $comment } Export-ModuleMember -Function Test-TargetResource,Set-TargetResource,Get-TargetResource |