Advice with get-adgroupmember
-
Wednesday, May 16, 2012 6:38 PM
So I dont think there is a way to fix this, its probably a limitation of AD/or the AD cmdlets but thought I would put it out there and ask the question...
I have the following code
function getGroupMembers($gname) { $thisgroup = "" | Select-Object Groupname,UMembers,GMembers $thisgroup.Groupname = $gname $thisgroup.UMembers=@() $thisgroup.GMembers=@() $thisgroup.UMembers = get-adgroupmember -server dc01 -identity $gname | ?{$_.objectClass -eq 'user'} $groups=get-adgroupmember -server dc01 -identity $gname | ?{$_.objectClass -eq 'group'} foreach($gmember in $groups) { write-host $gmember.name if ($gmember.name) { $childgroup=getGroupMembers $gmember.name $thisgroup.gMembers += $childgroup } } return $thisgroup } $mygroup=getGroupMembers 'LocalGroup01'
This does exactly what I want and works fine, it even works for ForeignSecurityPrincipals which to be honest I was surprised at, because if you just run Get-Adgroupmember in a powershell window it fails when it encounters a FSP. The problem I have is when the ForeignSecurityPrincipal is named the same as the group I am querying.
Example Domain1\LocalGroup01 has 3 members Domain1\GlobalGroup01, Domain2\GlobalGroup01, Domain3\GlobalGroup01, when the code above runs it returns an array containing Domain1\GlobalGroup01 in triplicate as it is just searching for the groupname, I tried to change the search to use the SID but this failed and ended up in a loop or errors.
Any help would be appreciated.
All Replies
-
Wednesday, May 16, 2012 7:54 PM
Why don't you try and explain what it is you are trying to accomplish. You are describing all kinds of scenarios and erros bu no error messages. Please understand that is is very difficult to follow what you are describing without the erro messages. Is is a suyntax error? Is it a null object error?
Don't make us guess.
¯\_(ツ)_/¯
-
Thursday, May 17, 2012 8:33 AM
Apologies I thought the code would explain what I am doing.
Basically what I want is an object that is an enumeration of an AD group, so I can out put the members of that group and any nested groups. I dont want to use recursive because I want to show which group the user belongs to and this could be 3 or 4 groups deep.
The environment this is being run on has 4 forests, so there is alot of nesting cross forest/domain (I should have explained this better)
The issues I have are as follows:
get-adgroupmember is slow, but I can live with this (VBS is alot faster)
If you have a local group with nested USERS from a different forest, when you display the object you get an error e.g:
$thisgroup = get-adgroupmember -server dc01 -identity 'LocalGroup_WithForeignUsers' | ?{$_.objectClass -eq 'user'} $thisgroup format-default : Exception getting "PSShowComputerName": "The adapter cannot ge t the value of property "PSShowComputerName"." + CategoryInfo : NotSpecified: (:) [format-default], GetValueInvo cationException + FullyQualifiedErrorId : CatchFromBaseAdapterGetValue,Microsoft.PowerShel l.Commands.FormatDefaultCommandStrangely you can access properties of the object tho example
PS c:\> $thisgroup[0].name Joe Bloggs
For me I can live with the error, although I dont understand why it occurs as the object is there and all of its properties.
Problems I cant live with are:
As discribed above, If I have a lot local groups with nested global groups from a trusted forests, if the groups are named the same as the source forest, the code I posted above only searches the source forest meaning I get duplicate data in my object (I think this is explained better in my initial post)
- Forest1\Domain1\LocalGroup01
- Forest1\Domain1\GlobalGroup01
- Forest2\Domain2\GlobalGroup01
- Forest3\Domain3\GlobalGroup01
This outputs an obect that looks like:
PS C:\scripts> $thisgroup.GMembers Groupname UMembers GMembers --------- -------- -------- GlobalGroup01 {CN=Bloggs\, Joe,OU=... {} GlobalGroup01 {CN=Bloggs\, Joe,OU=... {} GlobalGroup01 {CN=Bloggs\, Joe,OU=... {}As you can see the 3 groups are the same group from the source domain, I need for it to be the 3 different groups from the 3 different forests.
The next issue is probably the same as the first, but you actually get an error:
Imagine a group structure of
- Forest1\Domain1\LocalGroup01
- Forest1\Domain1\GlobalGroup01
- Forest2\Domain2\GlobalGroup01_Domain2
- Forest3\Domain3\GlobalGroup01_Domain3
So the foreign groups are now named differently than in the first example:
PS C:\scripts> $thisgroup = get-adgroupmember -server dc01 -identity 'LocalGroup01' | ?{$_.objectClass -eq 'group'} Get-ADGroupMember : The operation completed successfully At line:1 char:35 + $thisgroup = get-adgroupmember <<<< -server dc01 -identity 'LocalGroup01' | ?{$_.objectClass -eq 'group'} + CategoryInfo : NotSpecified: (LocalGroup01:ADGroup) [Get-ADGrou pMember], ADException + FullyQualifiedErrorId : The operation completed successfully,Microsoft.A ctiveDirectory.Management.Commands.GetADGroupMember PS C:\scripts> $thisgroup PS C:\scripts>This will happen with any ForeignSecurityPrinciple and makes the Get-ADGroupMember cmdlet useless in a large environment, I am sure there must be a way to get this to work.
Thanks in advance
-
Thursday, May 17, 2012 3:07 PM
It seems to me that you need to use the full grouyp DN to return the group and use the full DBN of the member to return the member. Names do not hav eto be unique across doamins and querying the server or GC with a partial name wil alway return the match from the current domain.
Switch to using DN and most of you problems should go away. YOu can alsways choose to save the full account name. 'dom\account'.
DNs are unique across forests and domains.
¯\_(ツ)_/¯
-
Friday, May 18, 2012 1:06 PM
I thought this to, however if you use the DN which will be the CN=ObjectSID,CN=ForeignSecurityPrinciples,DC=Domain1,DC=local you get an error
Get-ADGroupMember : Cannot find an object with identity: 'CN=OjectSID,CN=ForeignSecurityPrincipals,DC=Domain1,DC=local' u
nder: 'DC=Domain1,DC=local'.
At line:1 char:24
+ $temp=get-adgroupmember <<<< -server DC01 -identity $allobjects[2].disti
nguishedname.value
+ CategoryInfo : ObjectNotFound: (CN=ObjectSid...Domain1,DC=c
om:ADGroup) [Get-ADGroupMember], ADIdentityNotFoundException
+ FullyQualifiedErrorId : Cannot find an object with identity: 'CN=OjectSID,CN=ForeignSecurityPrincipals,DC=Domain1,DC=local' under: 'DC=Domain1,DC=local'.,Microsoft.ActiveDirectory.Management.Commands.GetADGroupMemberI think I have identified the issue now, but really have no way to sovle it, if I accept I cannot enumerate the groups from an FSP then my original code can be modified to pick up the object name which includes the domain, I can then just filter out the FSPs to begin with or maybe try and get the members of these groups a different way.
The issue is with get-groupmember where SIDHistory is enabled and the Local group contains the Original Group and the migrated version of that group (Hope that makes sense), If I look in ADU&C at one of these groups due to sid history the groups domain is shown as Domain1 even though the object is really in Domain2, if I run get-ADGroupMember on this group, the command will instantly fail.
- Forest1\Domain1\LocalGroup01 [SID=A, SIDHistory=B]
- Forest1\Domain1\GlobalGroup01 [SID=Z SIDHistory=X]
- Forest2\Domain2\GlobalGroup01 [SID=X No Sid history]
so looking at the members of LocalGroup01 you will see 2 members both members will display their domain as Domain1, even tho the Icon for the second group shows the arrow to denote it is in a different domain. Really hope I have explained that well enough, most of our groups are like this as we are migrating from 4 domains to 1 and we have to renest the groups from the source domain (Domain2) into the target (Domain1) to allow users who have not been migrated to maintain access to file shares and services.
-
Friday, May 18, 2012 4:05 PMModerator
Clearly, using gMember.Name will not identify the member, even in the domain, as Name (Relative Distinguished Name, which will be the Common Name) is only unique in the parent OU. At least gMember.sAMAccountName will uniquely identify the object in the domain, but not the forest. If your sAMccountName's match cn's, this could account for what you experience. gMember.distinguishedName will uniquely identify the object in the forest. Perhaps the distinguished name can only be resolved on a Global Catalog (GC), which could explain why this fails for objects in another domain. I'm not sure how to specify the GC with AD cmdlets. Just making sure that dc01 is also GC won't work, you need to specify the GC partition in AD. However, maybe this would work if you used either gMember.objectSID or gMember.objectGUID, both of which uniquely identify the object in the forest. I would try that, or find a way to specify a GC.
If speed is a concern, I find the AD cmdlets are 2 or 3 time slower than PowerShell V1 methods, like [ADSISearcher]. Then you can specify the GC: provider rather than the LDAP: provider (and more closely match the performance of VBScript).
Richard Mueller - MVP Directory Services
-
Sunday, May 20, 2012 12:15 PM
Thanks for the clarification on the .name, I wasnt aware of that (This is the first time I have used powershell)
I dont think this the issue is with a gc, the orignal code will find nested users from foreign forests, however I will try it with the sAMAccountName to be sure.
I have actually changed the code to now use [ADSI] which is faster and will work with anygroup, the only issue is it essentially ignores FSPs at this time.
Here's where I am for reference:
function getGroupMembers($gsid) { $securityidentifier = new-object security.principal.securityidentifier $gsid $object = ( $securityidentifier.translate( [security.principal.ntaccount] ) ) $objectar=$object.value.split("\") $thisgroup = "" | Select-Object Groupname,UMembers,GMembers $thisgroup.Groupname = $object $thisgroup.UMembers=@() $thisgroup.GMembers=@() write-host $group if ($objectar[0].ToLower() -eq 'Domain1'){ $group = get-adgroup -server dc01 -identity $gsid $ldapgroup = [ADSI] "LDAP://$group" write-host $ldapgroup.cn foreach ($member in $ldapgroup.member) { $memberobject = Get-ADObject -Server dc01 -Identity $member switch ($memberobject.objectclass.ToLower()) { 'group' { $groupobject=Get-ADGroup -Server dc01 -Identity $member $thisgroup.gMembers+=getGroupMembers $groupobject.sid.value } 'user' { $userobject=Get-ADUser -Server dc01 -Identity $member $thisgroup.UMembers+=$userobject } 'foreignsecurityprincipal' {$thisgroup.UMembers+=$memberobject} 'computer'{ $computerobject=Get-ADComputer -Server dc01 -Identity $member $thisgroup.UMembers+=$computerobject } } } } return $thisgroup }
I will test with the samaccountname but I really think its something to do with the way sid history works as get-groupmember wont return anything when I have both the original and migrated group nested. -
Tuesday, May 22, 2012 11:48 AM
So just to update on this, I have had to put in a work around to get this to work, its not a good one so maybe someone has a better Idea.
As stated the problem is with sidhistory where the migrated global group is nested into a local group and the orignal group is nested into the same local group.
I have tried lots of different things to over come the issue, one thing I found strange which has forced me into a work around is that if you have a group with sIDhistory and run
$securityidentifier = new-object security.principal.securityidentifier $Sid $object = ( $securityidentifier.translate( [security.principal.ntaccount] ) ) $object
Where $sid is the sID of Domain2\GlobalGroup1, $object will return Domain1\GlobalGroup1 (Which has a sIDHistory value of Domain2\GlobalGroup1), now I assume this is by design but has made it almost impossible for me to get anything to work.
My workaround is this
function get-Trusts() { $DomainSIDList = @{} $LocalDomain=get-addomain -server dc001 | Select-object DomainSid, DNSRoot $DomainSIDList.Add($LocalDomain.DomainSid.value,$LocalDomain.DNSRoot) # Get list of all domains in local forest $forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() # Get forest trusts from .NET $trusts = $forest.GetAllTrustRelationships() ForEach ($trust in $trusts) { $trust.TrustedDomainInformation | ForEach-Object { if (!$DomainSIDList.contains($_.DomainSid)) { $DomainSIDList.Add($_.DomainSid,$_.DnsName) } } } return $DomainSIDList }$object_sid =new-object System.Security.Principal.SecurityIdentifier $object_sid $DomainSIDList = get-Trusts $Object_domain = $DomainSIDList.Get_Item($object_sid.AccountDomainSid.value) $object=[adsi]"LDAP://$($Object_domain)/<SID=$($object_sid.value)>" $object_name = $object_Domain + "\" + $object.cn
By taking the Domain portion of the sid and looking up the dns name of the domain using the trusts I am able to guarentee I get the correct object, this is quite slow though and I am sure there must be a better way to find the domain name from the sid.
The finished function is as follows incase its useful to anyone: (I am sure I can clean it up, but this is the first working function I have had)
function get-GroupMembers($object_sid) { $object_sid =new-object System.Security.Principal.SecurityIdentifier $object_sid $DomainSIDList = get-Trusts $Object_domain = $DomainSIDList.Get_Item($object_sid.AccountDomainSid.value) $object=[adsi]"LDAP://$($Object_domain)/<SID=$($object_sid.value)>" $object_name = $object_Domain + "\" + $object.cn Write-Host $object_name $thisGroup = "" | Select-Object name,type,object,UMembers,GMembers $thisGroup.name = $object_name $thisGroup.object = $object $thisGroup.type=$object.properties.objectclass[$object.properties.objectclass.count-1] $thisGroup.UMembers=@() $thisGroup.GMembers=@() switch ($thisGroup.type) { 'group' { foreach ($member in $thisGroup.object.properties.member) { if ($member) { $member= New-Object System.DirectoryServices.DirectoryEntry "LDAP://$object_domain/$member" switch ($member.properties.objectclass[$member.properties.objectclass.count-1].tolower()) { 'group' { $member_sid =new-object System.Security.Principal.SecurityIdentifier $member.properties.objectsid[0], 0 $thisGroup.gMembers+=get-GroupMembers $member_sid } 'user' { $thisGroup.UMembers+=$member } 'foreignsecurityprincipal' { $member_sid =new-object System.Security.Principal.SecurityIdentifier $member.properties.objectsid[0], 0 $object_fsp=get-GroupMembers $member_sid switch ($object_fsp.type) { 'group' { $thisGroup.gMembers+=$object_fsp } 'user' { $thisGroup.UMembers+=$object_fsp } 'foreignsecurityprincipal' { $thisGroup.UMembers+=$object_fsp } 'computer'{ $thisGroup.UMembers+=$object_fsp } } } 'computer'{ $thisGroup.UMembers+=$member } } } } } 'user' { #Ignored at top level } 'foreignsecurityprincipal' { $object_sid =new-object System.Security.Principal.SecurityIdentifier $object.properties.objectsid[0], 0 $object_fsp=get-GroupMembers $member_sid switch ($object_fsp.type) { 'group' { $thisGroup.gMembers+=$object_fsp } 'user' { $thisGroup.UMembers+=$object_fsp } 'foreignsecurityprincipal' { $thisGroup.UMembers+=$object_fsp} 'computer'{ $thisGroup.UMembers+=$object_fsp } } } 'computer' { #Ignored at top level } } return $thisGroup }- Marked As Answer by IamMredMicrosoft Employee, Owner Thursday, June 07, 2012 7:18 AM

