PowerShell: Remove-ADComputer v. Remove-ADObject

So, as I mentioned the other day, we needed to do some major cleanup of defunct and orphaned computer accounts. Most computers that hadn’t been logged in to in the last year needed to go. And there were a LOT of them! Certainly more than anyone wanted to try to do in the GUI. So, having found them, it was time to remove them, using:

$oneyear = (Get-Date).AddDays(-365)
Get-ADComputer -Filter {(LastLogonDate -lt $oneyear ) -AND ((Name -like "ws-*") -OR (Name -like "Desktop*") -OR (Name -like "XP-*"))} 
               -Properties LastLogonDate `
  | Remove-ADComputer -Confirm:$False -Verbose

And I started watching the deletions go by on the screen. Honestly, a fairly scary moment. Especially when I started to see some errors scroll by..

VERBOSE: Performing the operation "Remove" on target "CN=WS-DCOVENTRY-02,OU=\#Workstations,DC=Contoso,DC=com".
VERBOSE: Performing the operation "Remove" on target "CN=WS-VTAWARE-02,CN=Computers,DC=Contoso,DC=com".
VERBOSE: Performing the operation "Remove" on target "CN=WS-VIMALG-02,CN=Computers,DC=Contoso,DC=com".
VERBOSE: Performing the operation "Remove" on target "CN=WS-FHEMMATI-02,OU=\#Workstations,DC=Contoso,DC=com".
VERBOSE: Performing the operation "Remove" on target "CN=WS-BGL-ECOM,CN=Computers,DC=Contoso,DC=com".
Remove-ADComputer : The directory service can perform the requested operation only on a leaf object
At line:1 char:228
+ ... LogonDate | Remove-ADComputer 
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (CN=WS-BGL-ECOM,...C=Contoso,DC=com:ADComputer) [Remove-ADComputer], ADExce
    + FullyQualifiedErrorId : ActiveDirectoryServer:8213,Microsoft.ActiveDirectory.Management.Commands.RemoveADCompute

I ended up with about 15% of the computer accounts refusing to be removed with Remove-ADComputer. So I checked the man pages for Remove-ADComputer, and there were no additional parameters that would overcome it. Well, phooie!


OK, so time to haul out the seriously powerful tool, Remove-ADObject -Recursive. A word of warning here — you can do some serious damage with this command.  First, I verified the cause of the failures — the offending computer accounts had subsidiary objects that they probably shouldn’t ever have had. OK, all that was before my time, but none of them were any longer relevant. So, now, my command needed to morph due to the somewhat more annoying syntax of Remove-ADObject. I couldn’t just pipe the results of Get-ADComputer to it, I needed to return a list of objects and walk through them with a ForEach loop, like this:

$oneyear = (Get-Date).AddDays(-365)
$adFilter = {(LastLogonDate -lt $oneyear ) -AND ((Name -like "ws-*") -OR (Name -like "Desktop*") -OR (Name -like "XP-*"))} 

ForEach ($Computer in (Get-ADComputer -Filter $adFilter -Properties LastLogonDate)) {
     Remove-ADObject $Computer -Recursive -Confirm:$False -Verbose

And there go the last of the orphaned accounts! Notice, by the way, the use of a variable to hold the filtering criteria. This is a useful trick if you’re iterating through a bunch of filters, or dealing with a fairly long and complicated one. You need to edit the variable with each iteration, but the actual command stays the same. Plus, IMHO, it makes the whole thing more readable.

PowerShell: Finding Orphaned Computer Accounts in AD

The other day we decided it was time and more to do some cleanup of orphaned computer accounts in our AD. We are about to do some AD restructuring, and figured it was a good opportunity to clean up and remove old computer accounts for machines that no longer existed. Now there are probably lots of ways to do this, but the way I chose was to look at the AD properties of the computer to see when it was last logged on to. Then arbitrarily deciding that any computer that hadn’t been logged on to in the last year was a good candidate. At first glance, that’s not part of the properties that are returned with Get-ADComputer:

Get-ADComputer -Identity srv2

DistinguishedName : CN=SRV2,OU=Servers,DC=contoso,DC=com
DNSHostName       : srv2.contoso.com
Enabled           : True
Name              : SRV2
ObjectClass       : computer
ObjectGUID        : 0ce3c9fa-4b07-4dde-8323-ff94153d2bf9
SamAccountName    : SRV2$
SID               : S-1-5-21-2576220272-3971274590-1167723607-15115
UserPrincipalName :

But wait, I know there have to be more than that — let’s try making sure that we get all the properties, not just the most common:

Get-ADComputer -Identity srv2 -Properties *

AccountExpirationDate                :
accountExpires                       : 9223372036854775807
AccountLockoutTime                   :
AccountNotDelegated                  : False


DistinguishedName                    : CN=SRV2,OU=Servers,DC=contoso,DC=com
DNSHostName                          : srv2.contoso.com


KerberosEncryptionType               : {RC4, AES128, AES256}
LastBadPasswordAttempt               : 4/25/2016 6:28:41 PM
LastKnownParent                      :
lastLogoff                           : 0
lastLogon                            : 131689942478668713
LastLogonDate                        : 4/18/2018 10:18:47 PM
lastLogonTimestamp                   : 131685887279055446


whenCreated                          : 4/23/2015 6:28:41 PM

Ah, that’s more like it. Now I can see that there’s a LastLogonDate property. That should do it. Now, it’s just a case of simple math. And because we’re looking for more than a single computer, we need to switch to using the -Filter parameter of Get-ADComputer. Plus I’ll specify which server to query, and the account credentials to use to run the query:

Get-ADComputer `
        -Server dc01 `
        -Credential $cred `
        -Filter * `
        -Properties LastLogonDate `
              | Where-Object LastLogonDate -lt (Get-Date).AddDays(-365) `
              | Select-Object Name,LastLogonDate

Now that’s fine for moderately sized Active Directories, but could be a bit of a problem for large ones. So, instead of grabbing every computer in the domain and then filtering them, let’s only get the one’s that fit our one year criteria.

$oneyear = (Get-Date).AddDays(-365)
Get-ADComputer `
        -Server dc01 `
        -Credential $cred `
        -Filter {LastLogonDate -lt $oneyear } `
        -Properties LastLogonDate `
             | Select-Object Name,LastLogonDate `
             | ConvertTo-CSV -NoTypeInformation > C:\Temp\Defunct.csv

And now we have the list usefully exported to a CSV where we can manipulate it and verify the names really are those of orphaned computers. From there, I could feed the list of computers into Remove-ADComputer, or I can do it directly by piping this result to Remove-ADComputer, complete with a -Force parameter. Yeah. Right. And maybe a good idea to just verify the list first.


ETA: Well, it might be a good idea to check the available parameters for Remove-ADComputer before I post something. Sigh. There is no -Force parameter. Instead, you need to use -Confirm:$False if you want Remove-ADComputer to just do its work without prompting. And if the computer has any objects associated with it, you’ll have to use Remove-ADObject. But more on that in another post.