Monitor AD group changes

Ever wanted to monitor group changes in AD? This is a script I came up with to do just that. It will collect the security log events from the last hour on all your domain controllers.

In order to use this script, just create a new scheduled task on a machine with the Active Directory module for Windows PowerShell installed and run this script every hour (or whatever you changed $time to). !! Note that the scheduled task needs to be run with an account which has domain admin privileges to be able to read from the security logs of all your domain controllers !!>

The script needs some customization to your environment:

  • variable $Time, as mentioned before, needs to be changed to the interval you want the scheduled task to run
  • variable $ListOfMonitoredOUs needs to be changed to contain all OU’s which contain the groups you want to monitor
  • variables $FromAddress, $ToAddress and $SMTPServer need to be updated to reflect the appropriate email addresses and SMTP server

!! Note that the scheduled task needs to be run with an account which has domain admin privileges to be able to read from the security logs of all your domain controllers. I would advice you to create and use a dedicated service account for running this scheduled task, and to limit the Logon To of this account to just the server running the scheduled task !!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
If (-not (Get-Module -Name 'ActiveDirectory' -ErrorAction SilentlyContinue)) {
Import-Module ActiveDirectory
}

# Fixed variables
$DomainDistinguishedName = (Get-ADDomain -Current LocalComputer).DistinguishedName
$ListOfDCs = @(Get-ADDomainController -Filter { Name -like "*" })

# https://www.morgantechspace.com/2013/08/active-directory-change-audit-events.html
# The following table document lists the event IDs of the Security Group Management category
$EventIDHashTable = [ordered]@{
'4727' = 'A security-enabled global group was created'
'4728' = 'A member was added to a security-enabled global group'
'4729' = 'A member was removed from a security-enabled global group'
'4730' = 'A security-enabled global group was deleted'
'4731' = 'A security-enabled local group was created'
'4732' = 'A member was added to a security-enabled local group'
'4733' = 'A member was removed from a security-enabled local group'
'4734' = 'A security-enabled local group was deleted'
'4735' = 'A security-enabled local group was changed'
'4737' = 'A security-enabled global group was changed'
'4754' = 'A security-enabled universal group was created'
'4755' = 'A security-enabled universal group was changed'
'4756' = 'A member was added to a security-enabled universal group'
'4757' = 'A member was removed from a security-enabled universal group'
'4758' = 'A security-enabled universal group was deleted'
'4764' = 'A groups type was changed'
}

#Dynamic variables
$Time = (Get-Date).AddHours(-1)
$ListOfMonitoredOUs = (
"OU=OU1,$DomainDistinguishedName",
"OU=OU2,$DomainDistinguishedName",
"OU=OU3,$DomainDistinguishedName"
)

$FromAddress = '<fromemailaddress>'
$ToAddress = '<toemailaddress>'
$SMTPServer = '<smtpserver>'

$ListOfMonitoredGroups = $null
ForEach ($MonitoredOU in $ListOfMonitoredOUs) {
$ListOfMonitoredGroups += @(Get-ADGroup -SearchBase $MonitoredOU -Filter *)
$ListOfMonitoredGroups.Count
}

# Get Security Log Entries
$LogEntry = @()
$LogEntries = @()
ForEach ($DC in $ListofDCs) {
Write-Host "Processing domain controller $DC"
$LogEntries += Get-WinEvent -ComputerName $DC.Hostname -FilterHashTable @{Logname = 'Security'; StartTime = $Time } -ErrorAction Ignore | `
Where-Object { $_.TaskDisplayName -eq "Security Group Management" -and $_.ID -ne "4737" }
$LogEntries = $LogEntries | Sort-Object TimeCreated
}
#$LogEntries | FL

# Process Log Entries
If ($LogEntries -ne "") {
ForEach ($LogEntry in $LogEntries) {
$Subject = "[$env:USERDOMAIN] - Eventid " + $LogEntry.ID + " - " + $EventIDHashTable.Item($LogEntry.Id.ToString())
$MailBody = ($LogEntry | Format-List | Out-String)
Send-MailMessage `
-From $FromAddress `
-To $ToAddress `
-Subject $Subject `
-SmtpServer $SMTPServer `
-Body $MailBody
}
}


