Monitoring Domain Group Membership Changes

I encountered a scenario at work where I had to monitor any changes performed on an account’s active directory membership groups on a daily/weekly basis. I believe there may be some out-of-box-experience solutions for this; however, I was recently moved into a new environment and I felt the quickest way to achieve this was through Powershell.

The script’s logic works around two text files, the first text file has a dump of all the group memberships at a certain time period, let’s say at the beginning of the month. The second file is a dump of all group memberships at the time of the script’s execution – the script works by comparing the newly created dump file of group memberships with the older dump file and displaying the differences if any. Since the second file is generated each time the script is executed, the script will also delete it upon exit, allowing for a clean slate for the next execution of the script. Lastly, the script will also email the results to you via SMTP protocol for a close to real-time notification.

I then set this script to run within a Windows Scheduled Task on a daily basis so that each day I would receive an email of any changes to the domain account.

Also, please keep in mind that in order to use the Get-ADUser cmdlet, you need to do Import-Module ActiveDirectory. If you try to Import-Module ActiveDirectory and it fails then you may need to enable the “Active Directory Module for Windows PowerShell” under Remote Server Administration Tools in Add/Remove Windows Features within your Control Panel. There are a handful of internet blogs that explain this step.

Here are some snippets of the script’s execution:

The script found no differences between the two files.

The script found differences in the two files, note that if there are differences, they are only shown in the email sent.

Here the first file was not found and the script prompted the user to create it. After it was created, the script went on to check the differences if any.

#fileName1 gathers your current domain group memberships and puts it into a file. It is ideal to keep this file
#as a starting point to which you can compare against in the event that you group membership changes.
$fileName1 = "currentAD.txt"

#fileName2 gathers your current domain group memberships each time you execute the script and compares it
#against your fileName1 file. Any changes between the two files will be compared and displayed.
$fileName2 = "currentADtemp.txt"

#This is a global variable to keep track of the contents of $checkDifference variable
#which contains the difference between $fileName1 and $fileName2
$Global:checkDifference = $checkDifference

function emailComparison ($checkDifference)
{   #Emails the results of the comparison via SMTP
    $From = "youruser@yourdomain.com"
    $To = "youruser@yourdomain.com"
    $Subject = "Domain Group Membership Change Report"
    $Body = $checkDifference
    $SMTPServer = "yourmailserver.yourdomain.com"
    $SMTPPort = "25" #This is the default port, if yours is different, you'll need to change it here.#
    Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer
    Write-Host "Email sent successfully..." -Foregroundcolor Green

    #Delete the temp file after email is sent.
    #This file is generated each time the script is executed.
    rm H:\Code\currentADtemp.txt
}

function comparingFiles ($fileName1, $fileName2)
{
    #Performs the comparison between the two files
    $checkDifference = Compare-Object (gc $fileName1) (gc $fileName2)
    
    #Adds text to output to show which file has the difference
    $checkDifference | foreach {
        if ($_.sideindicator -eq '<=')
            {$_.sideindicator = $fileName1}
        if ($_.sideindicator -eq '=>')
            {$_.sideindicator = $fileName2}
    }

    #If content is $checkDifference is not empty or $NULL
    if ($checkDifference)
    {   #Formats the content of $checkDifference into list format
        #Out-String changes the content from object to string
        $checkDifference = ( $checkDifference | Format-List | Out-String )

        #Output to console
        Write-Host "Comparison of files completed successfully..." -Foreground Green

        #Function call to emailComparison which handles the actual generation of the email message
        emailComparison $checkDifference
        
    }

    else
    {   #If there are no differences, you will still get emailed but with the below text
        $output = "There are no differences between $fileName1 and $fileName2."
        emailComparison $output
        Write-Host $output
    }
}
function mainFunction ()
{   #This is the main part of the script, writing it as a function, allows me to start the script again within the script
    #Checks if $fileName1 file exists
    if (Test-Path $fileName1)
    {
        #Takes content of currentAD.txt file into $file1Content variable
        $file1Content = gc .\currentAD.txt

        #Checks if $fileName2 file exists
        if (Test-Path $fileName2)
        {   #Takes content of currentADtemp.txt file into $file2Content variable
            $file2Content = gc .\currentADtemp.txt
            
            #Function call passing $fileName1 and $fileName2 variables as arguments
            comparingFiles $fileName1 $fileName2
        }
        else 
        {
            #Condition to create $fileName2 if it does not exist.
            Write-host "Creating $fileName2..."
            (Get-ADUser -Identity "domainuser" -Properties MemberOf | Select-Object MemberOf).MemberOf > $fileName2
            $file2Content = gc .\currentADtemp.txt
            
            if (Test-Path $fileName2)
            {   #Test condition to check if $fileName2 exists
                Write-Host "$fileName2 was created successfully!" -ForegroundColor Green
                #Function call passing $fileName1 and $fileName2 variables as arguments
                comparingFiles $fileName1 $fileName2
            }
            else 
            {   #If creation of $fileName2 failes for whatever reason
                Write-Host "$fileName2 creation failed!" -ForegroundColor Red
            }
            
        }
    }

    else
    {   #Condition to handle if $fileName1 does not exist
        #Provides user with option to create the file during script execution
        Write-Host "$fileName1 not found!" -ForegroundColor Red
        $currentADtext = Read-Host "Do you want to create a new currentAD.txt? (Y/N)"

        #Condition to allow script to decide if file creation should occur or not
        if ($currentADtext -eq 'Y' -or $currentADtext -eq 'y')
        {
            (Get-ADUser -Identity "domainuser" -Properties MemberOf | Select-Object MemberOf).MemberOf > $fileName1
            
            Write-Host "Creating a new currentAD.txt file..."
            Write-Host "Restarting script to consume newly created currentAD.txt file...`n"

            #Once $fielName1 is created, by calling the script's main function again,
            #it prevents user from having to re-run the script themselves
            mainFunction

        }

        #Condition if file creation should not occur
        elseif ($currentADtext -eq 'N' -or $currentADtext -eq 'n')
        {
            Write-Host "File creation refused by user...`n" -ForegroundColor Yellow
        }

        #Condition to handle invalid input
        else
        {
            Write-Host "Invalid input received for currentAD.txt creation prompt!`n" -ForegroundColor Red

        }    
    }
}

#Script starts here!#
#I am calling the mainFunction here, this actually starts the program upon execution#
mainFunction

 

Leave a Reply

avatar
  Subscribe  
Notify of