HostsFileManagement.psm1

#Generated at 07/22/2019 08:59:41 by Stephane van Gulick

Enum HostsEntryType{
  Entry
  Comment
  BlankLine
}

Class HostsFile {
    [string]$Path
    [String]$ComputerName
    Hidden [HostsEntry[]]$Entries
    Hidden [int]$LogRotation = 4
    
    ## Default Constructor
    HostsFile(){

      $This.Path ="\\$ENV:Computername\admin$\System32\drivers\etc\hosts"
      $This.ComputerName = $ENV:Computername

    }
    
    ## Constructor that accepts a string (preferrably a computer name)
    HostsFile([String]$ComputerName){
    
      If ( Test-Connection -ComputerName $ComputerName -Quiet -Count 2 ) {
        $This.Path = "\\$Computername\admin$\System32\drivers\etc\hosts"
        $This.ComputerName = $Computername
      } Else {
        Throw "Could not reach the computer $($ComputerName)"
      }

    }
    
    ## Constructor that accept a path as a file
    HostsFile([System.IO.FileSystemInfo]$Path){

      $This.Path = $Path.FullName

    }
    
    ## Method to read the content of the hosts file
    [void]ReadHostsFileContent(){

      ## Local variables
      $Array = New-Object System.Collections.ArrayList #@()
      $HostsData = Get-Content $This.Path -Encoding Ascii
      
      ## Loop through the content of the file
      Foreach ( $Line in $HostsData ) {
        $Array +=[HostsEntry]::New($Line)
      }

      ## If Array exists
      If ( $array ){
        $This.Entries = $Array
      }

    }
    
    ## Method to get file entries
    [array]GetEntries(){

      ## Return entries
      If ( $This.Entries ) {
        return $This.Entries
      } Else {
        Throw "No entries present. Load a Hosts file first using the 'ReadHostsFileContent()' method, or use the add() method to add entries to the Hosts file that seems to be empty."
      }

    }
    
    ## Method to add Entries
    [void]AddHostsEntry([HostsEntry[]]$Entries){
      
      ## Local Variables
      $Added = $False
      [int]$Count = 0

      ## Loop through Entries
      Foreach ( $Entry in $Entries ) {
        ## If current entry is not already present is the file, add it
        If ( ($This.entries.ipaddress.IPAddressToString -notcontains $Entry.Ipaddress.IPAddressToString) ) {
          $Added = $True
          $Count++
          $This.entries += $Entry
        } Else {
          Write-Warning "The IpAddress $($Entry.ipaddress.IPAddressToString) is already present in the Hosts file."
        }
  
      }

      ## Write Verbose
      If ( $Added ) {
        Write-Verbose "Added: $($Count) entries to $($This.path). Call Set() to write to file."
      }
      
    }

    ## Method to Set LogRotation Value
    [Void]SetLogRotation([Int]$value){

      If ( $value -in 1..100 ) {
        $This.LogRotation = $value
      } Else {
        Throw "LogRotation must a int between 1 and 100 ..."
      }

    }
    
    ## Method to remove Entries in the Hosts File
    [void]RemoveHostsEntry([HostsEntry[]]$Entries){

      ## Easier to work with an arraylist
      $ArrayListofThisEntries = New-Object -TypeName System.Collections.ArrayList

      ## Fill ArrayListofThisEntries with current entries of the [HostsFile]
      Write-Verbose "Creating an arraylist of this.entries ... easier to work with ..."
      Foreach ( $CurrEntry in $This.Entries ) { $ArrayListofThisEntries.add($CurrEntry) | Out-Null }

      ## Remove entries passed to the method from the arraylist
      Foreach ( $Entry in $Entries ) {
        Write-Verbose "Removing the current entry: $($Entry.IpAddress) ..."
        $ArrayListofThisEntries.Remove($Entry)
      }

      ## Push ArrayListofThisEntries This.Entries
      Write-Verbose "Pushing updated arraylist to this.entries ..."
      $This.Entries = $ArrayListofThisEntries

      ## Call Set Method, will backup current
      Write-Verbose "Applying changes by calling this.set ..."
      $This.Set()

    }
    
    ## Backup Method
    [void]Backup([System.IO.DirectoryInfo]$BackupFolder){
      
      ## Get All Hosts.bak files in the BackupFolder path
      $BackupItems = Get-ChildItem -Path $BackupFolder.FullName -Filter "*$($This.ComputerName)_Hosts.bak" | Sort-Object -Property CreationTime
      
      ## Check if the number of correspoing backup files is equal or greater than LogRotation Property
      If ( $BackupItems.Count -ge $This.LogRotation ) {
        #Remove the oldest Backup file
        Write-Verbose "LogRotation set to maximum $($This.LogRotation+1) backups. Deleting oldest backup $($BackupItems[0].Name)"
        $BackupItems[0].Delete()
      }
      
      ## Building backup file FullPath
      $FileStamp = Get-Date -Format 'yyyyMMdd-HHmmss'
      $Leaf = $FileStamp + "_" + $This.ComputerName + "_Hosts.bak"
      $BackupFullPath = Join-Path -Path $BackupFolder.FullName -ChildPath $Leaf

      ## Copying the backup file to the destination path
      Try {
        Copy-Item -Path $This.Path -Destination $BackupFullPath -ErrorAction stop
        Write-Verbose "Hosts file backup -> $($BackupFullPath)"
      } Catch {     
        Write-Warning "$_"
      }
  
    }
    
    ## Backup Method
    [void]Backup(){
  
      ## Get All Hosts.bak files in the default Path
      $BackupItems = Get-ChildItem -Path (split-Path -Path $This.Path -Parent) -Filter "*Hosts.bak" | Sort-Object CreationTime
      
      ## Check if the number of correspoing backup files is equal or greater than LogRotation Property
      If ($BackupItems.count -gt $This.logRotation){
        ## Remove the oldest Backup file
        Write-Verbose "LogRotation set to maximum $($This.LogRotation+1) backups. Deleting oldest backup $($BackupItems[0].Name)"
        $BackupItems[0].Delete()
      }
      
      ## Building backup file FullPath
      $FileStamp = Get-Date -Format 'yyyyMMdd-HHmmss'
      $BackupPath = Split-Path -Parent $This.Path
      $Leaf = $FileStamp + "_" + "Hosts.bak"
      $BackupFullPath = Join-Path -Path $backupPath -ChildPath $Leaf

      ## Copying the backup file to the destination path
      Try {
        Copy-Item -Path $This.Path -Destination $BackupFullPath -ErrorAction stop
        Write-Verbose "Hosts file backup -> $($BackupFullPath)"
      } Catch {   
        Write-Warning "$_"
      }

    }

    <#
    [void]Set(){
       
      if($This.Entries){
        $This.Backup()
        #write-warning "Waiting 3 seconds"
        #Start-Sleep -Seconds 3
        #Set-Content -Value "" -Path $This.Path -Encoding Ascii
   
       ("") | >> $This.Path -Encoding Ascii
        #$This.AddHostsEntry($This.Entries)
         
         
   
        foreach ($entry in $This.Entries){
          [string]$FullLine = ""
          #start-sleep -Seconds 1
          switch($entry.EntryType){
            "Comment"{
                    
              if (![string]::IsNullOrWhiteSpace($entry.Ipaddress.IPAddressToString)){
   
                $FullLine += $entry.Ipaddress.IPAddressToString
   
              }
   
              if (![string]::IsNullOrWhiteSpace($entry.HostName)){
   
                $FullLine += "`t`t" + $entry.hostname
   
              }
   
              if (![string]::IsNullOrWhiteSpace($entry.FullQuallifiedName)){
                $FullLine += "`t`t" + $entry.FullQuallifiedName
   
              }
   
              if (![string]::IsNullOrWhiteSpace($entry.Description)){
                if ($entry.Ipaddress.IpAddressToString){
                  $FullLine += "`t`t" + "#" + $entry.Description
                }else{
                  if ($fullLine -match '^#.+'){
                    $fullLine += "`t`t" + $entry.Description
                  }else{
                    $FullLine += $entry.Description
                  }
                }
                  
              }
   
              $fullLine = "#" + $FullLine
   
            ;Break}
            "BlankLine"{
              if ($entry.IsComment -eq $true){
                       
                $fullLine = "#"
              }else{
                #$fullLine = "`r`n"
                $fullLine = ""
              }
            ;Break}
            "Entry"{
              if (![string]::IsNullOrWhiteSpace($entry.Ipaddress.IPAddressToString)){
   
                $FullLine += $entry.Ipaddress.IPAddressToString
   
              }
   
              if (![string]::IsNullOrWhiteSpace($entry.HostName)){
   
                $FullLine += "`t`t" + $entry.hostname
   
              }
   
              if (![string]::IsNullOrWhiteSpace($entry.FullQuallifiedName)){
                $FullLine += "`t`t" + $entry.FullQuallifiedName
   
              }
   
              if (![string]::IsNullOrWhiteSpace($entry.Description)){
   
                $fullLine += "`t`t" + "# "+ $entry.Description
                   
                  
              }
            ;Break}#End Switch Entry
          }
   
   
         
          #$fullLine += "`r`n"
         
          try{
            write-verbose "Adding: $($fullLine) to $($This.path)"
            Start-Sleep -Milliseconds 70
            ($fullLine) | >> $This.Path -ErrorAction stop
             
          }catch{
            write-warning "$_"
             
          }
         
         
        }#endforeach
   
      }else{
        write-warning "No entries to set."
      }
    }
    #>


      ## Method to save the hosts file with changes
      [void]Set(){
      
      If ( $This.Entries ) {

        ## Create Backup
        $This.Backup()
        #write-warning "Waiting 3 seconds"
        #Start-Sleep -Seconds 3
        #Set-Content -Value "" -Path $This.Path -Encoding Ascii
        $stream = [System.IO.StreamWriter]::new($This.Path,$false)
       #("") | Set-Content -Path $This.Path -Encoding Ascii
       
        #$This.AddHostsEntry($This.Entries)
        
        Foreach ( $Entry in $This.Entries){
          [string]$FullLine = ""
          #start-sleep -Seconds 1
          switch($Entry.EntryType){
            "Comment"{
                   
              if (![string]::IsNullOrWhiteSpace($Entry.Ipaddress.IPAddressToString)){
  
                $FullLine += $Entry.Ipaddress.IPAddressToString
  
              }
  
              if (![string]::IsNullOrWhiteSpace($Entry.HostName)){
  
                $FullLine += "`t`t" + $Entry.hostname 
  
              }
  
              if (![string]::IsNullOrWhiteSpace($Entry.FullQuallifiedName)){
                $FullLine += "`t`t" + $Entry.FullQuallifiedName  
  
              }
  
              if (![string]::IsNullOrWhiteSpace($Entry.Description)){
                if ($Entry.Ipaddress.IpAddressToString){
                  $FullLine += "`t`t" + "#" + $Entry.Description
                }else{
                  if ($fullLine -match '^#.+'){
                    $fullLine += "`t`t" + $Entry.Description
                  }else{
                    $FullLine += $Entry.Description
                  }
                }
                 
              }
  
              $fullLine = "#" + $FullLine
  
            ;Break}
            "BlankLine"{
              if ($Entry.IsComment -eq $true){
                      
                $fullLine = "#"
              }else{
                #$fullLine = "`r`n"
                $fullLine = ""
              }
            ;Break}
            "Entry"{
              if (![string]::IsNullOrWhiteSpace($Entry.Ipaddress.IPAddressToString)){
  
                $FullLine += $Entry.Ipaddress.IPAddressToString
  
              }
  
              if (![string]::IsNullOrWhiteSpace($Entry.HostName)){
  
                $FullLine += "`t`t" + $Entry.hostname 
  
              }
  
              if (![string]::IsNullOrWhiteSpace($Entry.FullQuallifiedName)){
                $FullLine += "`t`t" + $Entry.FullQuallifiedName  
  
              }
  
              if (![string]::IsNullOrWhiteSpace($Entry.Description)){
  
                $fullLine += "`t`t" + "# "+ $Entry.Description
                  
                 
              }
            ;Break}#End Switch Entry
          }
  
  
        
          #$fullLine += "`r`n"
        
          try{
            write-verbose "Adding: $($fullLine) to $($This.path)"
            #Start-Sleep -Milliseconds 70
            $stream.WriteLine($FullLine)
            #$fullLine | Add-Content -Path $This.Path -ErrorAction stop
            
          }catch{
            write-warning "$_"
            
          }
        }#endforeach
  
        $stream.close()
  
      }else{
        write-warning "No entries to set."
      }
    }
  
  }
