Public/Import-DNSRecords.ps1

## Import records to AWS
## This is a painful function
## Return Errors if there is some
function Import-DNSRecords {

  [CmdletBinding()]
  param(
      [Parameter(Mandatory)]
      [Array]$NewZones,
      [Parameter(Mandatory, ValueFromPipeline)]
      [Array]$Records,
      [Parameter()]
      [Bool]$DryRun = $False,
      [Parameter()]
      ## Verbosity is 0 least verbose, 2 most verbose
      [ValidateSet(0, 1, 2)]
      [Int32]$Verbosity = 0,
      [Parameter()]
      [String]$OnlyZone ## If this is set, only one zone will be done

  )

  $Errors = @()

  #Global Change is our Array of change we'll submit
  $GlobalChanges = @()

  if (![string]::IsNullOrEmpty($OnlyZone)) {
      Write-Log -DV 0 -V $Verbosity -Message "OnlyZone is defined, will only process records for $OnlyZone"
      
      $OnlyZ = $NewZones | Where {$_.Name -like $OnlyZone}
      
      if ($OnlyZ -eq $Null) {
          Write-Error -Message "Could not find zone $OnlyZone in list of zones, aborting"
          return
      }

      $NewZones = @($OnlyZ)
  }


  $i = 0
  foreach ($z in $NewZones) {
      $PercentComplete = [math]::Round(($i/$NewZones.Count*100))
      $Name = $z.Name

      Write-Progress -Id 0 -Activity "Adding Records, now at $Name" -Status "$PercentComplete% Complete:"  -PercentComplete ($i/$NewZones.Count*100)
  
      
      $ZoneRecords = $Records | Where {$_.ZoneName -eq $z.Name} ##Get all corresponding DNS Records


      $changes = @() ##Changes for this specific zone



      $ix = 0


      ### Here we process MXRecords
      ### They have a particularity, they need to be all bundled in one request
      ### That's what we'll do here
      ### Keep in mind this doesn't support subdomain MXRecords Parsing
      if ($ZoneRecords.RecordType -contains "MX") {
          Write-Log -DV 2 -V $Verbosity -Message ("Processing MX Records for $Name")

          $PercentCompletex = [math]::Round(($ix/$ZoneRecords.Count*100))
          Write-Progress -Id 1 -Activity "Processing MX records for $Name" -Status "$PercentCompletex% Complete:"  -PercentComplete ($ix/$ZoneRecords.Count*100) -ParentId 0
      
          ## Create the new change object
          $change = New-Object Amazon.Route53.Model.Change
          $change.Action = "CREATE"
          $change.ResourceRecordSet = New-Object Amazon.Route53.Model.ResourceRecordSet
          $change.ResourceRecordSet.Name = $z.Name
          $change.ResourceRecordSet.Type = "MX"
          $change.ResourceRecordSet.TTL = 3600
      
          foreach ($r in ($ZoneRecords | Where {($_.RecordType -eq "MX") -and (($_.HostName -eq "@") -or ($_.HostName -eq $_.ZoneName))})) {
          
              ## Here we parse the name
              if ($r.HostName -eq "@") {
                  $r.HostName = $r.ZoneName
              } else {
          
                  $r.HostName = $r.HostName + "." + $r.ZoneName
      
              }

              $MXValue = [string]$r.MXPreference + " " + $r.Value

              if ($change.ResourceRecordSet.ResourceRecords.Value -Contains $MXValue) { # We check if the record is already here, if it is we do nada

                  Write-Log -DV 1 -V $Verbosity -Message ("Already contains $MXValue for $Name")
              } else {
                  
                  # Otherwise we go ahead and add it

                  $change.ResourceRecordSet.ResourceRecords.Add(@{Value=$MXValue})
                  Write-Log -DV 2 -V $Verbosity -Message ("Added $MXValue for $Name")
              }
      
          }

      
          $changes = $changes + $change
          $ix++
      } else {
      
          Write-Log -DV 1 -V $Verbosity -Message "$Name has no MX Records"
      
      }


      Write-Log -DV 2 -V $Verbosity -Message ("Done with MX for $Name")


      ## This marks the end of MX Records



      ## Not MX Records

      $Processed = @()


      #Pre parse hostnames
      #We have to do this out of the main block otherwise duplicate finding fucks up, I scracthed my head for hours on this one
      foreach ($r in ($ZoneRecords | Where {($_.RecordType -ne "MX") -and ($Processed -notcontains $_.GUID)})) {
          
         

          if (($r.HostName -eq "@") -or ($r.HostName -eq $Null) -or ($r.HostName -eq "")) {
              $r.HostName = $r.ZoneName
          } else {
              if ($r.RecordType -eq "CNAME" -and (($r.HostName -like ("*"+$r.ZoneName)))) {
              
                  #This handles weirdly pre-parsed cnames...
              
              } else {
              
                  $r.HostName = $r.HostName + "." + $r.ZoneName
              }
      
          }

      
      }

      foreach ($r in ($ZoneRecords | Where {($_.RecordType -ne "MX") -and ($Processed -notcontains $_.GUID)})) {

          if ($Processed -contains $r.GUID) {
          #donothing
          
          } else {


              Write-Log -DV 2 -V $Verbosity -Message ("Processing " + $r.HostName + " of type " + $r.RecordType + " for $Name")
      
              $PercentCompletex = [math]::Round(($ix/$ZoneRecords.Count*100))
              Write-Progress -Id 1 -Activity "Processing records for $Name" -Status "$PercentCompletex% Complete:"  -PercentComplete ($ix/$ZoneRecords.Count*100) -ParentId 0
              $change = New-Object Amazon.Route53.Model.Change
              $change.Action = "CREATE"
              $change.ResourceRecordSet = New-Object Amazon.Route53.Model.ResourceRecordSet
      

              ## Here we parse the records and manipulate them so they fit in amazon


              ##Write-Host $r.HostName

              if (($r.RecordType -eq "A") -or ($r.RecordType -eq "AAAA") -or ($r.RecordType -eq "TXT") -or ($r.RecordType -eq "CNAME")) {
                  $change.ResourceRecordSet.Name = $r.HostName
                  $change.ResourceRecordSet.Type = $r.RecordType
                  $change.ResourceRecordSet.TTL = $r.RecordTTL


                  Write-Log -DV 2 -V $Verbosity -Message ("Value is " + $r.Value + " for $Name")
              

                  if (($ZoneRecords | Where {($_.HostName -eq $r.HostName) -and ($_.RecordType -eq $r.RecordType) -and (($_.RecordType -ne "MX") -and ($_.RecordType -ne "SRV")) -and ($_.GUID -ne $r.GUID)}) -ne $Null) {
                      $Duplicate = ($ZoneRecords | Where {($_.HostName -eq $r.HostName) -and ($_.RecordType -eq $r.RecordType) -and (($_.RecordType -ne "MX") -and ($_.RecordType -ne "SRV")) -and ($_.GUID -ne $r.GUID)})


                      foreach ($d in $Duplicate) {
                          #Write-Host $d.Value
                          if ($d.RecordType -eq "TXT") {
                              
                              $change.ResourceRecordSet.ResourceRecords.Add(@{Value='"'+($d.Value)+'"'})
                          } elseif ($d.RecordType -eq "CNAME") {
                              ##Throwaway the weirdly duplicated CNAME
                              ##Not sure why this happens but oh well
                          } else {
                          
                              $change.ResourceRecordSet.ResourceRecords.Add(@{Value=($d.Value)})
                          }
                          $Processed = $Processed + $d.GUID
                          #Write-Host $d.GUID
                  
                      }
                      $Processed = $Processed + $r.GUID


                      if ($r.RecordType -eq "TXT") {
                              
                          $change.ResourceRecordSet.ResourceRecords.Add(@{Value='"'+($r.Value)+'"'})
                      } else {
                          
                          $change.ResourceRecordSet.ResourceRecords.Add(@{Value=($r.Value)})
                      }

                      Write-Log -DV 1 -V $Verbosity -Message "Duplicate found on $name"


                  } else {
                  
                      if ($r.RecordType -eq "TXT") {
                              
                          $change.ResourceRecordSet.ResourceRecords.Add(@{Value='"'+($r.Value)+'"'})
                      } else {
                          
                          $change.ResourceRecordSet.ResourceRecords.Add(@{Value=($r.Value)})
                      }

                  }

              } elseif ($r.RecordType -eq "SRV") {
                  $Processed = $Processed + $r.GUID 
                  $change.ResourceRecordSet.Name = $r.HostName
                  $change.ResourceRecordSet.Type = $r.RecordType
                  $change.ResourceRecordSet.TTL = $r.RecordTTL
          
                  $change.ResourceRecordSet.ResourceRecords.Add(@{Value=($r.Value)})
              
                  Write-Log -DV 2 -V $Verbosity -Message ("Value is " + $r.Value + " for $Name")
          
              }

              $changes = $changes + $change

              $ix ++
          }
      }

      ### EXECUTION BLOCK
      ### This is where we send changes to Route53 if it's not a dry run
      ### We also collect Errors
      if (!$DryRun) {

          Write-Log -DV 0 -V $Verbosity -Message "Pushing changes for $Name"

          
          if ($changes.Count -gt 0) {
              
              #Only run if there are actually changes

              try {
                  Edit-R53ResourceRecordSet -HostedZoneId $z.Id -Region us-east-1 -ChangeBatch_Comment ("AutoChanges for $Name at " + (Get-Date).DateTime) -ChangeBatch_Change $changes
              } catch {
                  
                  #We can catch exceptions here
                  
                  [pscustomobject]$ErrorObject = @{
                      Zone = $Name
                      Changes = $Changes
                      Exception = $_.Exception 
                  }
                  $Errors = $Errors + $ErrorObject
              }
          } else {

              #Just throw an error that it's empty

              [pscustomobject]$ErrorObject = @{
                  Zone = $Name
                  Changes = $Changes
                  Exception = "No changes, ZONE IS EMPTY"
              }
              $Errors = $Errors + $ErrorObject

          }

      } else {
          
          Write-Log -DV 1 -V $Verbosity -Message "Did not push changes for $Name, DRY RUN"
      
      }


      $GlobalChanges = $GlobalChanges + $changes

  


      $i++
  }

  
  return $Errors



}