Compare commits
3 Commits
38932b0384
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| be6c355856 | |||
| 7aeb3b5553 | |||
| df802f9899 |
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user