Compare commits

..

3 Commits

3 changed files with 262 additions and 1 deletions
+118
View File
@@ -0,0 +1,118 @@
<#
.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
+143
View File
@@ -0,0 +1,143 @@
<#
.SYNOPSIS
Lists all Active Directory users who have not logged on for more than 90 days
(default) and are not disabled.
.DESCRIPTION
Uses Active Directory cmdlets to find all users and checks their last logon
date. Users who have had no active logon for longer than the configured
number of days (default 90) are returned.
.PARAMETER Days
Number of days after which a user is considered inactive. Defaults to 90.
.PARAMETER IncludeDisabled
If set, the script also includes DISABLED users in the results.
.PARAMETER SearchBase
If set, the script searches for users only within the specified OU(s). This
is an array, so multiple OUs can be supplied; each one is searched
recursively and the results are combined.
.EXAMPLE
.\Get-InactiveUsers.ps1
Displays users who have not been active for more than 90 days.
.EXAMPLE
.\Get-InactiveUsers.ps1 | Sort-Object LastLogonDate | Format-Table -AutoSize
Displays users who have not been active for more than 90 days, sorted by
last logon date.
.EXAMPLE
.\Get-InactiveUsers.ps1 -Days 180
Displays users who have not been active for more than 180 days.
.EXAMPLE
.\Get-InactiveUsers.ps1 -Days 365 -IncludeDisabled
Displays users who have not been active for more than 365 days, including
DISABLED users.
.EXAMPLE
.\Get-InactiveUsers.ps1 -SearchBase "OU=Users,DC=example,DC=com"
Displays users who have not been active for more than 90 days, searching
only within the specified OU.
.EXAMPLE
.\Get-InactiveUsers.ps1 | Export-Csv -Path .\Export_InactiveUsers.csv -NoTypeInformation -Encoding UTF8
Exports all inactive users in Active Directory to a CSV file.
.EXAMPLE
.\Get-InactiveUsers.ps1 | Set-ADUser -Enabled $false
Disables all inactive users in Active Directory.
.NOTES
Author: Petr Štěpán
Created: 2024-10-20
Version: 1.1.0
Changelog:
1.1.0 - Fixed -SearchBase to correctly support multiple OUs (Get-ADUser's
own -SearchBase only accepts one); added standard header,
Set-StrictMode/$ErrorActionPreference, error handling, exit codes.
1.0.0 - Initial version
#>
#Requires -Modules ActiveDirectory
[CmdletBinding()]
param (
[Parameter(Mandatory = $false, HelpMessage = 'Number of days after which a user is considered inactive.')]
[ValidateRange(15, [int]::MaxValue)]
[int]
$Days = 90,
[Parameter(Mandatory = $false, HelpMessage = 'Also include DISABLED users?')]
[switch]
$IncludeDisabled,
[Parameter(Mandatory = $false, HelpMessage = 'Search for users only within the specified OU(s).')]
[ValidateNotNullOrEmpty()]
[string[]]
$SearchBase
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Read-only script (queries Active Directory only) - no ShouldProcess needed.
# The caller decides what to do with the results (export, disable, ...) via the pipeline.
$propertiesToRetrieve = 'Name', 'SamAccountName', 'LastLogonDate', 'Enabled', 'PasswordNeverExpires', 'CanonicalName', 'DistinguishedName'
# Date beyond which a user is considered inactive
$inactiveDate = (Get-Date).AddDays(-$Days)
if ($IncludeDisabled) {
$parameters = @{
Filter = {
LastLogonDate -le $inactiveDate
}
}
}
else {
$parameters = @{
Filter = {
LastLogonDate -le $inactiveDate -and Enabled -eq $true
}
}
}
if ($SearchBase) {
# Get-ADUser's own -SearchBase only accepts a single OU, so each requested
# OU is queried separately and the results are combined.
$inactiveUsers = [System.Collections.Generic.List[object]]::new()
$failedOuCount = 0
foreach ($ou in $SearchBase) {
Write-Verbose "Searching for inactive users in OU '$ou'..."
try {
$ouUsers = Get-ADUser @parameters -SearchBase $ou -Properties $propertiesToRetrieve -ErrorAction Stop
foreach ($u in $ouUsers) { $inactiveUsers.Add($u) }
}
catch {
Write-Warning "Skipping OU '$ou': failed to query users - $_"
$failedOuCount++
}
}
if ($failedOuCount -eq $SearchBase.Count) {
Write-Error "Failed to retrieve users from any of the specified OUs."
exit 1
}
}
else {
Write-Verbose "Searching for inactive users across the entire domain..."
try {
$inactiveUsers = @(Get-ADUser @parameters -Properties $propertiesToRetrieve -ErrorAction Stop)
}
catch {
Write-Error "Failed to query inactive users from Active Directory: $_"
exit 1
}
}
$inactiveUsers | Select-Object -Property $propertiesToRetrieve
exit 0
+1 -1
View File
@@ -42,7 +42,7 @@
.\Get-PasswordChangeHistory.ps1 -SamAccountName "jnovak" -ExportPath "C:\reports\jnovak.csv" -WhatIf
.NOTES
Author: Petr Stepan
Author: Petr Štěpán
Created: 2026-07-21
Version: 1.0.0
Changelog: