Forum Discussion
Get Privileged User Accounts and then associate those names to their AD Groups.... ???
Hi Lain,
I actually don't have the Group Names.  I need to know elevated privilege accounts and then associate those users to groups....
I'm not a Powershell expert so I am not sure what the string would look like. Would you be so kind as to let me know? Much appreciated your answer previously.
Scott
Hi, Scott.
The privileged groups can be found listed here:
Have you started writing your script, or are you looking for someone in here to write it for you?
Cheers,
Lain
- Scott_AZSep 03, 2023Copper Contributor
Hello Lain,
First, thank you for your reply. Although I do know a fairly easy way to find the groups a user belongs to in the script below (though there is a slight hiccup with two word service accounts) it does suit my needs. However..... if I could pull everything together in a script (Privileged Accounts and the accounts group memberships in csv format....) that would be perfect.
So that is where I am lost right now and yes, I could use help with writing the script.
Scott
PS -- I found the following script that give me the memberships of a given user....Add-Type -AssemblyName System.DirectoryServices.AccountManagement $username = read-host -prompt "Enter a username" $ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($ct, $username) $groups = $user.GetGroups() foreach($i in $groups){ $i.SamAccountName }
- LainRobertsonSep 04, 2023Silver Contributor
Hi, Scott.
The problem you're trying to solve is moderately complex, and that script is a very long way for getting you to completion.
Logically, the problem you've outlined requires the following approach:
I've already provided the URL to the Active Directory protected groups as well as explained why the adminCount attribute is not trustworthy. These will serve as the prerequisites for the following solution.
I've decided to implement the logical flow from above as two "scripts".
First script
This script does all of the heavy lifting and almost provides the complete end-to-end solution.
It's easier to say what it does not do, which is flatten out the object stream into a CSV file - that's what the second "script" (which is really just a single piped command) will do.
One reason for this is that you can easily extend this first script to capture other supporting data, which I've done as shown in the output, where you can see other useful attributes have been included.
Another reason is that in returning objects rather than just dumping to a CSV file, it can be used within pipelines for further processing by other scripts/commands.
I named this script Get-ADPrivilegedIdentities.ps1 but you can name it whatever you like. I specifically avoided referencing "Users" since it will show any member type that isn't a group, meaning computers could show up.
One useful behaviour of the script is that it automatically excludes duplicate references to the same identity, since there's no value in listing the same identity more than once and it just makes the output harder to process.
Get-ADPrivilegedIdentities.ps1
[cmdletbinding()] Param() try { #region Global variables. $RootDSE = [adsi]"LDAP://RootDSE"; $Server = $RootDSE.dNSHostName[0]; $Domain = [adsi]"LDAP://$Server/$($RootDSE.defaultNamingContext)"; $DomainSid = [System.Security.Principal.SecurityIdentifier]::new($Domain.objectSid[0], 0).Value; $ProcessedUsers = [System.Collections.Generic.List[int]]::new(); $MemberOf = [System.Collections.Generic.List[string]]::new(); Write-Verbose -Message "Connected to $Server ..."; #endregion # Protected groups obtained from https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-c--protected-accounts-and-groups-in-active-directory#protected-accounts-and-groups-in-active-directory-by-operating-system. $ProtectedGroups = @( "S-1-5-32-548" # Account Operators , "S-1-5-32-544" # Administrators , "S-1-5-32-551" # Backup Operators , "$DomainSid-512" # Domain Admins , "$DomainSid-519" # Enterprise Admins , "S-1-5-32-550" # Print Operators , "$DomainSid-518" # Schema Admins , "S-1-5-32-549" # Server Operators ) #region Set up a directory searcher for each level of expansion. $Searcher = [adsisearcher]::new(); $Searcher.SearchRoot = $Domain; $Searcher.PropertiesToLoad.AddRange(@("distinguishedName")); $MemberSearcher = [adsisearcher]::new(); $MemberSearcher.SearchScope = [System.DirectoryServices.SearchScope]::Base; $MemberSearcher.PropertiesToLoad.AddRange(@("msds-memberTransitive", "name")); $ObjectSearcher = [adsisearcher]::new(); $ObjectSearcher.SearchScope = [System.DirectoryServices.SearchScope]::Base; $ObjectSearcher.PropertiesToLoad.AddRange(@("accountExpires", "distinguishedName", "msds-memberOfTransitive", "msDS-UserPasswordExpiryTimeComputed", "name", "objectClass", "objectGUID", "primaryGroupID", "userAccountControl", "userPrincipalName" )); #endregion $ProtectedGroups | ForEach-Object { Write-Verbose -Message "`nProcessing $_ ..."; #region Convert the SID string template to a byte array first, and then to a useable LDAP search string. $SecurityIdentifier = [System.Security.Principal.SecurityIdentifier]::new($_); $SidAsBytes = [byte[]]::new($SecurityIdentifier.BinaryLength); $SecurityIdentifier.GetBinaryForm($SidAsBytes, 0); $Searcher.Filter = "objectSid=\$([System.BitConverter]::ToString($SidAsBytes).Replace("-", "\"))"; Write-Verbose -Message "Search filter = $($Searcher.Filter)"; #endregion # Find the privileged group's distinguishedName. $Searcher.FindOne() | ForEach-Object { $dn = $_.Properties["distinguishedName"]; Write-Verbose -Message "Enumerating memberships from $dn ..."; try { #region Perform a base search using the distinguishedName of the privileged group to retrieve the transitive memberships. $MemberSearcher.SearchRoot = [adsi]"LDAP://$Server/$dn"; $MemberSearcher.Filter = "(objectClass=*)"; Write-Verbose -Message "Member search root = $($MemberSearcher.SearchRoot.Path)"; $MemberSearcher.FindOne() | ForEach-Object { #region Iterate through each member of the privileged group. foreach ($Entry in $_.Properties["msds-memberTransitive"]) { #region Check we haven't already processed this user, and skip them if we have. if ($ProcessedUsers.Contains(($Checksum = $Entry.GetHashCode()))) { Write-Verbose -Message "Skipping $Entry."; continue; } else { Write-Verbose -Message "Enumerating transitive ""memberOf"" references for: $Entry."; $ProcessedUsers.Add($Checksum); } #endregion #region Perform a base search against the member to retrieve a transitive list of groups it is a member of $ObjectSearcher.SearchRoot = [adsi]"LDAP://$Server/$Entry"; if ($null -ne ($Instance = $ObjectSearcher.FindOne())) { if (-not ($ObjectClass = $Instance.Properties["objectClass"][$Instance.Properties["objectClass"].Count-1]).Equals("group")) { #region Fetch the primary group details first. $PrimaryGroup = [adsi]"LDAP://$Server/<SID=$DomainSid-$($Instance.Properties["primaryGroupID"][0])>"; $MemberOf.Add($PrimaryGroup.Properties["distinguishedName"][0]); #endregion $MemberOf.AddRange([string[]] ($Instance.Properties["msds-memberOfTransitive"])); $MemberOf.Sort(); [PSCustomObject] @{ objectGUID = [guid]::new($Instance.Properties["objectGUID"][0]); objectClass = $ObjectClass; enabled = ($Instance.Properties["userAccountControl"][0] -band 0x2) -eq 0; name = $Instance.Properties["name"][0]; userPrincipalName = $Instance.Properties["userPrincipalName"][0]; neverExpires = $Instance.Properties["userPrincipalName"][0] -in (0, [int64]::MaxValue); passwordNeverExpires = $Instance.Properties["msDS-UserPasswordExpiryTimeComputed"][0] -eq [int64]::MaxValue; distinguishedName = $Instance.Properties["distinguishedName"][0]; memberOfTransitive = $MemberOf.ToArray(); } $MemberOf.Clear(); $PrimaryGroup.Dispose(); } } #endregion } #endregion } #endregion } catch [System.UnauthorizedAccessException] { Write-Warning -Message "Access denied on ""$dn"""; } catch { throw; } finally { if ($MemberSearcher.SearchRoot) { $MemberSearcher.SearchRoot.Dispose(); } } } } } catch { throw; } finally { $ProcessedUsers.Clear(); if ($Seacher) { $Searcher.Dispose(); } if ($Domain) { $Domain.Dispose(); } if ($RootDSE) { $RootDSE.Dispose(); } }Example output
Second script
This isn't a script at all, but just a single piped command.
In any case, this command simply takes the output from Get-ADPrivilegedIdentities.ps1, chooses a few attributes of interest and pipes it out to a CSV file.
Command
.\Get-ADPrivilegedIdentities.ps1 | ForEach-Object { $DirectoryObject = $_; $_.memberOfTransitive | ForEach-Object { [PSCustomObject] @{ objectClass = $DirectoryObject.objectClass; distinguishedName = $DirectoryObject.distinguishedName; memberOf = $_; } } } | Export-Csv -NoTypeInformation -Path "D:\Data\Temp\Forum\forum.csv";Example output
Cheers,
Lain
- Scott_AZSep 04, 2023Copper Contributor
Hi Lain !
WOW - this is phenomenal work on your part !!! I hope to compensate you somehow. I can see by your proficiency that you have years of experience. I am really impressed..... trying to think of a similar expertise.... It would be like me or you becoming a pilot and all the skill that goes with it.
So I ran script 1 and it "pulled" 5 records then stopped at "Cannot index into to null array...." Line 69 Col 29 (see sample output further below)This is very close to a solution I can tell.....
Scott
Here is a sample output........... just below this fifth record output to screen.....
objectGUID : ac44ac93-8f0f-4661-bd3b-04e07005b339
objectClass : user
enabled : True
name : sHempshire
userPrincipalName : email address removed for privacy reasons
neverExpires : False
passwordNeverExpires : False
distinguishedName : CN=sHempshire0,OU=Systems,OU=Employee
Accounts,OU=Users,OU=TSS,OU=POSoc,OU=Sites,DC=Clmr,DC=Mcopaset,DC=Edu
memberOfTransitive : {CN=Administrators,CN=Builtin,DC=Clmr,DC=Mcopaset,DC=Edu, CN=Deep Freeze
Admins,CN=Users,DC=Clmr,DC=Mcopaset,DC=Edu, CN=Denied RODC Password
Replication Group,CN=Users,DC=Clmr,DC=Mcopaset,DC=Edu, CN=DHCP
Administrators,CN=Users,DC=Clmr,DC=Mcopaset,DC=Edu...}Script Errored-Out here.........>>>
ForEach-Object : Cannot index into a null array.
At line:69 char:29
+ ForEach-Object {
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [ForEach-Object], RuntimeException
+ FullyQualifiedErrorId : NullArray,Microsoft.PowerShell.Commands.ForEachObjectCommand