Automate Active Directory Auditing with PowerShell

Published:9 December 2020 - 8 min. read

Azure Cloud Labs: these FREE, on‑demand Azure Cloud Labs will get you into a real‑world environment and account, walking you through step‑by‑step how to best protect, secure, and recover Azure data.

Tracking and auditing changes to passwords in an Active Directory (AD) domain are crucial to maintaining a secure environment and heading off bad actors early. Thankfully, AD offers the information necessary to track these changes, despite being difficult to parse and understand at times. LAPS is a great example of this.

In this article, you’re going to learn how to enable Active Directory auditing of passwords, how to filter events in the Event Viewer and use PowerShell to more easily audit the results with a script.

This tutorial is sponsored by Specops and their useful tool Password Auditor.

Prerequisites

If you’d like to follow along with this tutorial, please be sure you have the following:

  • An Active Directory environment with at least one domain-joined workstation. The tutorial is at a domain functional level of Windows Server 2016, however, Advanced Audit Policies were introduced in Windows Server 2008 via a logon script and via a GPO in Windows Server 2008 R2.
  • Remote Server Administration Tools (RSAT) for Active Directory installed on your domain-joined workstation
  • PowerShell 7.x

Enable Group Policy for Active Directory Auditing

The necessary auditing information you need to audit AD password changes is stored on domain controllers (DC), but the domain controller in the Primary Domain Controller (PDC) emulator role will ultimately process the request. But, by default, the necessary auditing isn’t enabled on DCs. Let’s change that.

To find the DC holding the PDCe role, use the PowerShell command, (Get-ADDomain).PDCEmulator.

To enable password change auditing, create a new group policy object (GPO). This GPO will be created and linked to the entire domain.

You could change the Default Domain Policy but Microsoft recommends against this. If you ever need to make a change across DCs in your environment, always create a separate GPO.

Though you are creating and linking a GPO to the entire domain, the relevant audit events are only available on DCs. But, it is beneficial to have those same logs on domain-joined clients because it may be useful in the event that a local non-AD account password is changed. If you’re only auditing Active Directory accounts, you can instead link the GPO to the Domain Controllers organizational unit (OU).

On your domain-joined workstation, create a GPO that forces DCs to begin auditing password changes:

  1. Open the Group Policy Management snap-in by going to Start → Run and typing gpmc.msc.

2. Click on Create a GPO in this domain, and Link it here… and give the policy a name. This tutorial’s example will use the name Active Directory Password Auditing.

Creating a GPO to hold the user password auditing settings.
Creating a GPO to hold the user password auditing settings.

3. Once the policy has been created, right-click it, and choose Edit to open the Group Policy Management Editor.

Open the GPO for editing.
Open the GPO for editing.

4. Navigate to Computer Configuration → Policies → Windows Settings → Security Settings → Local Policies → Audit Policy → Audit account management.

5. Next, double-click on the Audit Account Management policy setting and check the checkbox Define these policy settings while ensuring both the Success and Failure checkboxes are checked. By doing so, successful and unsuccessful password attempts will be logged.

Enable Success and Failure for the Audit account management policy in the Group Policy Management Editor.
Enable Success and Failure for the Audit account management policy in the Group Policy Management Editor.

6. Click OK to close and exit the editor.

7. Open up a Remote Desktop (RDP) client and connect to the domain controller running the PDC emulator (PDCe) AD role.

All DCs process password changes but all DCs replicate password changes to the DC holding the PDC emulator (PDCe) role so technically, you only need to look at this DC’s events.

8. On the PDCe DC, open a command prompt or PowerShell console and run gpupdate to force a group policy update.

If you have PowerShell Remoting enabled on your DCs, you can also invoke a GPO update that way also.

Force a group policy update via the command line.
Force a group policy update via the command line.

Once the configuration changes have been made and group policy updated, you now have auditing events turned on and logging for account management. Read on to discover how to interpret these events.

Deciphering Account Management Event Logging

The category of audit events password changes fall under is called Account Management events. These events record information such as password change events and user account lockouts. Account Management audit events are logged as Windows events in the Security event log of a machine that has the auditing enabled.

On your domain-joined machine:

  1. Open up Windows Event Viewer by running eventvwr.msc or using the Start menu.

2. Right-click on Event Viewer (Local) and select Connect to Another Computer….

Connect to Another Computer
Connect to Another Computer

3. Provide the name of the DC running the PDCe role in the Another computer: box and click OK to connect Event Viewer to the DC’s event source.

Provide the name of the DC running the PDCe role in the Another computer
Provide the name of the DC running the PDCe role in the Another computer

4. Expand the Windows Logs item and click on Security. This will bring you to the Security log as shown below.