Class HostsEntry{
    [Ipaddress]$Ipaddress
    [string]$HostName
    [String]$FullQuallifiedName
    [String]$Description
    [HostsEntryType]$EntryType
    
    HostsEntry([IpAddress]$IpAddress,[String]$HostName,[String]$FQDN,[String]$Description,[HostsEntryType]$Type){
      
      if ($Type -eq [HostsEntryType]::BlankLine){
        throw "This constructor cannot be used to create a blank line. Use the constructor with the empty signature HostsEntry()."
      }
  
      $This.Ipaddress = $IpAddress
      $This.HostName = $HostName
      $This.FullQuallifiedName = $FQDN
      $This.Description = $Description
      $This.EntryType = $Type
    }
    
    ## Add description to constructor
    HostsEntry([IpAddress]$IpAddress,[String]$HostName,[String]$FQDN,[String]$Description){
      
      $This.Ipaddress = $IpAddress
      $This.HostName = $HostName
      $This.FullQuallifiedName = $FQDN
      $This.Description = $Description
    }
    
    ## Add description to constructor
    HostsEntry([String]$Comment,[HostsEntryType]$Type){
      
      If ( ![Hostsentrytype]::Comment ) {
        throw "This constructor can only be used woth the [HostsEntryType]::Comment property."
      }

      $This.Description = $Comment
      $This.EntryType = $Type
    }
    
    ## Hiddent Constructor
    hidden HostsEntry([string]$Line){

      ## Local variable
      $Type = [HostsEntry]::GetLineType($Line)

      ## Determine type, and populate properties
      Switch ($Type){
        "Comment" {
          $This.EntryType = $Type
          $SplittedLine = [regex]::Split($line, "\s+") #$line.Split(" ")
          If ( $SplittedLine[0] -match '^#\d{1,3}\.' ) {
            $This.Ipaddress = $SplittedLine[0].replace("#","")
            $This.HostName = $SplittedLine[1]
            $This.FullQuallifiedName = $SplittedLine[2]
            [string]$comment = ""

            If ( $SplittedLine.count -gt 3 ) {
              $i = 3
              
              While ( $i -ne $SplittedLine.count ) {
                If ( $SplittedLine[$i] -ne "" ) {
                  $Comment += $SplittedLine[$i] + " "
                  $i++
                } Else {
                  $i++
                }
              }
            }#end if splitted line gt 3

            If ( $Comment -match '^#.*' ) {
              $This.Description = $Comment.Replace("#","")
            } Else {
              $This.Description = $comment
            }
  
          } ElseIf ( $SplittedLine[0] -match '^#.*' ) {
            #$This.Ipaddress = [ipaddress]::None
            $This.HostName = [string]::Empty
            $This.FullQuallifiedName = [string]::Empty
            $This.Description = $line.Replace("#","")
          }

        ;Break}

        "BlankLine" {
          $This.EntryType = $Type
          $This.HostName = [string]::Empty
          $This.Description = [string]::Empty
          $This.FullQuallifiedName = [string]::Empty
        ;Break}

        "Entry" {
          $SplittedLine = [regex]::Split($line, "\s+") #$line.Split(" ")
          $This.Ipaddress = $SplittedLine[0]
          $This.HostName = $SplittedLine[1]
          $This.FullQuallifiedName = $SplittedLine[2]
     
          [string]$comment = ""

          If ( $SplittedLine.count -gt 3 ) {
            $i = 3
            While ($i -ne $SplittedLine.count) {
              If ( $SplittedLine[$i] -ne "" ) {
                $Comment += $SplittedLine[$i] + " "
                $i++
              } Else {
                $i++
              }
            }
  
            If ($comment -match '^#.*' ) {
              $This.Description = $comment.Replace("#","")
            } Else {
              $This.Description = $comment
            }
          }
  
        ;Break}

      }
    }
    
    ## Default Constructor
    HostsEntry(){

      $This.HostName = [String]::Empty
      $This.FullQuallifiedName = [String]::Empty
      $This.Description = [String]::Empty
      $This.EntryType = [HostsEntryType]::BlankLine
      
    }
  
    ## Method to get the type of each line: comment, blankline or entry
    [HostsEntryType] static hidden GetLineType([string]$Line){
      
      ## local variables
      $Type = ""

      ## Switch to analyze the current $line with regex
      Switch -Regex ($Line) {
        '^#'    { $Type = [HostsEntryType]::Comment;Break }
        '^\s*$' { $Type = [HostsEntryType]::BlankLine;Break }
        Default { $Type = [HostsEntryType]::Entry;Break }
      }

      return $Type

    }
  }
  
