Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

SCRIPT LIBRARY · POWERSHELL

Flushing the DNS Cache on One PC or Fifty

Clear the Windows DNS client cache locally or across a list of machines, and know when a flush will actually fix anything.

AT A GLANCEClear-DnsCache.ps1
What it does
Flushes the DNS client cache on this computer or a list of remote computers over PowerShell remoting, and reports how many entries were cached before and after.
Requires
  • Windows PowerShell 5.1 or PowerShell 7+
  • DnsClient module (built in on Windows 8 / Server 2012 and later; falls back to ipconfig otherwise)
  • PowerShell remoting (WinRM) enabled on remote targets
Permissions
Local admin on each target. Run it elevated when you flush your own machine.
Runs on
Windows 10/11, Windows Server 2016+
Tested
Parse-checked and dry-run with mocked cmdlets in PowerShell 7.4

Part 6 of the thread Spring cleaning for Windows PCs

"Have you tried flushing DNS?" is the IT version of "have you tried turning it off and on again." Sometimes it's exactly the fix. A record moved to a new IP, the old answer is still sitting in the client cache, and a flush gets you going again in two seconds. Other times it does nothing at all, because the stale answer isn't on the PC in the first place.

On a single machine, ipconfig /flushdns from an elevated prompt is all you need. The trouble starts when it's a whole lab, or every PC on a floor, right after a cutover. This script does the same job with Clear-DnsClientCache, fans out to as many computers as you give it, and tells you which ones actually got flushed and which ones never answered.

It also counts the cache before and after. That sounds like trivia, but it's a handy sanity check: if the "after" number isn't close to zero, you're probably looking at hosts file entries, which a flush can't remove.

Clear-DnsCache.ps1Download
<#
.SYNOPSIS
    Flushes the Windows DNS client cache on this computer or a list of remote computers.
