Skip to content

Instantly share code, notes, and snippets.

@webash
Last active April 23, 2024 03:03
Show Gist options
  • Save webash/b34c5a422288827ff4e53318e34c6923 to your computer and use it in GitHub Desktop.
Save webash/b34c5a422288827ff4e53318e34c6923 to your computer and use it in GitHub Desktop.
Detect and correct orphaned 'adminCount=1' users who are no longer in protected groups
<#
.SYNOPSIS
Detects Orphaned SD Admin users, resets admin count attribute and enables inheritable permissions
.Author
Alan.McBurney (+ Ashley Steel)
THIS CODE IS MADE AVAILABLE AS IS, WITHOUT WARRANTY OF ANY KIND. THE ENTIRE
RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.
Version 1.0, July 10th, 2014
.DESCRIPTION
This script gets all users that are members of protected groups within AD and compares
membership with users that have the AD Attribute AdminCount=1 set.
If the user has the AdminCount=1 enabled but is not a member of a protected group then the user
is considered an orphaned admin user and the AdminCount is reset to 0 and inheritable permissions
are reset
.REFERENCES
"http://blogs.technet.com/b/heyscriptingguy/archive/2010/07/11/hey-scripting-guy-weekend-scripter-checking-for-module-dependencies-in-windows-powershell.aspx">http://blogs.technet.com/b/heyscriptingguy/archive/2010/07/11/hey-scripting-guy-weekend-scripter-checking-for-module-dependencies-in-windows-powershell.aspx</a>
"http://blogs.msdn.com/b/muaddib/archive/2013/12/30/how-to-modify-security-inheritance-on-active-directory-objects.aspx">http://blogs.msdn.com/b/muaddib/archive/2013/12/30/how-to-modify-security-inheritance-on-active-directory-objects.aspx</a>
.EXAMPLE
$orphans = Get-OrphanAdminSdHolderUsers -OutputToPsHost
$orphans | Clear-OrphanAdminSdHolderUser
.Notes
To Do list: Enable logging
Originally acquired from here: https://everythingsysadmin.wordpress.com/2014/08/27/fixing-orphaned-adminsdholder-accounts/
#>
#Check to Ensure Active Directory PowerShell Module is available within the system
Function Get-MyModule
{
Param([string]$name)
if(-not(Get-Module -name $name))
{
if(Get-Module -ListAvailable |Where-Object { $_.name -eq $name })
{
Import-Module -Name $name
$True | Out-Null
}
else
{
Write-Host ActiveDirectory PowerShell Module Not Available -ForegroundColor Red
}
} # end if not module
else
{
$True | Out-Null
} #module already loaded
} #end function get-MyModule
Get-MyModule -name "ActiveDirectory"
Function Set-Inheritance
{
[cmdletbinding()]
Param($DistinguishedName)
$Acl = Get-ACL -Path ("AD:\{0}" -f $DistinguishedName)
If ($Acl.AreAccessRulesProtected -eq $True)
{
$Acl.SetAccessRuleProtection($False, $True)
Set-ACL -AclObject $ACL -Path ("AD:\{0}" -f $DistinguishedName);
}
}
Function Get-FlaggedAsAdminGroups {
Get-ADGroup -LDAPFilter "(adminCount=1)"
}
Function Get-FlaggedAsAdminUsers {
Get-ADUser -LDAPFilter "(adminCount=1)"
}
Function Get-AdminGroupUsers {
$RawAdminUsers = ForEach ($Group in Get-FlaggedAsAdminGroups) {
# Get all users from all admin groups recursively
Get-ADGroupMember $Group -Recursive | Where-Object {$_.ObjectClass -eq "User"}
# ...then sort them by distinguishedName to ensure accurate -Unique results (because some users might be in multiple protected groups)
}
$RawAdminUsers | Sort-Object distinguishedname | Select-Object -Unique;
}
Function Get-OrphanAdminSdHolderUsers {
[cmdletbinding()]
param(
[switch]$OutputToPsHost
)
#Get List of Admin Users (Past and Present)
$UsersFlaggedAsAdmin = Get-FlaggedAsAdminUsers
$UsersInAdminGroups = Get-AdminGroupUsers;
#Compare $AdminUsers to $Admins and place in appropriate hash table
$OrphanedUsers = ForEach ($User in $UsersFlaggedAsAdmin)
{
If ($UsersInAdminGroups.samAccountName -notcontains $User.samAccountName) {
if ( $OutputToPsHost.IsPresent ) {
Write-Host ("ORPHAN`t`t{0}" -f $User.samAccountName);
}
$User;
} else {
if ( $OutputToPsHost.IsPresent ) {
Write-Host ("STILL ADMIN`t{0}" -f $User.samAccountName);
}
}
}
return $OrphanedUsers;
}
Function Clear-OrphanAdminSdHolderUser {
[cmdletbinding()]
param(
[parameter(ValueFromPipeline)]
[Microsoft.ActiveDirectory.Management.ADPrincipal[]]$OrphanUser,
[bool]$Confirm = $true
)
Begin {
}
Process {
if ( $OrphanUser.SamAccountName -eq "krbtgt" ) {
Write-Warning "krbtgt has been skipped; it's unlikely you actually wish to demote this from being a protected user";
} else {
$Proceed = $false;
if ( $Confirm ) {
$Proceed = (Read-Host ("Do you wish to clear adminCount from {0}? Y or anything else" -f $OrphanUser.samAccountName)).toLower() -eq "y";
} else {
$Proceed = $true;
}
if ( $Proceed ) {
Write-Host ("{0}: Clearing AdminCount..." -f $OrphanUser.SamAccountName) -NoNewline;
$OrphanUser | Set-ADUser -Clear {AdminCount} -ErrorAction Continue -ErrorVariable ClearError;
if ( $ClearError.Count -gt 0 ) {
Write-Error ("{0} | Set-ADUser -Clear {AdminCount} failed. See error above" -f $OrphanUser.samAccountName);
exit;
} else {
Write-Host "OK...Enabling Inheritence..." -NoNewline;
Set-Inheritance $OrphanUser -ErrorAction Continue -ErrorVariable InheritenceError;
if ( $InheritenceError.Count -gt 0) {
Write-Error ("Set-Inheritence -DistinguishedName {0} failed. See error above" -f $OrphanUser.distinguishedname);
exit;
} else {
Write-Host "OK";
}
}
} else {
Write-Host ("{0} skipped" -f $OrphanUser.samAccountName)
}
}
}
}
@RyRyTech-Cpic
Copy link

RyRyTech-Cpic commented Jun 16, 2021

I really like the updates you made to this script over the original script posted back in 2014. The first thing I noticed was that this only gets and clears the accounts in the domain in which the computer its ran from resides in. If you have other domains with your domain forest, you'll miss out on those or need to run this again from a computer in those domains.

Lastly, I threw in another module that I love to use named ImportExcel by Doug Finke "https://github.com/dfinke/ImportExcel" to generate a more robust report before cleanup is made.

To get all domains, I added a few extra details and updates your existing functions.
Now I'm trying to figure out how to get the PSDrive for AD:\ to configure all domains as well.
Variables

$Forest = <YourDomainForest>
$domains = (Get-ADForest -Server $forest).Domains
$today = Get-Date -format 'MM-dd-yyyy_hhmmmss'
$Report = "C:\Reports\ProtectedAdmins_$($Today).xlsx"

Updated Code

Function Get-FlaggedAsAdminGroups {
	[cmdletbinding()]
	Param($Domain)
	Get-ADGroup -LDAPFilter "(adminCount=1)" -Server $Domain
}

Function Get-FlaggedAsAdminUsers {
    [cmdletbinding()]
	Param($Domain)
	Get-ADUser -LDAPFilter "(adminCount=1)" -Server $Domain
}

Function Get-AdminGroupUsers {
    [cmdletbinding()]
	Param($Domain)
	$RawAdminUsers = ForEach ($Group in Get-FlaggedAsAdminGroups -Domain $Domain) {
		# Get all users from all admin groups recursively
		Get-ADGroupMember $Group -Recursive -Server $Domain | Where-Object {$_.ObjectClass -eq "User"}
		# ...then sort them by distinguishedName to ensure accurate -Unique results (because some users might be in multiple protected groups)
	}
	
	$RawAdminUsers | Sort-Object distinguishedname | Select-Object -Unique;
}

Function Get-OrphanAdminSdHolderUsers {
	[cmdletbinding()]	
	param(
		[switch]$OutputToPsHost,
        $Domain
	)
	
	#Get List of Admin Users (Past and Present)
	$UsersFlaggedAsAdmin = Get-FlaggedAsAdminUsers $Domain
    $UsersFlaggedAsAdmin | Export-Excel -Path $Report -Append -AutoSize -WorksheetName Admins -TableName AdminsTable -TableStyle Medium2
	
	$GroupsFlaggedAsAdmin = Get-FlaggedAsAdminGroups $Domain;
    $GroupsFlaggedAsAdmin | Export-Excel -Path $Report -Append -AutoSize -WorksheetName AdminGroups -TableName AdminGroupsTable -TableStyle Medium2

	$UsersInAdminGroups = Get-AdminGroupUsers $Domain;
    $UsersInAdminGroups | Export-Excel -Path $Report -Append -AutoSize -WorksheetName AdminGroupUsers -TableName AdminGroupUsersTable -TableStyle Medium2
	
	#Compare $AdminUsers to $Admins and place in appropriate hash table
	$OrphanedUsers = ForEach ($User in $UsersFlaggedAsAdmin)
	{
		If ($UsersInAdminGroups.samAccountName -notcontains $User.samAccountName) {
			if ( $OutputToPsHost.IsPresent ) {
				Write-Host ("ORPHAN`t`t{0}" -f $User.samAccountName);
			}
			$User;
		} else {
			if ( $OutputToPsHost.IsPresent ) {
				Write-Host ("STILL ADMIN`t{0}" -f $User.samAccountName);
			}
		}
	}
    $OrphanedUsers | Export-Excel -Path $Report -Append -AutoSize -WorksheetName OrphanedUsers -TableName OrhpanedUsersTable -TableStyle Medium2
	return $OrphanedUsers;
}

Foreach ($Domain in $domains) {
    Get-OrphanAdminSdHolderUsers -Domain $Domain
}

@webash
Copy link
Author

webash commented Jul 5, 2021

Nice one!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment