Files
ActiveDirectory/Get-EmptySecurityGroups.ps1
T

119 lines
3.9 KiB
PowerShell

<#
.SYNOPSIS
Lists all Active Directory security groups that have no members.
.DESCRIPTION
Uses Active Directory cmdlets to retrieve every security group and checks
whether each one has any members. Groups with no members are returned as
objects (GroupName, CanonicalName, DistinguishedName). By default the whole
domain is searched; optionally the search can be limited to one or more
Organizational Units, searched recursively.
.PARAMETER SearchBase
One or more OU distinguished names to search recursively for security
groups. Optional - if omitted, the entire domain is searched.
.EXAMPLE
.\Get-EmptySecurityGroups.ps1
Displays all empty security groups in Active Directory.
.EXAMPLE
.\Get-EmptySecurityGroups.ps1 | Export-Csv -Path .\Export_EmptySecurityGroups.csv -NoTypeInformation -Encoding UTF8
Exports all empty security groups in Active Directory to a CSV file.
.EXAMPLE
.\Get-EmptySecurityGroups.ps1 -SearchBase "OU=Sales,DC=contoso,DC=com"
Searches recursively only within the Sales OU.
.EXAMPLE
.\Get-EmptySecurityGroups.ps1 -SearchBase "OU=Sales,DC=contoso,DC=com","OU=IT,DC=contoso,DC=com"
Searches recursively within multiple OUs.
.NOTES
Author: Petr Štěpán
Created: 2024-10-20
Version: 1.1.0
Changelog:
1.1.0 - Added optional -SearchBase parameter for recursive search within specific OUs.
1.0.0 - Initial version
#>
#Requires -Modules ActiveDirectory
[CmdletBinding()]
param (
[Parameter(Mandatory = $false, HelpMessage = 'One or more OU distinguished names to search recursively. Defaults to the entire domain.')]
[ValidateNotNullOrEmpty()]
[string[]]
$SearchBase
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Read-only script (queries Active Directory only) - no ShouldProcess needed.
$emptyGroups = [System.Collections.Generic.List[PSCustomObject]]::new()
$counter = 0
if ($SearchBase) {
$securityGroups = [System.Collections.Generic.List[object]]::new()
$failedOuCount = 0
foreach ($ou in $SearchBase) {
Write-Verbose "Retrieving security groups from OU '$ou' (recursive)..."
try {
$ouGroups = Get-ADGroup -Filter { GroupCategory -eq 'Security' } -SearchBase $ou -SearchScope Subtree -ErrorAction Stop
foreach ($g in $ouGroups) { $securityGroups.Add($g) }
}
catch {
Write-Warning "Skipping OU '$ou': failed to query security groups - $_"
$failedOuCount++
}
}
if ($failedOuCount -eq $SearchBase.Count) {
Write-Error "Failed to retrieve security groups from any of the specified OUs."
exit 1
}
}
else {
Write-Verbose "Retrieving all security groups from Active Directory..."
try {
$securityGroups = @(Get-ADGroup -Filter { GroupCategory -eq 'Security' } -ErrorAction Stop)
}
catch {
Write-Error "Failed to retrieve security groups from Active Directory: $_"
exit 1
}
}
foreach ($group in $securityGroups) {
$counter++
Write-Progress -Activity "Checking group $($group.Name)" -Status "Processing $counter of $($securityGroups.Count)" -PercentComplete (($counter / $securityGroups.Count) * 100)
try {
$members = @(Get-ADGroupMember -Identity $group.DistinguishedName -ErrorAction Stop)
}
catch {
Write-Warning "Skipping group '$($group.Name)': failed to read members - $_"
continue
}
if ($members.Count -eq 0) {
# Record the empty group as a structured object
$emptyGroups.Add([PSCustomObject]@{
GroupName = $group.Name
Description = $group.Description
DistinguishedName = $group.DistinguishedName
})
}
}
Write-Progress -Activity "Checking group" -Completed
if ($emptyGroups.Count -gt 0) {
$emptyGroups
} else {
Write-Host "No empty security groups were found."
}
exit 0