Security Log
Security Log

Inside of the security log, you’ll find various events with a source of Microsoft Windows security auditing and User Account Management task categories as shown in the filtered view below.

Demonstrate the filtered view of events in the Event Viewer.
Demonstrate the filtered view of events in the Event Viewer.

Each Windows event has a unique ID that represents the type of event. Though there are several event IDs that the Microsoft Windows security auditing source contains, the primary event IDs that you should be interested in for password changes (and user lockouts) are:

  • 4723 – An attempt was made to change an account’s password.
  • 4724 – An attempt was made to reset an account password.
  • 4740 – A user account was locked out.
  • 4767 – A user account was unlocked.

You’ll see a lot of events in the Security log so you’ll need to create an apply some filters to narrow down only password changes.

Filtering on Password Change Events

Within the Event Viewer, you can create a filter. A filter is a way to limit the number of events that show up and is mandatory when combing through the Windows Security event log.

To create this filter in the Event Viewer:

  1. Right-click on the Security log and click on Filter Current Log… as shown below.
Filter Current Log
Filter Current Log

2. In the Filter Current Log dialog box, create a filter to only find password change events using the following criteria and click on OK.

  • Event Sources: Microsoft Windows security auditing.
  • Event IDs: 4723,4724,4740,4767
  • Task Category: User Account Management.

When you’re complete, your Filter Current Log screen should look like below.

Create a filter for the Security log.
Create a filter for the Security log.

By filtering the Event Viewer, on the domain controller, to just the important event IDs a targeted list of events that have occurred specific to password and password changes are shown below.

Demonstrate the filtered view of events in the Event Viewer.
Demonstrate the filtered view of events in the Event Viewer.

Voila! You now have all of the password change (and user lockout) events that have occurred in your domain since you’ve linked the GPO created earlier!

Find Audit Events with PowerShell

Even though you can use the Windows Event Viewer connected to the DC’s Security event log sometimes you need a faster approach. Perhaps you have some automation built in the background or would like to automatically monitor this event log. In this case, you should use PowerShell.

Using PowerShell allows you to perform the exact same function all within a single script. With a script, you won’t have to connect to DCs, create filters manually and manually parse through events.

To find important auditing events with PowerShell, use the Get-WinEvent cmdlet. This cmdlet queries a local or remote event log and returns all events. It also has support for filters too just as the Event Viewer does. If you’ve already built a filter with Event Viewer, you can even, in fact, share that same filter with PowerShell!

Extracting the Event Viewer Filter

Since PowerShell can use the same filter as the Event Viewer, let’s save some time and extract that filter and use it with PowerShell’s Get-WinEvent cmdlet.

While still ensuring you’re still connected to the DC with Event Viewer:

  1. Go back to the Filter Current Log screen. You should still have all of the filter criteria set.
  2. Click on the XML tab at the top. This tab contains the raw XML that Event Viewer is passing to the event log to display only certain events. PowerShell also can use this XML.
  3. Select and copy all of the text been <Select Path=”Security”> and </Select> as shown below. This text is an XPath value that the Get-WinEvent cmdlet can use.
Demonstrate how to get the XPath easily for the Get-WinEvent cmdlet.
Demonstrate how to get the XPath easily for the Get-WinEvent cmdlet.

4. Open up a PowerShell console and paste in the following code snippet. You can see the FilterXPath parameter value is the exact same text extracted from the Event Viewer filter above.

Get-WinEvent -ComputereName <YOUR DC> -LogName 'Security' -FilterXPath "*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and Task = 13824 and (EventID=4723 or EventID=4724 or EventID=4740 or EventID=4767)]]"

When you run Get-WinEvent, you should see all of the same events you saw in the Event Viewer earlier as shown in the following screenshot.

Demonstrate using Get-WinEvent to show the results of the Event Viewer Security log.
Demonstrate using Get-WinEvent to show the results of the Event Viewer Security log.

You’ve now found all password change and user lockout events with PowerShell!

Buiding a Password Change Auditing Tool

You’ve seen how to use PowerShell’s Get-WinEvent cmdlet to audit AD events. If you plan to do this often, it’s always a good idea to use a reusable tool to use in other scripts or automation routines.

In this section, let’s build a PowerShell function called Get-ADPasswordEvent. This function will explore many techniques that can help you retrieve just the information that you want without all the additional properties that you get with Get-WinEvent.

To not bore you with all of the details, you can see a complete function already built for you below. This function performs a few different tasks:

  • Creates the XPath filter
  • Runs Get-WinEvent using the filter retrieving the specific AD account management password events
  • Returns custom output relevant to the task at hand.

You can find more granular details in the code comments.

Function Get-ADPasswordEvent {
  [CmdletBinding()]

  Param(
    [Parameter(Position=0)]
    [System.Collections.Generic.List[Int]]$EventID = @(4723,4724,4740,4767),

    [Parameter(Position=1)]
    [Int]$Hours,

    [Parameter(Position=2)]
    [ValidateSet("Success","Failure")]
    [System.Collections.Generic.List[String]]$EventType = @("Success","Failure"),
    [Parameter(Position=3)]
    [string]$ComputerName
  )

  Process {
    # The event type filter values are from the Audit Success and Audit Failure in the Keywords section of the Event Viewer filter.
    If ($EventType -Contains "Success" -And $EventType -Contains "Failure") {
      $EventTypeFilter = " and (band(Keywords,13510798882111488))"
    } ElseIF ($EventType -Contains "Success") {
      $EventTypeFilter = " and (band(Keywords,9007199254740992))"
    } Else {
      $EventTypeFilter = " and (band(Keywords,4503599627370496))"
    }

    # We construct a filter to pass to -FilterXPath. Hours should be returned in milliseconds and we use Join-String to properly format the EventID combinations.
    $Filter = "*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and Task = 13824{0}{1}{2}]]" -F
      (($Hours) ? " and TimeCreated[timediff(@SystemTime) <= $((New-TimeSpan -Hours $Hours).TotalMilliseconds)]" : $Null),
      (($EventId) ? ($EventId | Join-String -OutputPrefix " and (" -FormatString 'EventID={0}' -Separator ' or ' -OutputSuffix ")") : $Null),
    $EventTypeFilter
    
    Write-Verbose $Filter

    $Events = Get-WinEvent -ComputerName $ComputerName -LogName 'Security' -FilterXPath $Filter

    # For the Password Change and Reset ID's we format the Details to be more readable and show what is going on regarding the target (account being changed) and the subject (who did the changing).
    $Events | ForEach-Object {
      $Event = $_
      
      Switch ($Event.ID) {
        4723 {
          $Description = "Account Password Change Attempt"

          $Details = [PSCustomObject]@{
            "TargetAccount"  = ("{0}\{1}" -F ($Event.Properties)[1].Value, ($Event.Properties)[0].Value)
            "TargetSID"      = ($Event.Properties)[2].Value
            "SubjectAccount" = ("{0}\{1}" -F ($Event.Properties)[5].Value, ($Event.Properties)[4].Value)
            "SubjectSID"     = ($Event.Properties)[3].Value
          }

          Break
        }
        4724 {
          $Description = "Account Password Reset Attempt"
          
          $Details = [PSCustomObject]@{
            "TargetAccount"  = ("{0}\{1}" -F ($Event.Properties)[1].Value, ($Event.Properties)[0].Value)
            "TargetSID"      = ($Event.Properties)[2].Value
            "SubjectAccount" = ("{0}\{1}" -F ($Event.Properties)[5].Value, ($Event.Properties)[4].Value)
            "SubjectSID"     = ($Event.Properties)[3].Value
          }

          Break
        }
        4740 {
          $Description = "Account Locked Out"

          $Details = $Event.Message
          Break
        }
        4767 {
          $Description = "Account Unlocked"

          $Details = $Event.Message
          Break
        }
        Default {
          $Description = $Null
          $Details     = $Event.Message
          Break
        }
      }

    # Finally let's output our custom object that shows the event information in a more readable format.
      [PSCustomObject]@{
        "TimeCreated" = (Get-Date $_.TimeCreated)
        "ID"          = $_.ID
        "Description" = $Description
        "EventType"   = (($_.KeywordsDisplayNames -EQ 'Audit Success') ? "Success" : "Failure")
        "Details"     = $Details
      }
    }
  }
}

Once created, drop this function into a PowerShell console and run it with no parameters.

PS> Get-ADPasswordEvent

Shown below is running Get-ADPasswordEvent. You can see that the function returns many success and failure events for password reset attempts.

You can pass the output of the Get-ADPasswordReset function to Format-Table to make the output a little easier to read.

Retrieving AD account management password events using the Get-ADPasswordEvents function.
Retrieving AD account management password events using the Get-ADPasswordEvents function.

Implementing Better Password Auditing Solutions

Whether you’re using the Event Viewer or PowerShell, you have to jump through a few different hoops to find AD auditing events. The native tools and abilities of Active Directory could certainly be expanded upon, and for that, third-party tools such as Specops Password Auditor can help.

Specops’ Password Auditor tool not only can find these events, but it can also create in-depth reports that include changes, expired passwords, blank passwords, and more. Specops Password Auditor can fill in the missing gaps for Active Directory password auditing.

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!