Function Get-HFMHostsfile {
    <#
    .SYNOPSIS
        Get the hosts file of the desired hostname.
    .DESCRIPTION
        Get the hostfile of the desired hostname.
        By default the localhost hosts file is fetched. You can specify a remote computer name.
    .EXAMPLE
        PS C:\> Get-HFMHostsfile
        Return a [HostsFile] object representing the local hosts file.
    .EXAMPLE
        PS C:\> Get-HFMHostsfile -Name Computer1
        Return a [HostsFile] object representing the hosts file of Computer1.
    .EXAMPLE
        PS C:\> "Computer1","Computer2" | Get-HFMHostsfile
        Return an array of [HostsFile] objects representing the hosts file of Computer1 and Computer2.
    .INPUTS
        Input String.
    .OUTPUTS
        Return [HostsFile] Object(s).
    .NOTES
        This cmdlet uses Class.HostsManagement classes, by @StephaneVG
        Fork hist project if you like it: https://github.com/Stephanevg/Class.HostsManagement
        Visit his site, and read his article a boute pratical use of PowerShell Classes: http://powershelldistrict.com/powershell-class/
    #>


    [CmdletBinding()]
    Param
    (
        [Alias("Name")]
        [Parameter(Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [String[]]$ComputerName
    )

    BEGIN{}

    PROCESS{
        If ( !$ComputerName ) {
            return [HostsFile]::New()
        } Else {
            Foreach ( $Computer in $ComputerName ) {
                Return [HostsFile]::New($Computer)
            }
        }
    }

    END{}
}
Function Get-HFMHostsFileContent {
    <#
    .SYNOPSIS
        Read the Host file content.
    .DESCRIPTION
        Read the Host file content. Take input from Get-HFMHostsFile.
    .EXAMPLE
        PS C:\> $a = Get-HFMHostsFile
        PS C:\> Get-HFMHostsFileContent -Path $a
        Use Get-HFMHostsFile to get a [HostsFile] object
        Use Get-HFMHostFileContent to return a [HostsEntry] object(s)
    .EXAMPLE
        PS C:\> Get-HFMHostsFile | Get-HFMHostsFileContent -ExcludeComments
        List HostsFile entrie, but exclude comments
    .INPUTS
        Input must be of type [HostsFile].
    .OUTPUTS
        Return [HostsEntry] Object(s).
    .NOTES
        This cmdlet uses Class.HostsManagement classes, by @StephaneVG
        Fork hist project if you like it: https://github.com/Stephanevg/Class.HostsManagement
        Visit his site, and read his article a boute pratical use of PowerShell Classes: http://powershelldistrict.com/powershell-class/
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$false,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [HostsFile[]]$Path,
        [Parameter(Mandatory=$False)]
        [Switch]$ExcludeComments = $False
    )

    BEGIN {}

    PROCESS {
        Foreach ( $HostPath in $Path ) {
            $HostPath.ReadHostsFileContent()
            If ( $ExcludeComments ) {
                return $($HostPath.GetEntries() | Where-ObJect EntryType -ne "Comment")
            } Else {
                return $HostPath.GetEntries()
            }
        }
    }

    END {}
}
Function New-HFMHostsFileEntry {
    <#
    .SYNOPSIS
        Create a new Hosts File Entry.
    .DESCRIPTION
        Create a new Hosts File Entry.
    .EXAMPLE
        PS C:\> $NewEntry = New-HFMHostsFileEntry -IpAddress "20.0.0.0" -HostName "toto" -FullyQualifiedName "toto.com" -Description "ahahha" -EntryType Comment
        Create a new comment entry.
    .INPUTS
        Mostly Strings.
    .OUTPUTS
        A [HostsEntry] object.
    .NOTES
        This cmdlet uses Class.HostsManagement classes, by @StephaneVG
        Fork hist project if you like it: https://github.com/Stephanevg/Class.HostsManagement
        Visit his site, and read his article a boute pratical use of PowerShell Classes: http://powershelldistrict.com/powershell-class/
    #>


    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$False)]
        [String]$IpAddress,
        [Parameter(Mandatory=$False)]
        [String]$HostName,
        [Parameter(Mandatory=$False)]
        [String]$FullyQualifiedName,
        [Parameter(Mandatory=$False)]
        [String]$Description,
        [ValidateSet("Entry","Comment","BlankLine")]
        [Parameter(Mandatory=$True)]
        [String]$EntryType
    )

    Switch ($EntryType) {
        BlankLine {
            return [HostsEntry]::New()
        }

        Comment {
            return [HostsEntry]::New($IpAddress,$HostName,$FullyQualifiedName,$Description,[HostsEntryType]::Comment)
        }

        Entry {
            return [HostsEntry]::New($IpAddress,$HostName,$FullyQualifiedName,$Description,[HostsEntryType]::Entry)
        }
    }
}
Function Remove-HFMHostsFileEntry {
    <#
    .SYNOPSIS
        Remove entry/entries from a Hosts File
    .DESCRIPTION
        Remove entry/entries from a Hosts File
    .EXAMPLE
        PS C:\> Get-HFMHostsFile | Remove-HFMHostsFileEntry -Entry "#5.0.0.0"
        Will remove any Comment line with ip 5.0.0.0
    .EXAMPLE
        PS C:\> $a = Get-HFMHostsFile
        PS C:\> $b = New-HFMHostsFileEntry -IpAddress "20.0.0.0" -EntryType Entry
        PS C:\> Remove-HFMHostsFileEntry -Path $a -Entry $b
        Will remove any Entry line With IpAdress starting with 20.0.0.0
    .EXAMPLE
        PS C:\> Get-HFMHostsFile | Remove-HFMHostsFileEntry -Entry "#5.0.0.0","20.0.0.0"
        Will remove any Comment line wich start with commented ipadress #5.0.0.0 or 20.0.0.0
    .INPUTS
        You must provide a valid path of type [HostsFile] and an entry of type [HostsEntry] or a [String] representing an ipaddress or a comment.
    .OUTPUTS
        -
    .NOTES
        This cmdlet uses Class.HostsManagement classes, by @StephaneVG
        Fork and star his project if you like it: https://github.com/Stephanevg/Class.HostsManagement
        Visit his site, and read his article a boute pratical use of PowerShell Classes: http://powershelldistrict.com/powershell-class/
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [HostsFile[]]$Path,
        [Parameter(Mandatory=$True)]
        ## Accepte string ou [HostEntry]
        [Object[]]$Entry
    )

    BEGIN{}

    PROCESS{
        Foreach ( $File in $Path ) {

            If ( $null -eq $File.Entries ) {
                $File.ReadHostsFileContent()
            }

            $ToBeRemoved = @()

            Switch ( $Entry ) {

                ({ $PSItem.GetType().FullName -eq 'System.String' }) {
                    If ( $PSItem -Match '^\s+$' ) { Throw "Entry can not be a string only made of spaces..."}
                    $HostsEntry = [HostsEntry]::New($PSItem)
                    ## Is Current Entry present in file.entries, add it to TobeRemoved Array
                    $File.entries | Where-Object { ($_.Ipaddress -eq $HostsEntry.Ipaddress) -and ($_.EntryType -eq $HostsEntry.EntryType)} | %{ $ToBeRemoved+=$_ }
                    Continue;
                }

                ({ $PSItem.GetType().FullName -eq 'HostsEntry'}) {
                    $HostsEntry = $PSitem
                    ## Is Current Entry present in file.entries, add it to TobeRemoved Array
                    $File.entries | Where-Object { ($PSItem.Iaddress -eq $HostsEntry.IpAddress) -and ($PSItem.EntryType -eq $HostsEntry.EntryType)} | %{ $ToBeRemoved+=$_ }
                    Continue;
                }

                Default { Throw "Entries must be of type System.String or HostsEntry"; Break }
            }

            ## Remove entries
            $File.RemoveHostsEntry($ToBeRemoved)
        }
    }

    END{}
}
Function Save-HFMHostFile{
    <#
    .SYNOPSIS
        Backup Hosts File.
    .DESCRIPTION
        Backup Hosts File. By default the backup file will be stored in the system32\drivers\etc folder.
        You can use the BackupFolder to specify override the default behavior of the cmdlet.
        Hosts file backups are prefixed with a timestamp, and the name of the current computer.
        20181010-185510_COMPUTER1_Hosts.Bak
    .EXAMPLE
        PS C:\> $a = Get-FHMHostsFile
        PS C:\> Save-HFMHostsFile -Path $a
        Creates a backup of your hosts file.
    .EXAMPLE
        PS C:\> Get-FHMHostsFile | Save-HFMHostFile -BackupFolder c:\temp
        Create a backup of your hosts file in the c:\temp folder. MaxLog
    .EXAMPLE
        PS C:\> "Computer1","Computer2" | Get-FHMHostsFile | Save-HFMHostsFile -BackupFolder c:\temp -MaxLogRotation 10
        Creates a backup of the named computer hosts files, in your c:\temp folder. Each computer can have a max of 10 .bak files.
    .INPUTS
        Input must be of type [HostsFile].
    .OUTPUTS
        File, extension .bak
    .NOTES
        Remember to run Powershell in elevated mode, if you must backup your current hosts file by using the following: Get-FHMHostsFile | Save-HFMHostFile
        Standard credential dont allow you to write inside the system32\drivers\etc folder.
    #>

    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [HostsFile[]]$Path,
        [Parameter(Mandatory=$False)]
        [System.IO.DirectoryInfo]$BackupFolder,
        [Parameter(Mandatory=$False)]
        [Int]$MaxLogRotation
    )

    BEGIN{}

    PROCESS{
        Foreach ( $HostPath in $Path ) {
            If ( $MaxLogRotation ) { $HostPath.LogRotation = $MaxLogRotation }
            If ( $BackupFolder ) {
                $HostPath.Backup($BackupFolder)
            } Else {
                $HostPath.Backup()
            }
        }
    }

    END{}
}

