Unlocking an account on every DC without forcing replication

Today I’m going to go over a script which will address an issue admins in a large network face : unlocking an account on DCs that spread across multiple WAN links WITHOUT FORCING A REPLICATION.

This script is fairly straight forward but does require the person running it have domain admin privileges and access to the AD PowerShell extensions.

The core of what is needed is a simple command : Unlock-ADAccount. We can start by laying out that command:

Unlock-ADAccount <user>

Since we are interested in doing this on all DCs we’ll include the option -Server. Obviously we can do it the hard way:

Unlock-ADAccount <user> -Server DC1
Unlock-ADAccount <user> -Server DC2
….

But that is hard work if you have a lot of DCs, say 50 or more, or if the DCs are changing. So let’s add something to the script to make it smarter:

$DCList = Get-ADComputer -Filter * -SearchBase ‘ou=Domain Controllers,dc=contoso,dc=com’
Foreach ($targetDC in $DCList.Name)
{
Unlock-ADAccount <user> -Server $targetDC
}

We’ve added a little brains here. The Get-ADComputer cmdlet is used to get the names of all the computers [-Filter *] in our Domain Controllers OU [-SearchBase ‘ou=Domain Controllers,dc=contoso,dc=com’]. We’re dumping the results of this command into the variable $DCList. The end result is that we wind up with an array where each item in the array has multiple properties. We’re interested in one, Name. The Foreach command essentially allows us to loop through all of the array values one at a time. We use another single-value variable, $targetDC, to hold the current DC name. In five lines we are able to do what might take a hundred or more.

So far so good. But what happens if a DC is offline or unreachable? The script blows up. We’ll add an option to our Unlock-ADAccount cmdlet to keep everything going along rather than just halting

$DCList = Get-ADComputer -Filter * -SearchBase ‘ou=Domain Controllers,dc=contoso,dc=com’
Foreach ($targetDC in $DCList.Name)
{
Unlock-ADAccount <user> -Server $targetDC -ErrorAction SilentlyContinue
}

Now the script will just roll on if it hits a downed DC. But it would be nice to know about that, right? So let’s add TRY-CATCH

$DCList = Get-ADComputer -Filter * -SearchBase ‘ou=Domain Controllers,dc=contoso,dc=com’
Foreach ($targetDC in $DCList.Name)
{
Try
{
Unlock-ADAccount <user> -Server $targetDC -ErrorAction SilentlyContinue
}
Catch
{
$errormsg = $targetDC + ” is down/not responding.”
Write-Host $errormsg -ForegroundColor white -BackgroundColor red
}
}

There. Now we get a message indicating a particular DC was down. The ForegroundColor option allows us to specify white as the text color, while the BackgroundColor option allows us to specify red as the text background, making it stand out in our shell window.

But if we’re doing that why not give ourselves a clue as to where we’re at by doing something similar in the Try section:

$DCList = Get-ADComputer -Filter * -SearchBase ‘ou=Domain Controllers,dc=contoso,dc=com’
Foreach ($targetDC in $DCList.Name)
{
Try
{
Unlock-ADAccount <user> -Server $targetDC -ErrorAction SilentlyContinue
Write-Host (“Completed on ” + $targetDC) -BackgroundColor DarkGreen
}
Catch
{
$errormsg = $targetDC + ” is down/not responding.”
Write-Host $errormsg -ForegroundColor white -BackgroundColor red
}
}

Great, but it’s kind of useless to have to adjust the script for each user. Let’s add an argument to our script and, before trying the script, check to make sure it isn’t empty.

$targetacct = $args[0]
if ($targetacct -ne $null)
{
$DClist = get-adcomputer -filter * -SearchBase ‘ou=domain controllers,dc=contoso,dc=com’ | Sort-Object name
Try
{
Get-ADUser $targetacct
Foreach ($targetDC in $DClist.Name)
{
“Processing ” + $targetacct + ” on DC ” + $targetDC | Out-Default
Try
{
Unlock-ADAccount $targetacct -Server $targetDC -ErrorAction SilentlyContinue | Out-Null
Write-Host (“Completed on ” + $targetDC) -BackgroundColor DarkGreen
}
Catch
{
$errormsg = $targetDC + ” is down/not responding.”
Write-Host $errormsg -ForegroundColor white -BackgroundColor Red
}
}
}
Catch
{
$errormsg = $targetacct + ” is an INVALID account. Check to see if it exists and that this is the SAM name.”
Write-Host $errormsg -ForegroundColor white -BackgroundColor Red
}
}
else
{
write-host “INVALID Parameters!”
Write-Host “USAGE: unlock.ps1 <USERNAME>”
}

Not only have we added the input from arguments (the first line), but in the second line we set up an IF-ELSE structure to ensure we have an actual value. [And yell at us when we don’t!] We also added an additional line [“Processing….”] so that we have an even better idea of what is being tried, not just whether it succeeded or not. There’s even a check [Try … Get-ADUser….] to see if the account actually exists and exit if it doesn’t.

Granted this is a linear script and can several minutes to run based on WAN links and DC load, but that is a lot better that forcing a replication across multiple small pipes.

Advertisements

Comments are closed.