Setting DNS on servers via Invoke-Command

We’re updating our AD and as part of that I’m trying to update the DNS settings for the member servers but it’s not working as I’m expecting.

There are two primary issues:
The first and most pressing is that it’s not actually updating the DNS settings for the servers.
As far as I can tell the Invoke-Command is getting all the variables it needs from the start of the script, and it says it finds the old DNS settings and is setting the new addresses, but when looking at the server nothing has changed.
However if I just run:

invoke-command -ComputerName srv01 {Set-DnsClientServerAddress -InterfaceIndex 2 -ServerAddresses ('192.168.0.1', '192.168.0.2', '192.168.0.3') -ErrorAction Stop}

from the same client and terminal from which I run the script the settings are updated instantly.

The second problem is that the Status and Error CSV-files are not populated.
I expect that the problem there is that the Invoke-Command does not return the $StatusLog and $ErrorLog variables back to the main script to be processed.
I thought maybe I’d solved it by adding them both to the -ArgumentList and adding the $Using: keyword to the StatusLog and ErrorLog assignments in the Invoke-Command block, but I got the following error:

At [FILEPATH]:117 char:11
+           $Using:StatusLog += [PSCustomObject]@{
+           ~~~~~~~~~~~~~~~~
The assignment expression is not valid. The input to an assignment operator must be an object that is able to accept assignments, such as a variable or a property.

The two CSV-files are created, but they are empty.

Here’s the sanitized version of the script.

#Requires -version 5.1

[CmdLetBinding()]
Param (
  [Parameter(Mandatory=$false)]
  [ValidateSet('AD1','AD2')]
  [string]$AD = 'AD1'
)

switch ($AD) {
  'AD1' {
    $AD = 'AD1'
    $DC = 'ad1.domain.local'
    $SB = 'OU=Servers,DC=ad1,DC=domain,DC=local'
    $DNS = "('192.168.0.1', '192.168.0.2', '192.168.0.3')"
    $OldDNS = '192\.168\.0\.1[1-3]'
    break
  }
  'AD2' {
    $AD = 'AD2'
    $DC = 'ad1.domain.local'
    $SB = 'OU=Servers,DC=ad2,DC=domain,DC=local'
    $DNS = "(192.168.1.1, 192.168.1.2, 192.168.1.3)"
    $OldDNS = '192\.168\.1\.1[1-3]'
    break
  }
  default {
    Write-Warning -Message 'No valid domain chosen. Run script again to try again'
    exit
  }
}

$CsvPath    = 'C:\Temp'
$StatusCSV  = Join-Path -Path $CSVPath -ChildPath "$(Get-Date -Format yyyyMMdd)-$($AD)-DnsClientStatus.csv"
$ErrorCSV   = Join-Path -Path $CsvPath -ChildPath "$(Get-Date -Format yyyyMMdd)-$($AD)-DnsClientServerError.csv"
$ErrorLog   = @()
$StatusLog  = @()

$Servers = Get-ADComputer -Server $DC -Filter {(OperatingSystem -Like '*server*') -and (enabled -eq 'true')} -SearchBase $SB -Properties Name,OperatingSystem,OperatingSystemVersion,IPv4Address |
  Sort-Object -Property OperatingSystem,Name |
  Select-Object -Property Name,OperatingSystem,OperatingSystemVersion,IPv4Address

foreach ($Server in $Servers) {
  # Test if the server is online and has WinRM enabled.
  $pingtest = Test-Connection -ComputerName $Server.Name -Quiet -Count 1 -ErrorAction SilentlyContinue
  $winrmtest = Test-WSMan -ComputerName $Server.Name -ErrorAction SilentlyContinue

  if ($pingtest -and $winrmtest) {
    Write-Verbose "Working on server: $($Server.Name)"
    Invoke-Command -ComputerName $Server.Name -ArgumentList $Server, $DNS, $OldDNS -ScriptBlock {
      $ifIndex = Get-NetRoute -DestinationPrefix '0.0.0.0/0' |
        Sort-Object -Property { $_.InterfaceMetric + $_.RouteMetric } |
        Select-Object -First 1 |
        Select-Object -ExpandProperty InterfaceIndex

      $currentDNS = (Get-DnsClientServerAddress -InterfaceIndex $ifIndex).ServerAddresses

      if ($currentDNS -match $Using:OldDNS) {
        Write-Verbose "Old DNS settings found on $($Using:Server.Name)"
        Write-Verbose "Setting new DNS on $($Using:Server.Name)"
        try {
          Set-DnsClientServerAddress -InterfaceIndex $ifIndex -ServerAddresses $Using:DNS -Validate -ErrorAction Stop
          $StatusLog += [PSCustomObject]@{
            DataTime  = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
            Server    = $Using:Server.Name
            IP        = $Using:Server.IPv4Address
            Message   = "Set new DNS"
          }
        }
        catch {
          Write-Warning "Failed to set new DNS on $($Using:Server.Name)"
          $ErrorLog += [PSCustomObject]@{
            DateTime  = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
            Server    = $Using:Server.Name
            IP        = $Using:Server.IPv4Address
            Error     = 'Failed to set new DNS'
          }
        }
      }
      else {
        Write-Warning "Unexpected DNS settings found on $($Using:Server.Name). Needs to be checked"
        $ErrorLog += [PSCustomObject]@{
          DateTime  = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
          Server    = $Using:Server.Name
          IP        = $Using:Server.IPv4Address
          Error     = "Unexpected DNS settings"
        }
      }
    }
  }
  else {
    Write-Warning "$($Server.Name) is offline"
    $ErrorLog += [PSCustomObject]@{
          DateTime  = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
          Server    = $Server.Name
          IP        = $Server.IPv4Address
          Error     = 'Server is offline'
    }
  }
}

$StatusLog | Export-Csv -Path $StatusCSV -Encoding UTF8 -NoTypeInformation -Delimiter ';'
$ErrorLog | Export-Csv -Path $ErrorCSV -Encoding UTF8 -NoTypeInformation -Delimiter ';'

I think I solved my primary problem.
If I change the line:

$DNS = "('192.168.0.1', '192.168.0.2', '192.168.0.3')"

to:

$DNS = @('192.168.0.1', '192.168.0.2', '192.168.0.3')

Making it an array instead of a long string, the actual functionality of the script seems to be resolved. At least the one test-server I’m running it against now has the correct DNS servers in place.

However, I’m still no closer to figuring out how I can get the reporting log-variables out of the Invoke-Command and back to the primary script so I can actually see how a full run of the script went.
Any help with that would be appreciated!

I think I managed to fix the logging for this now too.
It may not be the perfect way of getting this done, but it seems to work.
I assign the variable $IC to the Invoke-Command block:

$IC = Invoke-Command -ComputerName $Server.Name -ArgumentList $Server, $DNS, $OldDNS -ScriptBlock {

Inside the scriptblock I replace the PSCustomObject $ErrorLog/$StatusLog blocks with these lines:

$StatusCode = 'DnsSetSuccess'

$StatusCode = 'DnsSetFailure'

$StatusCode = 'DnsSetUnexpected'

and I explicitly return the $StatusCode variable at the end of the Invoke-Command block:

return $StatusCode

And finally after the Invoke-Command block I do:

if ($IC -match 'DnsSetSuccess') {
      $StatusLog += [PSCustomObject]@{
        DataTime  = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Server    = $Server.Name
        IP        = $Server.IPv4Address
        Message   = "Set new DNS"
      }
    }
    elseif ($IC -match 'DnsSetFailure') {
      $ErrorLog += [PSCustomObject]@{
        DateTime  = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Server    = $Server.Name
        IP        = $Server.IPv4Address
        Error     = 'Failed to set new DNS'
      }
    }
    elseif ($IC -match 'DnsSetUnexpected') {
      $ErrorLog += [PSCustomObject]@{
        DateTime  = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Server    = $Server.Name
        IP        = $Server.IPv4Address
        Error     = "Unexpected DNS settings"
      }
    }
    else {
      $ErrorLog += [PSCustomObject]@{
        DateTime  = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Server    = $Server.Name
        IP        = $Server.IPv4Address
        Error     = "Something else happened"
      }
    }

This also moves a bit of processing from the servers to the client running the script.

If anyone has a better way of doing this, I’m all ears. But now I have functional logging on this script.