Active Directory — Unlocking a User Account with PowerShell

As any SysAdmin knows, users periodically lock themselves out of their accounts, usually because they forgot a password or somehow mistyped it too many times. And after all, if it failed once, why not keep trying it? Unlocking that account is NOT something you do with Set-ADUser, unfortunately, because the PowerShell ActiveDirectory module has a special, single-purpose cmdlet - Unlock-ADAccount. Now, it doesn't take a whole script to run a single cmdlet, but sometimes there are good reasons to wrap a command in a more complicated script, and this is one I've had to. Primarily because I work on at least three different domains that are unrelated - my home domain, my "work" domain, and my testlab domain. With this script, I can also take advantage of an encrypted password for one of those domains, stored securely on my main desktop's hard disk. Plus, I've added in a bit of code to go grab the current holder of the FSMO PDCEmulator role, since I'd prefer to unlock the account directly against that PDC.

Also, to make life easier, and allow us to stick this in the middle of a pipeline when we need to, we'll add the ability to handle parameters from the pipeline. We do that in the Param section:

[CmdletBinding()]
Param(
     [Parameter(Mandatory=$True,ValueFromPipeline=$true,Position=0)]
     [alias("user","account")]
     [string[]]
     $Identity,
     [Parameter(Mandatory=$False,ValueFromPipeline=$True,Position=1)]
     [string]
     $Domain = "DOMAIN",
     [Parameter(Mandatory=$False,ValueFromPipeline=$True)]
     [PSCredential]
     $Credential = $NULL
     )

You'll notice here that we've allowed for each of our parameters to accept pipeline input. And they'll accept that "ByValue", meaning that the piped objects must have the same .NET type, or must be able to be converted to that type. So only an actual PSCredential object can be accepted for the -Credential parameter.

 

Next, we want to be able to handle credentials in any of several ways. So we use:

if ( $Credential ) {
   $myCred = $Credential
} else {
   # Test if there is a stored, encrypted, password we can use
   $pwPath = "$home\Documents\WindowsPowerShell\$domainPW.txt"
   if (Test-Path $pwPath ) {
      $mypw = Get-Content $pwPath | ConvertTo-SecureString
      $myCred = New-Object -TypeName System.Management.Automation.PSCredential `
                           -ArgumentList "$DOMAIN\Charlie",$mypw
   } else {
      # Prompt for a credential, since we don't seem to have one here. 
      $myCred = Get-Credential 
   }
}

Let's look at this for a moment. First, we know from the Param section that the default value of $Credential is $NULL, so our first test is whether that's been overridden at the command line with either a pipelined parameter, or a specifically entered credential object. If so, we're good, and we use that. But failing that, we'll check if there's one stored securely on disk. (More on storing credentials on disks in another post soon.) If we've stored one on disk that matches the domain we're going against, we'll use that. If not, and we still don't have a PSCredential object we can use, we'll simply prompt for one.

 

Next up, we want to run this against the domain controller that hosts the PDCEmulator role. Not a big deal in simplified domain environment, but really a good idea in a complicated, multi-site environment where site-to-site propagation is a bit slow.

$PDC = (Get-ADDomain -Identity $DOMAIN -Credential $myCred).PDCEmulator

We'll plug the value of $PDC into the -Server parameter of our AD commands.

Finally, the meat of the whole thing. I'm not assuming you're only unlocking a single account, so I've designed this to take a list of strings ([string[]]). (A list can, of course, be a list of length one.)

foreach ($usr in $Identity) {
   Unlock-ADAccount -Identity $usr `
                    -Credential $myCred `
                    -Server $PDC `
                    -PassThru `
       | Get-ADUser -Properties LockedOut

 

That last line ensures that we report back in a way that shows the account no longer locked out. But for Get-ADUser to actually do anything, you need to include the -PassThru parameter in the Unlock-ADAccount command. Otherwise, nothing at all gets passed to the Get-ADUser command.

So, here's the whole thing:

<#
.Synopsis
Unlocks a  domain account
.Description
Unlock-myUser accepts an array of DOMAIN account names and unlocks the accounts 

This script accepts pipeline input for the credential. If no credential is supplied, 
it will attempt to use one you have stored on disk. Failing that, it will prompt 
you for credentials. 
.Example
Unlock-myUser -Identity Charlie
Unlocks the account of Charlie 
.Example
Unlock-myUser -Identity Charlie, Sharon
Unlocks the accounts of Charlie and Sharon 
.Example
Unlock-myUser -Identity Charlie -Domain TREYRESEARCH `
              -Credential (Get-Credential `
                               -username "TREYRESEARCH\Alfie"  `
                               -Message "Enter your Domain PW")

Unlocks the account of TreyResearch\Charlie, prompting Alfie for credentials. 
.Parameter Identity
The AD identity of the user or users whose acounts are to be reset.
.Parameter Domain
The Active Directory domain of the user or users whose accounts are to be reset
.Parameter Credential
The user credentials to run the script under. 
.Inputs
[string[]]
[string]
[PSCredential]
.Notes
    Author: Charlie Russel
 Copyright: 2016 by Charlie Russel
          : Permission to use is granted but attribution is appreciated
   Initial: 07 Sept, 2016 (cpr)
   ModHist: 09 Sept, 2016 (cpr) - added PDC test and Domain parameter 
          :
#>
[CmdletBinding()]
Param(
     [Parameter(Mandatory=$True,ValueFromPipeline=$true,Position=0)]
     [alias("user","account")]
     [string[]]
     $Identity,
     [Parameter(Mandatory=$False,ValueFromPipeline=$True,Position=1)]
     [string]
     $Domain = "DOMAIN",
     [Parameter(Mandatory=$False,ValueFromPipeline=$True)]
     [PSCredential]
     $Credential = $NULL
     )

if ( $Credential ) {
   $myCred = $Credential
} else {
   # Test if there is a stored, encrypted, password we can use
   $pwPath = "$home\Documents\WindowsPowerShell\$domainPW.txt"
   if (Test-Path $pwPath ) {
      $mypw = Get-Content $pwPath | ConvertTo-SecureString
      $myCred = New-Object -TypeName System.Management.Automation.PSCredential `
                           -ArgumentList "DOMAIN\Charlie",$mypw
   } else {
      # Prompt for a credential, since we don't seem to have one here. 
      $myCred = Get-Credential 
   }
}

# Find out which server holds the PDC role. 
# Useful in complicated, multi-site environments where 
# Domain changes might not propagate quickly. 

$PDC = (Get-ADDomain -Identity $DOMAIN -Credential $myCred).PDCEmulator

foreach ($usr in $Identity) {
   Unlock-ADAccount -Identity $usr `
                    -Credential $myCred `
                    -Server $PDC `
                    -PassThru `
       | Get-ADUser -Properties LockedOut
}

As always, feel free to use this script or any portion of it that you find useful. However, if you do, I'd appreciate attribution and a pointer back to my blog.

Leave a Reply

Your email address will not be published. Required fields are marked *