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.
- 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.
<#
.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
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-ComputerName | string[] | $env:COMPUTERNAME | The 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. |
-Credential | pscredential | — | An account with admin rights on the remote machines, if your current one doesn't have them. |
-ThrottleLimit | int | 32 | How many remote computers to hit at the same time. |
Run it
Flush this machine. Run PowerShell as administrator first.
.\Clear-DnsCache.ps1Flush every PC in a text file, one name per line.
Get-Content .\lab-pcs.txt | .\Clear-DnsCache.ps1Every 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 -WhatIfWhat you'll see
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
- Sort local from remote. If a name is this computer (or
.orlocalhost), the script just runs the flush directly. Everything else is collected and sent out in oneInvoke-Commandcall, which runs on up to 32 machines at once. - Flush on the target. On each machine it counts
Get-DnsClientCache, runsClear-DnsClientCache, and counts again. If the DnsClient module isn't there (very old builds), it falls back toipconfig /flushdnsand checks the exit code. - Account for the stragglers. Any computer that didn't send results back gets its own
Failedrow, with the remoting error attached when one can be matched up. You never have to wonder whether a machine was skipped or just quiet. - Return objects. Filter, sort or export them like anything else.
Export-Csvworks 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-DnsServerCachedoes 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 -DnsOnlybypasses 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(orRegister-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.