SCOM: Monitoring GPO changes using SCOM and PowerShell

Monitoring GPO changes is quite complex discipline and for covering all aspects is available couple of tools or extra modules. Goal of this article is not detailed analysis of monitoring GPO changes but guidelines how to use recourses of SCOM and PowerShell to get basic overview of GPO monitoring.

Before we start lets go through quick summary. On Group Policy Object subject of monitoring can be:

·         Creating or deleting GPO

·         Changes in GPO

o   Changes in AD part

o   Changes in SYSVOL part

·         Linking or unlinking to Organization Unit

·         Changing in DACL and SACL

By monitoring we can rely on auditing more precisely on events that are created in security log. Using this method is quite straightforward but has one important weakness. Events in security logs are notifications saying that in GPO were made any changes but are not saying what exactly was changed. As I mentioned above detailed monitoring of GPO changes is quite complex, I’m not covering this. What I’m going to describe is how to set SCOM to notify that something in GPO was changed and how to customize these notifications.

Let’s take an example: create an alert with information that specific GPO was changed including:

·         GPO Name

·         GPO GUID

·         Author of change (if possible)

·         Event ID

·         Event Description

·         Any additional info

To reach this goal is necessary:

·         Setup audit on monitored servers

·         Create rule to respond to specific event in security log with alert

·         Create subscription responding to specific alert with PowerShell script

·         Create PowerShell script which collect additional attributes

·         Create new entry into SCOM log with collected information

·         Create final alert

All mentioned steps are descripted bellow.

Step 1 – Setup audit

Monitoring changes related to GPO is based on built-in feature of all Windows systems, on security audit. To get all necessary events we need turn on audit category Directory Service Changes, type Success and Failure. Most important events are:

·         5136: A directory service object was modified

·         5137: A directory service object was created

·         5141: A directory service object was deleted

Setting up security audit can be performed either on individual server or large-scale using Group Policy.

Step 2 – New rule in SCOM

Next step is to instruct SCOM to respond when any of monitored events is created on one of monitored servers (DCs). In our case we are looking for Event ID 5136 and need to fire up new alert. In SCOM console in section Authoring create new rule with following properties:

·         Alert generating rule: NT event log

·         Rule name: Info alert - GPO changed based on 5136

·         Rule target: Windows Domain Controller

·         Log name: Security

·         Filter: Event ID equals 5136 (for testing only this filter is enough but you can add more)

·         Priority: Medium

·         Severity: Information

·         Description:

o   Event ID: $Data/EventNumber$

o   Event Description: $Data/EventDescription$

·         Alert suppression: I’d recommend to setup suppression with following parameters:

o   Event ID

o   Parameter 4 (user name)

o   Parameter 10 (GUID)

o   Parameter 11 (class)

Alert created by this rule is just temporary and is used only for collecting parameters.  If we look closer at all provided parameters we will find that there is missing probably most imported parameter which is GPO name (event 5136 provide only GPO GUID). This is the main reason why to create a PowerShell script and let it run our code behind.

Step 3 New SCOM subscription

The easiest way to run custom PowerShell is through command line subscription. In SCOM console go to Administration – Notifications – Channels and create new channel like following:

·         Type: command

·         Name: PowerShell CMD – GPO changes

·         Full Path: C:\Windows\system32\windowspowershell\v1.0\powershell.exe

·         Parameters: C:\Temp\Res1.ps1 -AlertId '$Data/Context/DataItem/AlertId$'

·         Startup folder: C:\Temp

As next we need to create new subscriber and subscription:

Subscriber:

·         New subscriber: Administrator

·         Address: admin@mycompany.com

·         Channel type: command

·         Command channel: PowerShell CMD – GPO changes

·         Schedule: always

Subscription:

·         Name: GPO change notification by PowerShell

·         Criteria:

o   Created by rule: Info alert - GPO changed based on 5136

o   Resolution state: New(0)

·         Subscriber: Administrator

·         Channels: PowerShell CMD – GPO changes

·         Aging: Send notifications without delay

Step 4 – PowerShell script

PowerShell script is probably most interesting part. You can use it to customize parameters from alert, add additional parameters and many more. In our case we use PowerShell code for following:

·         Get all parameters from SCOM alert Info alert - GPO changed based on 5136

·         Close that alert

·         Parse provided GPO GUID and base on it find GPO name

·         Parse other provided parameters and build custom description

·         Create new entry into SCOM log

In following paragraphs are introduced particular pieces of code. There are provided just the most imported parts of code i.e. there is not error handling or logging. It is just example from my lab and before introducing into production it must be tested and customized based on specific environment.

Part A – Initializing script environment

function Init()

{

       $RMS = "localhost"

       $script:SCOMCliApi = new-object -ComObject "MOM.ScriptAPI"

       Add-PSSnapin "Microsoft.EnterpriseManagement.OperationsManager.Client"

       Microsoft.EnterpriseManagement.OperationsManager.ClientShell.Functions.ps1

       New-RMSConnection -RMS $RMS

}

function New-RMSConnection

{

       [CmdletBinding()]

       param(

            [parameter(Mandatory=$true, Position=0)] [ValidateNotNull()][String] $RMS

        )

       $connection = New-ManagementGroupConnection -ConnectionString:$RMS -ErrorAction:SilentlyContinue;

       set-location "OperationsManagerMonitoring::";

}

Part B – Main executive

function Main()

{

       $scomAlert = Get-Alert -Id "$AlertId"

 

       $gpoClass = Get-ValueFromDescription $scomAlert "Class"

      

       $gpoEventDescription = Get-ValueFromDescription $scomAlert "Event Description"

      

       $gpoEventID = Get-ValueFromDescription $scomAlert "Event ID:"

      

       $gpoLDAPDisplayName = Get-ValueFromDescription $scomAlert "LDAP Display Name"

      

       Switch ($gpoClass)

             {

             "groupPolicyContainer"{

                    # parse Account Name from filed Accont Name

                    $gpoAcctName = Get-ValueFromDescription $scomAlert "Account Name"

 

                    # parse GUID from filed GUID

                    $gpoGUID = Get-ValueFromDescription $scomAlert "GUID"

                    $gpoGUID=$gpoGUID.Trim()

                    if ($gpoGUID -ne "")

                    {

                           $gpoGUID=$gpoGUID.substring(($gpoGUID.indexof("{")+1),36)

                           if ($gpoGUID -ne $null)

                           {

                                  $foundGPOName = Get-GPOName $gpoGUID

                           }

                           else

                           {

                                  $Global:gpoNameStr="[ERROR] GPO not found."

                                  $gpoGUID="[ERROR] GUID not found (provided NULL GUID)."    

                           }

                    }

                    else

                    {

                           $Global:gpoNameStr="[ERROR] GPO not found."

                           $gpoGUID="[ERROR] GUID not found (provided NULL GUID)."    

                    }

 

                    # Create new log entry

                    $Text="$($Global:gpoNameStr)`n User: $($gpoAcctName)`n Event ID: $($gpoEventID) `n Event Description: $($gpoEventDescription)"

                    $Text = $Text + "`n GUID: $($gpoGUID) `n Class: $($gpoClass)`n LDAP Display Name: $($gpoLDAPDisplayName)`n Repeat Count: $($scomAlert.RepeatCount)"

             }

             "organizationalUnit"{

                    # parse GPO GUID from filed Value

                    $gpoGUID = Get-ValueFromDescription $scomAlert "Value"

                    $gpoGUID=$gpoGUID.Trim()

                    if ($gpoGUID -ne "")

                    {

                           $gpoGUID=$gpoGUID.substring(($gpoGUID.indexof("{")+1),36)

                           if ($gpoGUID -ne $null)

                           {

                                  $foundGPOName = Get-GPOName $gpoGUID

 

                           }

                           else

                           {

                                  $Global:gpoNameStr="[ERROR] GPO not found."

                                  $gpoGUID="[ERROR] GUID not found (provided NULL GUID)."    

                           }

                    }

                    else

                    {

                                  $Global:gpoNameStr="GPO not found."

                                  $gpoGUID="GUID not found (no value provided)."

                    }

                    # parse Account Name from field Account Name

                    $gpoAcctName = Get-ValueFromDescription $scomAlert "Account Name"

 

                    # parse DN of OU field GUID

                    $gpoOUDN = Get-ValueFromDescription $scomAlert "GUID"

 

                    # Create new log entry

                    $Text="$($Global:gpoNameStr)`n User: $($gpoAcctName)`n Event ID: $($gpoEventID)`n Event Description: $($gpoEventDescription)`n OU: $($gpoOUDN)"

                    $Text = $Text + "`n GUID: $($gpoGUID)`n Class: $($gpoClass) `n LDAP Display Name: $($gpoLDAPDisplayName)`n Repeat Count: $($scomAlert.RepeatCount)"

 

             }

       }

      

       New-EventLogEntry $Text -NewEntry -WriteToLog

      

       # Resolve existing alert

       $resState=$scomAlert.ResolutionState

       $scomAlert.ResolutionState=255

       $scomAlert.Update("")

}