As always, please keep in mind this script is tailored to my environment, but can be used as a template for your environment. I do not pretend to be a PowerShell guru and as such my script may not be perfect. I am open to suggestions  🙂
If you found this script useful, I’d appreciate it if you leave a comment.

2 thoughts on “Monitor AD group changes”

  1. Hello,
    Thanks for your script.
    However when I run it, I got some emails, and for some events I don’t have the Group Name. I have the variable. when I search this variable in my ActiveDirectory I get the real group.
    How to get the real group name for all events ?

    Examples:

    OK:

    TimeCreated : 3/15/2021 10:32:08 AM
    ProviderName : Microsoft-Windows-Security-Auditing
    Id : 4728
    Message : A member was added to a security-enabled global group.

    Subject:
    Security ID: S-1-5-21-2356255079-306637177-891954067-11111
    Account Name: User1
    Account Domain: Domain1
    Logon ID: 0xB9A7C48

    Member:
    Security ID: S-1-5-21-2356255079-306637177-891954067-22222
    Account Name: CN=Firstname Lastname,OU=xxxx,OU=Users,OU=xx-xxxx,DC=vxxxxxDC=xxxxx,DC=axxxx

    Group:
    Security ID: S-1-5-21-2356255079-306637177-891954067-4444
    Group Name: GroupName1
    Group Domain: Domain1

    Additional Information:
    Privileges: –

    NOT OK :

    TimeCreated : 3/15/2021 9:56:20 AM
    ProviderName : Microsoft-Windows-Security-Auditing
    Id : 4728
    Message : A member was added to a security-enabled global group.

    Subject:
    Security ID: S-1-5-21-2356255079-306637177-891954067-1111
    Account Name: User2
    Account Domain: Domain1
    Logon ID: 0xB61DE89

    Member:
    Security ID: S-1-5-21-2356255079-306637177-891954067-1111
    Account Name: CN=Firstname Lastname,OU=xxxx,OU=Users,OU=xxxxxx,DC=xxxxx,DC=xxxxx,DC=xxx

    Group:
    Security ID: S-1-5-21-2356255079-306637177-891954067-2222
    Group Name: $4O4100-6RB7AH7DLQB8
    Group Domain: Domain1

    Additional Information:
    Privileges: –

    The problem is this Group Name: $4O4100-6RB7AH7DLQB8
    How to get the real name of this group instead of this variable ??

    Many thanks for your help and regards,
    Johnny

    1. Hello Johnny,

      I have not encountered this issue myself and, upon examining the code, could not find and issue which may explain this.

      We have refined this script some time ago. I have attached the script below. Please try this revised code, but be aware of typos. I had to remove some parts specific to my environment and have not been able to test the code.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      #############################################################################################################################
      # // Start of section Script initialization. Load all required modules and declare base variables \\

      If (-not (Get-Module -Name 'ActiveDirectory' -ErrorAction SilentlyContinue)) {
          Import-Module ActiveDirectory
      }

      # Define variables
      $Domain = '<FQDN of AD domain. Eg. contoso.com>'            ### Change according to your environment
      $SMTPServer = '<FQDN or IP address of your SMTP server>'    ### Change according to your environment
      $Time = (Get-Date).AddHours(-1)                             ### Change according to your requirement
      $ListOfMonitoredGroups = $null
      $ListOfMonitoredOUs = (
          "OU=OU1,$DomainDistinguishedName",
          "OU=OU2,$DomainDistinguishedName",
          "OU=OU3,$DomainDistinguishedName"
      )                                                           ### Change according to your environment
      $DomainDistinguishedName = (Get-ADDomain -Server $Domain).DistinguishedName
      $ListOfDCs = Get-ADDomainController -Server $Domain -Filter { Name -like '*' } | Sort-Object

      # https://www.morgantechspace.com/2013/08/active-directory-change-audit-events.html
      # The following table document lists the event IDs of the Security Group Management category.
      $EventIDHashTable = [ordered]@{
          '4727' = 'A security-enabled global group was created'
          '4728' = 'A member was added to a security-enabled global group'
          '4729' = 'A member was removed from a security-enabled global group'
          '4730' = 'A security-enabled global group was deleted'
          '4731' = 'A security-enabled local group was created'
          '4732' = 'A member was added to a security-enabled local group'
          '4733' = 'A member was removed from a security-enabled local group'
          '4734' = 'A security-enabled local group was deleted'
          '4735' = 'A security-enabled local group was changed'
          '4737' = 'A security-enabled global group was changed'
          '4754' = 'A security-enabled universal group was created'
          '4755' = 'A security-enabled universal group was changed'
          '4756' = 'A member was added to a security-enabled universal group'
          '4757' = 'A member was removed from a security-enabled universal group'
          '4758' = 'A security-enabled universal group was deleted'
          '4764' = 'A groups type was changed'
      }


      # \\ End of section Script initialization //
      #############################################################################################################################
      # // Start of main code section \\

      ## Part 1. First get the security logs from all domain controllers
      $LogEntries = @()
      $LogEntries = ForEach ($DC in $ListofDCs) {
          Write-Host "Processing domain controller $DC"
          Get-WinEvent -ComputerName $DC.Hostname -FilterHashtable @{
              Logname   = 'Security'
              StartTime = $Time
              ID        = 4727, 4728, 4729, 4730, 4731, 4732, 4733, 4734, 4735, 4754, 4755, 4756, 4757, 4758, 4764
          } -ErrorAction Ignore # In case you want to filter the results, pipe the results to a Where-Object clause like | Where-Object { $_.Message -notmatch "text" }
      }
      $LogEntries = $LogEntries | Sort-Object TimeCreated


      ## Part 2. Get a list of all groups located within the monitored OUs
      $ListOfMonitoredGroups = ForEach ($MonitoredOU in $ListOfMonitoredOUs) {
          Get-ADGroup -Server $Domain -SearchBase $MonitoredOU -Filter '*'
      }
      $ListOfMonitoredGroups = $ListOfMonitoredGroups | Sort-Object -Property Name


      ## Part 3. Filter variable $LogEntries to match only the groups located within the monitored OUs
      $FilteredLog = $null
      $FilteredLog = ForEach ($LogEntry in $LogEntries) {
          $ListOfMonitoredGroups | ForEach-Object $_.Name {
              If ($LogEntry.Message -match $_.Name) {
                  $LogEntry
              }
          }
      }
      $LogEntries = $FilteredLog | Sort-Object TimeCreated


      ##Part 4. Mail each entry in variable $LogEntries
      If ($LogEntries -ne "") {
          ForEach ($LogEntry in $LogEntries) {
              #$Subject = "[$Domain] - Eventid " + $LogEntry.ID + " - " + $EventIDHashTable.Item($LogEntry.Id.ToString())

              # Mail report
              Write-Host "Emailing Report"
              $SmtpClient = New-Object Net.Mail.SmtpClient($smtpServer);
              $MailMessage = New-Object system.net.mail.mailmessage
              $MailMessage.From = '<from address>'
              $MailMessage.To.add('<mail recipient 1')
              $MailMessage.To.add('<mail recipient 2')
              $MailMessage.To.add('<mail recipient 3')
              $MailMessage.Subject = "[$Domain] - Eventid " + $LogEntry.ID + " - " + $EventIDHashTable.Item($LogEntry.Id.ToString())
              $MailMessage.IsBodyHtml = $false
              $MailMessage.Body = ($LogEntry | Format-List | Out-String)
              $smtpclient.Send($MailMessage)

              Start-Sleep 1
          }
      }

      # \\ End of main code section //
      #############################################################################################################################

      Hope this helps.

      Kind regards,
      Dennis

Leave a Reply

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