Function Set-HFMHostsFileEntry {
    <#
    .SYNOPSIS
        Add new entry/entries to hosts file.
    .DESCRIPTION
        Add new entry/entries to hosts file(s)
    .EXAMPLE
        PS C:\> $Host = Get-HFMHostsfile
        PS C:\> $Comment1 = New-HFMHostsFileEntry -IpAddress "20.0.0.0" -HostName "toto" -FullyQualifiedName "toto.com" -Description "ahahha" -EntryType Comment
        PS C:\> $Comment2 = New-HFMHostsFileEntry -IpAddress "21.0.0.0" -HostName "toto" -FullyQualifiedName "toto.com" -Description "ahahha" -EntryType Comment
        PS C:\> Set-HFMHostsFileEntry -Path $Host -Entries $Comment1,$Comment2
 
        Create 2 new [HostsEntry], and add them to the local hosts file.
        A backup is automatically created.
    .EXAMPLE
        PS C:\> $Comment1 = New-HFMHostsFileEntry -IpAddress "20.0.0.0" -HostName "toto" -FullyQualifiedName "toto.com" -Description "ahahha" -EntryType Comment
        PS C:\> "Computer1","Computer2" | Get-HFMHostsfile | Set-HFMHostsFileEntry -Entries $Comment1
 
        Create a new [HostsEntry], and add it to hostsfile on computer1, and computer2
    .INPUTS
        You must provide a valid path of type [HostsFile] and an entry of type [HostsEntry]
    .OUTPUTS
         
    .NOTES
        This cmdlet uses Class.HostsManagement classes, by @StephaneVG
        Fork hist project if you like it: https://github.com/Stephanevg/Class.HostsManagement
        Visit his site, and read his article a boute pratical use of PowerShell Classes: http://powershelldistrict.com/powershell-class/
    #>

    Param
    (
        [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
        [HostsFile[]]$Path,
        [Parameter(Mandatory=$True)]
        [HostsEntry[]]$Entries
    )

    BEGIN{}

    PROCESS{

        Foreach ( $File in $Path ) {
            $File.ReadHostsFileContent()
            $File.AddHostsEntry($Entries)
            $File.Set()
        }
    }

    END{}
}