.DESCRIPTION
    Runs Clear-DnsClientCache (falling back to ipconfig /flushdns where the DnsClient module
    isn't available) and reports how many entries were cached before and after. Remote
    computers are reached with Invoke-Command over PowerShell remoting, in parallel.
    One unreachable machine doesn't stop the rest. Supports -WhatIf.
.PARAMETER ComputerName
    One or more computers to flush. Defaults to this one. Accepts pipeline input, so
    Get-Content .\pcs.txt | .\Clear-DnsCache.ps1 works.
.PARAMETER Credential
    Credentials for the remote computers. Not needed if your current account is an admin there.
.PARAMETER ThrottleLimit
    How many remote computers to work on at once. Default: 32.
.EXAMPLE
    .\Clear-DnsCache.ps1
.EXAMPLE
    Get-Content .\lab-pcs.txt | .\Clear-DnsCache.ps1 -Credential (Get-Credential)
#>
[CmdletBinding(SupportsShouldProcess)]
param(
    [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Alias('CN', 'Name')]
    [ValidateNotNullOrEmpty()]
    [string[]]$ComputerName = $env:COMPUTERNAME,

    [pscredential]$Credential,

    [ValidateRange(1, 256)]
    [int]$ThrottleLimit = 32
)

begin {
    # This block runs on the target machine, local or remote.
    $flush = {
        $haveModule = [bool](Get-Command Clear-DnsClientCache -ErrorAction SilentlyContinue)
        $before = if ($haveModule) { @(Get-DnsClientCache -ErrorAction SilentlyContinue).Count } else { $null }

        if ($haveModule) {
            Clear-DnsClientCache
            $method = 'Clear-DnsClientCache'
        }
        else {
            $null = ipconfig.exe /flushdns
            if ($LASTEXITCODE -ne 0) { throw "ipconfig /flushdns exited with code $LASTEXITCODE" }
            $method = 'ipconfig /flushdns'
        }

        # Whatever is left is usually the hosts file, which gets reloaded straight back in.
        $after = if ($haveModule) { @(Get-DnsClientCache -ErrorAction SilentlyContinue).Count } else { $null }

        [pscustomobject]@{
            EntriesBefore = $before
            EntriesAfter  = $after
            Method        = $method
        }
    }

    $localNames = @('.', 'localhost', $env:COMPUTERNAME)
    $remote = [System.Collections.Generic.List[string]]::new()
}

process {
    foreach ($computer in $ComputerName) {
        if (-not $PSCmdlet.ShouldProcess($computer, 'Flush DNS client cache')) { continue }

        if ($localNames -contains $computer) {
            try {
                $r = & $flush
                [pscustomobject]@{
                    ComputerName  = $env:COMPUTERNAME
                    Status        = 'Flushed'
                    EntriesBefore = $r.EntriesBefore
                    EntriesAfter  = $r.EntriesAfter
                    Method        = $r.Method
                    Error         = $null
                }
            }
            catch {
                [pscustomobject]@{ ComputerName = $env:COMPUTERNAME; Status = 'Failed'; EntriesBefore = $null; EntriesAfter = $null; Method = $null; Error = $_.Exception.Message }
            }
        }
        else {
            $remote.Add($computer)
        }
    }
}

end {
    if ($remote.Count -eq 0) { return }

    Write-Verbose "Flushing $($remote.Count) remote computer(s), $ThrottleLimit at a time."
    $icm = @{
        ComputerName  = $remote
        ScriptBlock   = $flush
        ThrottleLimit = $ThrottleLimit
        ErrorAction   = 'SilentlyContinue'
        ErrorVariable = 'remoteErrors'
    }
    if ($Credential) { $icm.Credential = $Credential }

    $results = @(Invoke-Command @icm)

    foreach ($r in $results) {
        [pscustomobject]@{
            ComputerName  = $r.PSComputerName
            Status        = 'Flushed'
            EntriesBefore = $r.EntriesBefore
            EntriesAfter  = $r.EntriesAfter
            Method        = $r.Method
            Error         = $null
        }
    }

    # Anything that didn't answer gets its own row, with the error if we can match it up.
    $answered = @($results.PSComputerName)
    foreach ($computer in $remote | Where-Object { $answered -notcontains $_ }) {
        $err = $remoteErrors | Where-Object { "$($_.TargetObject)" -eq $computer -or "$($_.OriginInfo.PSComputerName)" -eq $computer } | Select-Object -First 1
        [pscustomobject]@{
            ComputerName  = $computer
            Status        = 'Failed'
            EntriesBefore = $null
            EntriesAfter  = $null
            Method        = $null
            Error         = if ($err) { $err.Exception.Message } else { 'No response (unreachable, or remoting not enabled)' }
        }
    }
}

Parameters

ParameterTypeDefaultWhat it's for
-ComputerNamestring[]$env:COMPUTERNAMEThe computers to flush. Takes pipeline input, so you can pipe a text file of names straight in. Local names (., localhost, or this PC's name) run directly without remoting.
-Credentialpscredential—An account with admin rights on the remote machines, if your current one doesn't have them.
-ThrottleLimitint32How many remote computers to hit at the same time.

Run it

Flush this machine. Run PowerShell as administrator first.

.\Clear-DnsCache.ps1

Flush every PC in a text file, one name per line.

Get-Content .\lab-pcs.txt | .\Clear-DnsCache.ps1

Every enabled workstation in an OU, then show only the ones that failed.

Get-ADComputer -Filter 'Enabled -eq $true' -SearchBase 'OU=Workstations,DC=contoso,DC=com' | .\Clear-DnsCache.ps1 | Where-Object Status -eq 'Failed'

See which machines it would touch, without touching them.

.\Clear-DnsCache.ps1 -ComputerName PC-0142, PC-0143, PC-0198 -WhatIf

What you'll see

Example outputvalues are illustrative
ComputerName Status  EntriesBefore EntriesAfter Method               Error
------------ ------  ------------- ------------ ------               -----
PC-0142      Flushed           214            3 Clear-DnsClientCache
PC-0143      Flushed            97            3 Clear-DnsClientCache
PC-0198      Failed                                                  Connecting to remote server PC-0198 failed: WinRM cannot complete the operation...

How it works

  1. Sort local from remote. If a name is this computer (or . or localhost), the script just runs the flush directly. Everything else is collected and sent out in one Invoke-Command call, which runs on up to 32 machines at once.
  2. Flush on the target. On each machine it counts Get-DnsClientCache, runs Clear-DnsClientCache, and counts again. If the DnsClient module isn't there (very old builds), it falls back to ipconfig /flushdns and checks the exit code.
  3. Account for the stragglers. Any computer that didn't send results back gets its own Failed row, with the remoting error attached when one can be matched up. You never have to wonder whether a machine was skipped or just quiet.
  4. Return objects. Filter, sort or export them like anything else. Export-Csv works fine if somebody wants proof the flush happened.

When a flush won't help

Worth knowing before you flush fifty machines and the problem is still there:

  • The stale answer is on the DNS server. Clients ask a DNS server, and that server caches too. If Resolve-DnsName app.contoso.com -Server <your DNS server> returns the old address, flushing clients is pointless until the server's cache clears. On a Windows DNS server, Clear-DnsServerCache does that.
  • The record hasn't replicated. In an AD-integrated zone, a change made on one DC can take a few minutes (or a lot longer across sites) to reach the others. Query each DNS server directly and see which ones have it.
  • The app caches on its own. Java apps, some agents, and anything with a long-lived connection pool may hold onto an IP until they're restarted.
  • The name resolves fine and it's still broken. Then it's not DNS. (Okay, it's usually DNS. But not always.)

Take it further

  • Check before you flush. Resolve-DnsName name -DnsOnly bypasses the local cache and hosts file, so you can see what the server actually says versus what the client has cached.
  • Pair it with a registration refresh. If the problem is a client's own record being wrong, ipconfig /registerdns (or Register-DnsClient) re-registers it. That's a different fix from flushing, and people mix them up constantly.
  • Run it after a cutover. Keep a list of the machines that talk to whatever you're moving, and flush them as the last step of the change.

Things that'll trip you up

  • The hosts file comes right back. Entries from C:\Windows\System32\drivers\etc\hosts are loaded into the cache and reloaded as soon as you flush. If a bad answer survives the flush, check the hosts file before anything else.
  • Browsers keep their own cache. Chrome and Edge cache DNS answers inside the browser for a short time. If the command line resolves correctly and the browser doesn't, close the browser or clear its host cache at chrome://net-internals/#dns (edge://net-internals/#dns in Edge).
  • Negative answers get cached too. If a name didn't exist when a PC first asked, Windows caches the "doesn't exist" answer for a while (up to 15 minutes by default). That's the classic "I just created the record and it still won't resolve" problem, and it's the one case where a flush reliably helps.
  • Remoting has to be on. Remote targets need WinRM enabled and reachable (TCP 5985 by default). A machine that's off, asleep, or firewalled shows up as Failed with the reason, and the rest of the batch carries on.