Link directory

Learn how to monitor group memberships with PowerShell

Controlling privileged group memberships is an important task that PowerShell automation can handle with minimal effort.

The easiest approach to managing members of groups, such as domain administrators in Active Directory or the local administrators group on a server, is to control access to group management and keep your fingers crossed for let no one be mistaken. However, in larger environments where many people have administrative access to these groups, crossing your fingers is not a good policy. With PowerShell, we can easily write a script to monitor, and even enforce, these group memberships.

For Windows and Active Directory servers, the approach to managing these groups will be similar. First, we need to maintain a list of groups to monitor, then get the members of each group, and finally check for any changes since the script was last run.

How to Gather Local Group Memberships on Windows Server

On a local server, the list of groups is probably quite short. For this article, we’ll be working with just two groups – administrators and remote desktop users – but you can expand your coverage to other groups by adjusting the script accordingly.

Start the script with a variable to hold the groups, then add a foreach loop to loop through each of those groups and return group membership.

$groups = @(
    'Administrators'
    'Remote Desktop Users'
)
$groupMembers = foreach ($group in $groups) {
    Get-LocalGroupMember $group
}

We can now examine the output of $groupMembers and see the members of each of these groups (Figure 1).

Figure 1. The results of the group membership collection script on the server lists local administrators.

If you wanted to take those memberships and send them as an email report, you could. But we can use PowerShell to monitor group memberships and send notifications when changes occur.

To get started, export memberships to a CSV file and then upload it to compare the previous run with the current run. To do this, use two additional foreach loops: the first will search for users who have been added and the second will search for users who have been deleted.

foreach ($group in $groups) {
    $previousRunMemberships = $null
    if (Test-Path "C:tmp$group.csv") {
        $previousRunMemberships = Import-Csv -Path "C:tmp$group.csv"
    }
    $groupMembers = Get-LocalGroupMember $group
    $added = foreach ($member in $groupMembers) {
        if ($previousRunMemberships.SID -notcontains $member.SID) {
            $member
        }
    }
    $removed = foreach ($member in $previousRunMemberships) {
        if ($groupMembers.SID -notcontains $member.SID) {
            $member
        }
    }
   $groupMembers | Export-Csv -Path "C:tmp$group.csv" -NoTypeInformation
}

The script will display current users in a CSV file which will be used to compare current group memberships to a previous state.

The next step adds the notification. There are many options, such as the Send-MailMessage command, but this tutorial will use Azure Logic Apps to route your notification.

First, create a function and include it at the top of the script, like so:

Function Send-LogicAppEmail {
    param (
        [string]$LogicAppUri = '',
        [string]$To = '[email protected]',
        [string]$CC,
        [string]$Subject,
        [string]$Message
    )
    $headers = @{
        'Content-Type' = 'application/json'
    }
    $body = @{
        To = $To
        CC = $CC
        Subject = $Subject
        Body = $Message
    }
    $splat = @{
        Uri = $LogicAppUri
        Method = 'POST'
        Headers = $headers
        Body = ($body | ConvertTo-Json)
    }
    Invoke-RestMethod @splat
}