Part C – Parsing function

function Get-ValueFromDescription($scomAlert, $Field)

{

       If ($scomAlert.Description -eq $null)

       {

             $ValueFromDescription=""

       }

       Else

       {

             $AlertDescription = $scomAlert.Description

             $parsedText=$AlertDescription

             $parsedText=$parsedText.toString()

             # check if Description contains searched field

             if ($parsedText.contains($Field))

             {

                    $parsedText=$parsedText.Split("`n") | Select-String -Pattern "$Field"

                    $parsedText=$parsedText.ToString()

                    $parsedText=$parsedText.substring($parsedText.lastindexof(":")+1)

                    $parsedText=$parsedText.Trim()

                    $ValueFromDescription=$parsedText

             }

             else

             {

                    # searched field not found

                    $ValueFromDescription="Field $Field not found."

             }

       }

       $ValueFromDescription

}

Part D – Create new entry into SCOM event log

function New-EventLogEntry()

{

       [CmdletBinding()]

       param(

             [parameter(Mandatory=$false, Position=0)] [ValidateNotNull()] [string] $Message,

             [parameter(Mandatory=$false, Position=1)] [ValidateNotNull()] [int] $EventSeverity,

             [parameter(Mandatory=$false, Position=2)] [ValidateNotNull()] [uint32] $EventID = $EVENT_LOG_MESSAGE_DEFAULT_EVENTID,

             [parameter(Mandatory=$false)] [switch] $NewEntry,

             [parameter(Mandatory=$false)] [switch] $AppendToExisting,

             [parameter(Mandatory=$false)] [switch] $WriteToLog

       )

 

       If ($WriteToLog -eq $true)

       {

             $script:SCOMCliApi.LogScriptEvent("Group policy", $EventID, 4, $Script:EventLogMessage)

       }

}

Part E – script body

# Just put all together

[CmdletBinding()]

param(

       [parameter(Mandatory=$true, Position=0)] [ValidateNotNull()] [string] $AlertID

         )

$RMS = "localhost"

$EVENT_LOG_MESSAGE_DEFAULT_EVENTID = 11101

Sleep 10     # just for delay in case of massive growth of entries in security log

Init   # Call init functions

Main   # Call main script part

Step 5 – Final SCOM alert

Creating new entry into SCOM event log give us flexibility by creating final alert. We can go first through some test rounds and tune all parameters and script.

Then it is just piece of cake – create new collecting rule that will collect only events from SCOM event log, in our case only with event id 11101. And finally create long-awaited rule for firing up new alert with necessary information.

The whole effort can by express in graphics like:

Notes

By testing you will see that only little change in GPO (like rename) will rice always at least two new entries in security log (by editing settings in GPO you will find probably tens of them). This is the reason, why to use alert suppression.

Think about where you save PowerShell script. By default script is used in security context of default Action Account (AA), so you need to ensure, that AA has at least read permission and in case you create some output log into text file AA needs write permission as well.

Also consider that same AA is used for translating GPO GUID to GPO name. It means that AA needs at least read permission on each Group Policy Object. By default on each GPO is set delegation for group Authenticated Users with read permissions, if you use AA as standard domain account it should be fine. Do not forget consider this aspect in multi domain or in multi forest environment.
        
Do not forget to grant to AA account in SCOM at least user role “Operations Manager Operators”. The reason is that by default AA does not have permission to close alerts.

(My test lab was build on Windows Server 2008 R2 and SCOM 2007 R2.)