Technology Solutions for Everyday Folks

Basic "Diff" or Delta Identification Between AD Objects Using Powershell

A couple times a year I do some spot checking and a little manual cleanup in AD. This is triggerd by inventory/asset management analysis. I say "spot checking" because by and large the existing business processes and automations take care of the vast majority of changes, but for a couple of reasons there are always a few stragglers.

A number of years ago I built out this simple-ish script to essentially "diff" the presence of AD devices with the presence of an associated AD group (for our MMA allowlisting). Enabled devices live in a special OU, and an associated AD security group defines which accounts have MMA rights for enabled machines. As I mentioned, most of the existing processes keep these tightly synchronized but once in a while there's an exception and this is what I want to find and correct. Doing so during a semi-annual inventory spot check/reconciliation is simple and super low effort.

I had a recent conversation with someone that prompted me to write this post as an example of a way to help find orphaned objects or groups, so here we go!

So, What Does It Need To Do?

Ultimately there are devices, and there are associated groups. I want to identify the gaps:

  1. What "enabled" devices don't have an associated group?
  2. What groups don't have an "enabled" device?
  3. What groups have a missing device association (device no longer exists)?

For the sake of this search, our "key" is the device name, which is (obviously) unique and also present in the security group name.

Let's See The Scripts

For question #1 above, I can use this bit of Powershell to show me groups that aren't present:

$MMADevices = Get-ADComputer -Filter * -Property Name -SearchBase 'OU=Devices,OU=To,OU=Path,DC=company,DC=com'
$MMACount = 0
$MMAGroupMatches = 0
$MMADevices | Foreach-Object {
  if ($_ -match ",OU=MMA-Enabled,") {
    $NameFilter = $_.Name
    $FilterGroup = "ORG-MMA-$NameFilter"
    $MMAGroup = Get-ADGroup -Filter "Name -eq '$FilterGroup'" | Measure-Object
    if (1 -eq $MMAGroup.Count) {
      $MMAGroupMatches++
    } else {
      Write-Host -ForegroundColor Red "!! --> $NameFilter Group Not Found <-- !!"
    }
    $MMACount++
  }
}

This will spit out missing (the "diff" of) groups at the command line. For example, it would output something like this for a missing group association (in the terminal it's highlighted in red):

!! --> NAME-OF-DEVICE Group Not Found <-- !!

Similarly, for questions #2 and #3 above, I can use this bit of Powershell to show me devices that aren't in the right OU or are no longer active/present in AD:

$MMAGroups = Get-ADGroup -Filter "Name -like 'ORG-MMA-*'"
$MMAGroupCount = 0
$MMADeviceMatches = 0
$MMAGroups | Foreach-Object {
  $NameFilter = $_.Name -replace 'ORG-MMA-',''
  $Device = Get-ADComputer -Filter "Name -eq '$NameFilter'" -Property Name -SearchBase 'OU=Devices,OU=To,OU=Path,DC=company,DC=com'
  if ($Device.DistinguishedName -notmatch ",OU=MMA-Enabled,") {
    Write-Host -ForegroundColor Red "!! --> $NameFilter Device Not in MMA-Enabled OU <-- !!"
  }
  $MMADevice = $Device | Measure-Object
  if (1 -eq $MMADevice.Count) {
    $MMADeviceMatches++
  } else {
    Write-Host -ForegroundColor Red "!! --> $NameFilter Device Not Found <-- !!"
  }
  $MMAGroupCount++
}

This will spit out misplaced or missing (again, the "diff" of) devices at the command line, again as an example here (terminal color is red):

!! --> NAME-OF-DEVICE-1 Device Not Found in MMA-Enabled OU <-- !!
!! --> NAME-OF-DEVICE-2 Device Not Found <-- !!

The first line above indicates that the AD object isn't in the correct OU. The second line above indicates the associated device cannot be found in AD.

I also spit out a brief summary of group and device counts, primarily because I can. The numbers should match under both comparisons when things are correct/in order:

Write-Host "========== SEARCH SUMMARY =========="
Write-Host "$MMACount devices are properly scoped for MMA."
Write-Host "$MMAGroupMatches devices have a matching group"
Write-Host ""
Write-Host "$MMAGroupCount groups are enabled for MMA."
Write-Host "$MMADeviceMatches devices have a matching group

Let's See It In Action

I took a couple screenshots at the most recent iteration, before and after I made a few cleanups:

Before Fixing:

Screen snip of a terminal prompt in which the output of the above script is presented with four records in red and a summary with mismatching totals.

There are four problems identified in the above snip:

  • The first line/device is enabled for MMA, but doesn't have an allowlist group;
  • The second and third lines indicate a device is both not in the correct OU, but also not found in AD; and
  • The fourth line/device has a MMA allowlist group, but isn't in the proper OU for MMA to work.

After Fixing:

Screen snip of a terminal prompt in which the output of the above script is presented with a matching totals.

 The Whole Script

# Script Requires AD Module
Import-Module ActiveDirectory
# Run through the MMA-Enabled devices of a parent OU and highlight anything that doesn't have an associated MMA rights group
$MMADevices = Get-ADComputer -Filter * -Property Name -SearchBase 'OU=Devices,OU=To,OU=Path,DC=company,DC=com'
$MMACount = 0
$MMAGroupMatches = 0
$MMADevices | Foreach-Object {
  if ($_ -match ",OU=MMA-Enabled,") {
    $NameFilter = $_.Name
    $FilterGroup = "ORG-MMA-$NameFilter"
    $MMAGroup = Get-ADGroup -Filter "Name -eq '$FilterGroup'" | Measure-Object
    if (1 -eq $MMAGroup.Count) {
      $MMAGroupMatches++
    } else {
      Write-Host -ForegroundColor Red "!! --> $NameFilter Group Not Found <-- !!"
    }
    $MMACount++
  }
}
# Run through the MMA-Enabled groups and highlight anything that doesn't have an associated MMA-Enabled computer object
$MMAGroups = Get-ADGroup -Filter "Name -like 'ORG-MMA-*'"
$MMAGroupCount = 0
$MMADeviceMatches = 0
$MMAGroups | Foreach-Object {
  $NameFilter = $_.Name -replace 'ORG-MMA-',''
  $Device = Get-ADComputer -Filter "Name -eq '$NameFilter'" -Property Name -SearchBase 'OU=Devices,OU=To,OU=Path,DC=company,DC=com'
  if ($Device.DistinguishedName -notmatch ",OU=MMA-Enabled,") {
    Write-Host -ForegroundColor Red "!! --> $NameFilter Device Not in MMA-Enabled OU <-- !!"
  }
  $MMADevice = $Device | Measure-Object
  if (1 -eq $MMADevice.Count) {
    $MMADeviceMatches++
  } else {
    Write-Host -ForegroundColor Red "!! --> $NameFilter Device Not Found <-- !!"
  }
  $MMAGroupCount++
}
Write-Host "========== SEARCH SUMMARY =========="
Write-Host "$MMACount devices are properly scoped for MMA."
Write-Host "$MMAGroupMatches devices have a matching group"
Write-Host ""
Write-Host "$MMAGroupCount groups are enabled for MMA."
Write-Host "$MMADeviceMatches devices have a matching group"

 That's All, Folks!

There are a couple ways this particular process/script could be more efficient, useful, etc., but again since I use it ~2x/year this does exactly what I need without overcomplicating things and provides me the actionable response/output I need to clean up the last couple stragglers that weren't addressed through the normal operating process. Hopefully you find this concept useful for something in your own environments!