Then construct the HTML code and call this function in the foreach loop.

    if ($added -or $removed) {
        $html = @"

$($env:COMPUTERNAME)

$group

Added

$($added | Format-Table | Out-String)

Removed

$($removed | Format-Table | Out-String)
"@ Send-LogicAppEmail -To '[email protected]' -Subject "Changes to $group on $($env:COMPUTERNAME)" -Message $html }

Figure 2 shows an example email indicating that a user has been added to the Remote Desktop Users group.

email notification
Figure 2. The notification displays the list of users who have been added or removed from the Remote Desktop Users group.

The following is the complete group membership monitoring script.

Function Send-LogicAppEmail {
    param (
        [string]$LogicAppUri = '',
        [string]$To = '[email protected]',
        [string]$CC,
        [string]$Subject,
        [string]$Message
    )
    $headers = @{
        'Content-Type' = 'application/json'
    }
    $body = @{
        To = $To
        CC = $CC
        Subject = $Subject
        Body = $Message
    }
    $splat = @{
        Uri = $LogicAppUri
        Method = 'POST'
        Headers = $headers
        Body = ($body | ConvertTo-Json)
    }
    Invoke-RestMethod @splat
}
$groups = @(
    'Administrators'
    'Remote Desktop Users'
)
foreach ($group in $groups) {
    $previousRunMemberships = $null
    if (Test-Path "C:tmp$group.csv") {
        $previousRunMemberships = Import-Csv -Path "C:tmp$group.csv"
    }
    $groupMembers = Get-LocalGroupMember $group
    $added = foreach ($member in $groupMembers) {
        if ($previousRunMemberships.SID -notcontains $member.SID) {
            $member
        }
    }
    $removed = foreach ($member in $previousRunMemberships) {
        if ($groupMembers.SID -notcontains $member.SID) {
            $member
        }
    }
    if ($added -or $removed) {
        $html = @"

$($env:COMPUTERNAME)

$group

Added

$($added | Format-Table | Out-String)

Removed

$($removed | Format-Table | Out-String)
"@ Send-LogicAppEmail -To '[email protected]' -Subject "Changes to $group on $($env:COMPUTERNAME)" -Message $html } $groupMembers | Export-Csv -Path "C:tmp$group.csv" -NoTypeInformation }

Take this script, save it to a local path on each server, then call it periodically with a scheduled task.

How to Monitor Group Memberships in Active Directory

The process for monitoring groups in Active Directory is similar to the steps for monitoring local groups on server systems. The only difference is that we will be using the Active Directory module commands, which will require the following script to run either on a domain controller or on a device that has the Active Directory module installed and is connected to the domain controller. domain.

First, set the groups table to reference Active Directory groups. Add as many as you want, but for this example we’ll use the following two groups:

$groups = @(
    'Domain Admins'
    'Schema Admins'
)

Use the Get-ADGroupMember command to gather group members.

$groupMembers = Get-ADGroupMember $group

Finally, adjust the HTML message and reference Active Directory.

    if ($added -or $removed) {
        $html = @"

Active Directory

$group

Added

$($added | Format-Table | Out-String)

Removed

$($removed | Format-Table | Out-String)
"@ Send-LogicAppEmail -To '[email protected]' -Subject "Changes to $group in Active Directory" -Message $html }

Now the complete script will look like this:

Function Send-LogicAppEmail {
    param (
        [string]$LogicAppUri = '',
        [string]$To = '[email protected]',
        [string]$CC,
        [string]$Subject,
        [string]$Message
    )
    $headers = @{
        'Content-Type' = 'application/json'
    }
    $body = @{
        To = $To
        CC = $CC
        Subject = $Subject
        Body = $Message
    }
   $splat = @{
        Uri = $LogicAppUri
        Method = 'POST'
        Headers = $headers
        Body = ($body | ConvertTo-Json)
    }
    Invoke-RestMethod @splat
}
$groups = @(
    'Domain Admins'
    'Schema Admins'
)
foreach ($group in $groups) {
    $previousRunMemberships = $null
    if (Test-Path "C:tmp$group.csv") {
        $previousRunMemberships = Import-Csv -Path "C:tmp$group.csv"
    }
    $groupMembers = Get-ADGroupMember $group
    $added = foreach ($member in $groupMembers) {
        if ($previousRunMemberships.SID -notcontains $member.SID) {
            $member
        }
    }
    $removed = foreach ($member in $previousRunMemberships) {
        if ($groupMembers.SID -notcontains $member.SID) {
            $member
        }
    }
    if ($added -or $removed) {
        $html = @"

Active Directory

$group

Added

$($added | Format-Table | Out-String)

Removed

$($removed | Format-Table | Out-String)
"@ Send-LogicAppEmail -To '[email protected]' -Subject "Changes to $group in Active Directory" -Message $html } $groupMembers | Export-Csv -Path "C:tmp$group.csv" -NoTypeInformation }

Figure 3 shows the email notification generated by the script.

e-mail notification Active Directory Change
Figure 3. The email notification displays the name of the user who has been added to the Domain Admins group in Active Directory.

The email indicates that a user with the username test has been added to the domain administrators group.

This script can also be run periodically through the task scheduler.

Why monitor group memberships with a PowerShell script?

PowerShell may not look like a fancy monitoring product, but it can definitely meet your needs if you take the time to learn how to write and manage a script. A more sophisticated version of the script can go even further and remove users who shouldn’t be in the group or add users who were accidentally deleted. PowerShell is flexible enough to handle these tasks and let you focus on other tasks.