Update Host Files on Multiple Servers

$arecord='intranet'
$domain='contoso.com'
$serverIp='555.555.555.555'
$groupTag='# Accounting Servers'
$serversToEdit='Accounting1','Accounting2','Watermelon'
$commentOut=$false
function updateHostFiles{
    param(
        $appLocalIp, $orgName, $domain, $searchString, $servers,$commentOut=$false
    )
    # placing this supporting function for easy copy/paste
    function confirmation($content,$testValue="I confirm",$maxAttempts=3){
        $confirmed=$false;
        $attempts=0;        
        $content|write-host
        write-host "Please review this content for accuracy.`r`n"
        while ($attempts -le $maxAttempts){
            if($attempts++ -ge $maxAttempts){
                write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
                break;
                }
            $userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm";
            if ($userInput.ToLower() -ne $testValue.ToLower()){
                cls;
                $content|write-host
                write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again..`r`n"
                }else{
                    $confirmed=$true;
                    write-host "Confirmed!`r`n";
                    break;
                    }
            }
        return $confirmed;
    }
    function generateARecord{
        param(
            $newARecord,
            $domain,
            $ip
        )
        $url=$newARecord+'.'+$domain;
        $recordString="$ip`t$url";
        return $recordString;
    }    
    function updateHostRecord{    
        param(    
            $record,
            $insertAfter,
            $servers,
            $remove=$false
            )
        $hostFiles=$($servers|%{"\\$_\C$\windows\system32\drivers\etc\hosts"})
        $results=@{}
        #replaceLine -textContent $fileContent -match $url -updateLineContent $record -remove $remove
        function replaceLine($textContent,$match,$updateLineContent,$remove=$false){
            write-host "Search string: $match"
            $lineMatch = $textContent | Select-String "$match"
            $lineNumber = $lineMatch.LineNumber
            $lineContent = $lineMatch.Line
            $replaceWith=if($remove){if($lineContent[0] -eq '#'){$lineContent}else{"#$lineContent #record disabled $(get-date) by $(whoami)"}}                            
                            else{$updateLineContent}
            write-host "Replacing`t: $lineMatch`r`nWith`t: $replaceWith"            
            $textContent[$lineNumber-1]=$replaceWith
            return $textContent
        }
    
        function updateLocalHostRecord{
            param(    
            $record,
            $hostfile="c:\windows\system32\drivers\etc\hosts",
            $searchString=$false,
            $remove=$false
            )
     
            function insertAfterMatchString{
                param(
                    $content,
                    $searchString,
                    $insert,
                    $remove=$false
                )
                if($remove){$insert='#'+$insert}
                [int]$matchedIndex=.{$lines=[array]$content
                                        for ($i=0;$i -lt $lines.Count;$i++) {if($lines[$i] -like "$searchString*"){return $i}}
                                        }
                if($matchedIndex){
                    $nextAvailableSpaceIndex=.{for($i=$matchedIndex;$i -lt $content.Length;$i++){
                                                    $isEmpty=$content[$i] -match "^\s*$"
                                                    #$isEmpty=$content[$i].Trim().isEmpty()
                                                    if ($isEmpty){return $i}
                                                    }
                                                }
                    $content=$content[0..$($nextAvailableSpaceIndex-1)]+$insert+$content[$($nextAvailableSpaceIndex)..$($content.length-1)]
                    write-host "$insert will be inserted after $($content[$nextAvailableSpaceIndex-1])`r`n-----------------------`r`n"
                }else{
                    $content+=$insert
                    write-host "$searchString has not matched anything.";
                }
                return $content
            }
     
            $validPath=[System.IO.File]::Exists($hostfile) # Slightly faster than Test-Path for true positives but not true negatives
            if($validPath){
                $fileContent=Get-Content -Path $hostfile
                $url=.{[void]($record -match "([0-9A-Za-z_\.\-]*)$");$matches[1]}
                $entryExists=$fileContent|?{$_ -match "$url"}|select -First 1
                write-host "Entry exists: $entryExists"
                if(!$entryExists){
                    write-host "Search string: '$searchString'"
                    pause
                    if($searchString){
                        $fileContent=insertAfterMatchString -content $fileContent -searchString $searchString -insert $record $remove
                        }else{
                            $fileContent+=$(if($remove){'#'})+$record
                            }         
                    $confirmed=confirmation -content $fileContent
                    if ($confirmed){
                        $backupFile=$hostFile+"_backup"
                        try{
                            Rename-Item -Path $hostfile -NewName $backupFile -ErrorAction Stop
                            }
                            catch{
                                write-host "Unable to rename. Now trying to delete previous backup and retry"
                                Remove-item $backupFile -Force
                                Rename-Item -Path $hostfile -NewName $backupFile
                                }
                        # The try-catch method overcomes this error
                        #Rename-Item : Cannot create a file when that file already exists.
                        #At line:1 char:1
                        #+ Rename-Item -Path $hostfile -NewName $($hostFile+"_backup") -Force
                        #+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        #    + CategoryInfo          : WriteError: (C:\windows\system32\drivers\etc\hosts:String) [Rename-Item], IOException
                        #    + FullyQualifiedErrorId : RenameItemIOError,Microsoft.PowerShell.Commands.RenameItemCommand
                        $fileContent|set-Content $hostfile
                        $message="$hostfile updated successfully."
                        write-host $message -ForegroundColor Green
                        return $message
                        }else{
                            $message="$hostfile update cancelled."
                            write-host $message -ForegroundColor Red
                            return $message;
                            }
                    }
                else{
                    $fileContent=replaceLine -textContent $fileContent -match $url -updateLineContent $record -remove $remove
                    $confirmed=confirmation -content $fileContent
                    if ($confirmed){
                        $backupFile=$hostFile+"_backup"
                        try{
                            Rename-Item -Path $hostfile -NewName $backupFile -ErrorAction Stop
                            }
                            catch{
                                write-host "Unable to rename. Now trying to delete previous backup and retry"
                                Remove-item $backupFile -Force
                                Rename-Item -Path $hostfile -NewName $backupFile
                                }
                        # The try-catch method overcomes this error
                        #Rename-Item : Cannot create a file when that file already exists.
                        #At line:1 char:1
                        #+ Rename-Item -Path $hostfile -NewName $($hostFile+"_backup") -Force
                        #+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        #    + CategoryInfo          : WriteError: (C:\windows\system32\drivers\etc\hosts:String) [Rename-Item], IOException
                        #    + FullyQualifiedErrorId : RenameItemIOError,Microsoft.PowerShell.Commands.RenameItemCommand
                        $fileContent|set-Content $hostfile
                        $message="$hostfile updated successfully."
                        write-host $message -ForegroundColor Green
                        return $message
                        }else{
                            $message="$hostfile update cancelled."
                            write-host $message -ForegroundColor Red
                            return $message;
                            }
                    }
            }else{
                $message="Unable to access $hostfile. Action aborted.";
                write-warning $message
                return $message;
                }
        }
     
        for ($i=0;$i -lt $hostFiles.count; $i++){
            $hostfile=$hostFiles[$i]
            write-host "Processing $hostfile..."       
            $result=updateLocalHostRecord -record $record -hostfile $hostFile -searchString $insertAfter -remove $remove
            $results.Add($servers[$i],$result)
            }
        return $results
    }
    function updateHostRecordOnServers{
        param(
            $appLocalIp,
            $orgName,
            $domain,
            $searchString,
            $servers,
            $remove=$false
        )
        $regexIP = [regex] "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
        if ($appLocalIp -match $regexIP){
            $newRecord=generateARecord -newARecord $orgName -domain $domain -ip $appLocalIp
            $result=updateHostRecord -record $newRecord -insertAfter $searchString -servers $servers -remove $remove
            }else{
                $result="Unable to retrieve the app server private IP. Cannot update host records without that information."
                write-warning $result
                }
            return $result
    }
    return updateHostRecordOnServers $appLocalIp $orgName $domain $searchString $servers $commentOut
    
    #$regexIP = [regex] "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
    #$newRecord=generateARecord -newARecord $orgName -domain $domain -ip $appLocalIp
    #foreach ($server in $servers){
    #    if ($appLocalIp -match $regexIP){            
    #        #updateHostRecord -record $newRecord -insertAfter $searchString -servers $server -remove $remove                    
    #        invoke-command -ComputerName $server -credential $adminCredential -scriptblock {
    #            param ($updateHostRecord,$newRecord, $searchString, $server, $remove)
    #            [ScriptBlock]::Create($updateHostRecord).invoke($newRecord, $searchString, $server, $remove)
    #            } -Args ${function:updateHostRecord},$newRecord, $searchString, $server, $remove
    #        }
    #    else{
    #        write-host "Unable to retrieve the app server private IP. Cannot update host records without that information."
    #        }
    #}
}
# updateHostFiles $appLocalIp $orgName $domain $searchString $sourceServers $commentOut
function invokeUpdateHostFiles{
    param(
        $adminCredential,$serverLocalIp,$orgName, $domain, $searchString,$targetHosts,$remove=$false
    )
    $jobResults=start-job -credential $adminCredential -scriptblock {
        param ($updateHostFiles,$serverLocalIp, $orgName, $domain, $searchString, $targetHosts,$remove)
        [ScriptBlock]::Create($updateHostFiles).invoke($serverLocalIp, $orgName, $domain, $searchString, $targetHosts,$remove)
        } -Args ${function:updateHostFiles},$serverLocalIp, $orgName, $domain, $searchString, $targetHosts,$remove|Receive-Job -Wait
    return $jobResults
}
invokeUpdateHostFiles $adminCredential $serverIp $aRecord $domain $groupTag $serversToEdit $commentOut

Leave a Reply

Your email address will not be published. Required fields are marked *