Issue with ADSI script not pulling users from all groups - powershell

I have a list of groups that i need to pull back all users. I am aware due to the numbers it is restricted. I have tried adding page size but it does not work.
Here is the code
Get-Module ActiveDirectory - ErrorAction silentlycontinue
Function resolve-group}
param ($group)
For each ($member in $group.member){
$obj = [ADSI] ("LDAP://" + member)
if (obj.objectclass[1] -eq 'group'){resolve-group $obj}
else {
If ($obj.employeeid.length -eq 6)
{
$displayname = $obj.displayname
$employeeid = $obj.employeeid
$groupname = $group.name
$global:members +="$employeeid,$displayname,$groupname"
}
}
}
}
$global:members =#()
$group = [ADSI] "LDAP://cn=ab,ou=cd,ou=ef,DC=gh,DC=BB"
resolve-group $group
$group = [ADSI] "LDAP://cn=aa,ou=cc,ou=ef,DC=gh,DC=BB"
resolve-group $group
"ID,Name,Group" > c:\test\groups.csv
$global:members | sort-object - unique >> c:\test\groups.csv
I don't know how to amend the script to add and increase the page size?
I have only listed a couple of the groups but there are 100's of groups
Thanks

Related

Get AD group members with ADSI - Powershell

I cannot use the Active Directory Module to get the SamAccountName of the users in a specific AD-group. How can I do this with ADSI?
I've tried:
$Group = [ADSI]"LDAP://DN of the AD group"
$Group.Member | ForEach-Object {
$Searcher = [adsisearcher]"(samAccountName=$_)"
$searcher.FindOne().Properties
}
But I see this message:
The samAccountName search filter is invalid.
How can I do this?
There are 2 ways around this as I see it, there might be an easier way of doing it though.
One is to search for all users which's memberOf attribute has the DistinguishedName of the group (this might be the less cumbersome approach):
$group = 'CN=myGroup, OU=myOU, DC=myDomain'
$searcher = [adsisearcher]"(&(objectclass=user)(objectcategory=person)(memberof=$group))"
$members = foreach($member in $searcher.FindAll()) {
$member.Properties.samaccountname
}
The other way around is using the same approach as you're using in your question:
$group = 'CN=myGroup, OU=myOU, DC=myDomain'
$adsi = [adsi]"LDAP://$group"
$members = foreach($member in $adsi.member) {
$isUser = [adsi]"LDAP://$member"
if('person' -in $isUser.objectclass) {
$isUser.samaccountname
}
}
Similar as the one above, but using adsisearcher, not sure which one would be more efficient in this case:
$members = foreach($member in $adsi.member) {
$check = [adsisearcher]"(&(distinguishedname=$member)(objectclass=user)(objectcategory=person))"
if($isUser = $check.FindOne()) {
$isUser.Properties.samaccountname
}
}
I ran this modified version of your code on my own system, so I could see what the search string actually looked like:
$Group = [ADSI]"LDAP://DN of the AD group"
$Group.Member | ForEach-Object {
$searchKey = "(samAccountName=$_)"
$searchKey
$Searcher = [adsisearcher]$searchKey
# $searcher.FindOne().Properties
}
Note the point where I let $searchKey come to the console. When I do this, I see values with the full distinguished name instead of just samAccountName. Based on this result I changed the code to look for that value instead of samAccountName, and then I saw (presumably) expected results:
$Group = [ADSI]"LDAP://DN of the AD group"
$Group.Member | ForEach-Object {
$Searcher = [adsisearcher]"(distinguishedName=$_)"
$searcher.FindOne().Properties
}

Get-ADGroupMember processing for non domain members

We have a trusted domain that we administer and users are given acceess to a file server on our domain through groups on the trusted domain added to the local domain group which controls permissions e.g.
For the path "\server\folder\folder" the permissions might look like
MyDomain\FolderXReadWrite
Then within that group a group from the trusted domain would be included
TrustedDomain\FolderXReadWrite
I'd like to write a script to "reverse engineer" this so that I can derive both the local and trusted domain groups that have access from a given folder path and list them - I've got the following:
$path = '\\server\path\path'
$Permissions = (Get-Item -Path $path -ErrorAction Stop | Get-Acl -ErrorAction Stop).Access | Where-Object { $_.IdentityReference -like "MyDomain\*" } | ForEach-Object {
$TrustedDomGroup = Get-ADGroupMember ($_.IdentityReference.Value -split '\\')[1] -Verbose | Where-Object { ($_.DistinguishedName -split 'DC=', 2)[-1] -like "*TrustedDomain*" -and ($_.objectClass -eq 'group') }
If ($TrustedDomGroup) {
[pscustomobject]#{Name=("TrustedDomain\" + $TrustedDomGroup.SamAccountName);Rights=("Member of " + $_.IdentityReference);Domain='PHS'}
}
[pscustomobject]#{Name=$_.IdentityReference;Rights=$_.FileSystemRights;Domain='NSS'}
}
$Permissions | Sort-Object Name -Unique
This produces the output I require, however, the domain group in the above example has LOTS of users therefore processing "Get-ADGroupMember" takes a long time. Does anyone have suggestions for a faster method?
you can try to retrieve all group members with this command:
(Get-ADGroup $ADGroup -Properties members).members
I don't know if it's faster in your environment then Get-AdGroupMember.
A measurement via Measure-Command may give you more information:
Measure-Command, Microsoft Docs
Ok, so the best method to improve the lookup speed seems to be using LDAP so I put together a small function to get Foreign Security Principal group names from a local AD group:
Function List-FSPGroupMembers {
# Use LDAP search to find and resolve Foreign Security Principals from an AD group - faster than native cmdlet
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=1)][string]$GroupName="",
[Parameter(Mandatory=$true,Position=2)][string]$Domain
)
[regex]$SIDmatch = "S-\d-(?:\d+-){1,14}\d+"
Function fGetADGroupObjectFromName([System.String]$sGroupName,[System.String]$sLDAPSearchRoot) {
$oADRoot = New-Object System.DirectoryServices.DirectoryEntry($sLDAPSearchRoot)
$sSearchStr ="(&(objectCategory=group)(name="+$sGroupName+"))"
$oSearch=New-Object directoryservices.DirectorySearcher($oADRoot,$sSearchStr)
$oFindResult=$oSearch.FindAll()
If($oFindResult.Count -eq 1) {
Return($oFindResult)
}
Else{
Return($false)
}
}
$sSearchRoot="LDAP://" + $Domain + ":3268"
If($oSearchResult=fGetADGroupObjectFromName $groupname $sSearchRoot) {
$oGroup=New-Object System.DirectoryServices.DirectoryEntry($oSearchResult.Path)
$oGroup.Member | Where-Object {($_.ToString()) -match $SIDmatch } | ForEach-Object {
$SID = (Select-String -Pattern $SIDmatch -InputObject $_.ToString()).Matches.Value
Try {
(New-Object System.Security.Principal.SecurityIdentifier($SID)).Translate([System.Security.Principal.NTAccount]).Value
}
Catch {
$_
}
}
}
Else {
Write-Warning ("Group "+$groupname+" not found at "+$domain)
}
}

How to pass an array through Add-UnifiedGroupLinks -Identity?

I would like to add a user to several Office365 groups in one go.
My code is as follows:
$OfficeGroups = #("Egg_Group", "Chicken Group", "Soup Group")
foreach ($Group in $OfficeGroups)
{
$AddParams = #{
Identity = $Group
LinkType = 'Members'
Links = $Username
}
Add-UnifiedGroupLinks #AddParams
}
I am unable to pass an array of groups through the -Identity parameter. Is there an alternative cmdlt I could use? I have tried
Add-DistributionGroupMember
but it still doesn't work. Is this an impossible task?
When you work with an array of group names it should be sufficient to just loop through them:
$OfficeGroups = #('Egg_Group', 'Chicken_Group', 'Soup Group')
foreach ($Group in $OfficeGroups) {
$AddParams = #{
Identity = $Group
LinkType = 'Members'
Links = $Username
}
Add-UnifiedGroupLinks #AddParams
}
Note: It's always better to use splatting i.e. a hashtable to feed parameters to your function if you want to make it look more readable.
Can you try looping through your array like this?
$OfficeGroups = #("Egg_Group", "Chicken Group", "Soup Group")
$OfficeGroups | ForEach-Object {Add-UnifiedGroupLinks -Identity $_ -LinkType 'Members' -Links $Username}

Adding Computer to Multiple Active Directory Groups During Task Sequence Using Powershell

I'm trying to set up my task sequence for SCCM to automatically add laptops to 3 Active Directory groups. I've set up a step to do this and am calling upon a Powershell script to do so. The script should be run as a network admin so I don't believe I'm having any issues with authorization however I am super new to Powershell so I believe my issue is with my syntax. My script is simple enough and all of the resources I look at seem to overcomplicate Powershell for what I need to do. Here is my script:
ADD-ADGroupMember "GroupOne" -members "$env:computername$"
ADD-ADGroupMember "GroupTwo" -members "$env:computername$"
ADD-ADGroupMember "GroupThree" -members "$env:computername$"
The $env:computername is supposed to automatically gather the computer's name which is established earlier in the task sequence and the $ following it is required to add using powershell, I've found.
Any help on this would be very much appreciated.
EDIT: I've got it working finally, below is the code I've found and used for one of the powershell scripts -
$ComputerName = gc env:computername
$isMember = new-object DirectoryServices.DirectorySearcher([ADSI]"")
$ismember.filter = “(&(objectClass=computer)(sAMAccountName=$Computername$)(memberof=CN=<CN NAME>,OU=<OU NAME>,DC=<DC NAME>,DC=<DC NAME>))”
$isMemberResult = $isMember.FindOne()
If ($isMemberResult) {exit}
else
{
$searcher = new-object DirectoryServices.DirectorySearcher([ADSI]"")
$searcher.filter = “(&(objectClass=computer)(sAMAccountName= $Computername$))”
$FoundComputer = $searcher.FindOne()
$P = $FoundComputer | select path
$ComputerPath = $p.path
$GroupPath = "LDAP://CN=<CN NAME>,OU=<OU NAME>,DC=<DC NAME>,DC=<DC NAME>"
$Group = [ADSI]"$GroupPath"
$Group.Add("$ComputerPath")
$Group.SetInfo()
}
Instead of trying to compose the computer's account name from an environment variable, and then using that to add to the group, simply get the computer object from Active Directory:
$Computer = Get-ADComputer -Identity $env:ComputerName
foreach ($Group in #("GroupOne", "GroupTwo", "GroupThree")) {
Add-ADGroupMember -Identity $Group -Members $Computer
}
Since you mentioned that you didn't have the AD cmdlets available. Here's how you could do it with ADSI:
$filter = "(&(objectCategory=computer)(objectClass=computer)(cn=$env:COMPUTERNAME))"
$ComputerDN = ([adsisearcher]$filter).FindOne().Properties.distinguishedname
$GroupName = "GroupOne"
$filter = "(&(objectClass=Group)(cn=$GroupName))"
$GroupDN = ([adsisearcher]$filter).FindOne().Properties.distinguishedname
$Group = [adsi]"LDAP://$GroupDN"
$Group.Add($ComputerDN)
$GroupName = "GroupTwo"
$filter = "(&(objectClass=Group)(cn=$GroupName))"
$GroupDN = ([adsisearcher]$filter).FindOne().Properties.distinguishedname
$Group = [adsi]"LDAP://$GroupDN"
$Group.Add($ComputerDN)
$GroupName = "GroupThree"
$filter = "(&(objectClass=Group)(cn=$GroupName))"
$GroupDN = ([adsisearcher]$filter).FindOne().Properties.distinguishedname
$Group = [adsi]"LDAP://$GroupDN"
$Group.Add($ComputerDN)
If you have any more groups, it would probably worth turning into a function.
Just make sure that whatever user context is running this has rights to add members to that group.

Find user and AD group relation via nested AD groups

Basically, in our environment, we have a ton of security groups. Security groups that are nested within other groups etc. So it is a real PITA to find out why a setting is applying to a user, because of one of the nested groups they may or may not be a part of
For e.g. If you add a user to group X, they suddenly have a published application in Citrix. Citrix is configured for security group Y. Attempting to find the link between X and Y is very time consuming but can be automated.
I want to create a script where, you enter a user and the end security group (group Y from above), and the script outputs the intermediary groups that connects the user to the final group. If this makes sense?
Something like this:
function get-grouprelationship($username, $knownsecuritygroup)
{
$getallgroups = get-adgroupmember $knownsecuritygroup | where-object {$_.ObjectClass -eq "Group" | select-object SamAccountName | foreach-object {get-adgroupmember $_.SamAccountName}
}
(The above variable takes your group, and loops through all members of that group, printing their members)
$usergroups = (get-aduser -identity $username -Properties memberof | select-object memberof).memberof
(The above gets all groups that a user is in)
$usergroups1 = $usergroups.split(",")
$usergroups2 = $usergroups1[0]
$usergroups3 = $usergroups2.substring(3)
(the above formats the text nicely)
if ($usergroups3 -contains $groupname){write-host "$username is directly in $groupname}
From here, I am quite stuck as I basically need to nest multiple for loops, depending on how many groups are in each group. Then do a condition check that
if ($groupname -eq $currentgroup){write-host "connected by $groupname and $currentgroup}
I'm also stuck with the $getallgroups variable, because it only checks 1 level down. It would then need another foreach loop inside that, which would need another one inside that etc.
Having no prior coding experience, I am really struggling to get my head around an easy way to achieve my goal.
EDIT:
I found this script here - script. Below basically works, except it is way to verbose:
import-module activedirectory
$username = read-host "What's their username?"
Function RecurseUsersInGroup {
Param ([string]$object = "", [int]$level = 0)
$indent = "-" * $level
$x = Get-ADObject -Identity $object -Properties SamAccountName
if ($x.ObjectClass -eq "group") {
Write-Host "# $($x.SamAccountName)"
$y = Get-ADGroup -Identity $object -Properties Members
$y.Members | %{
$o = Get-ADObject -Identity $_ -Properties SamAccountName
if ($o.ObjectClass -eq "user" -and $o.SamAccountName -eq $username) {
Write-Host "-> $($o.SamAccountName)"
} elseif ($o.ObjectClass -eq "group") {
RecurseUsersInGroup $o.DistinguishedName ($level + 1)
}
}
} else {
Write-Host "$($object) is not a group, it is a $($x.ObjectClass)"
}
}
$thegroup = read-host "What's the Group?"
RecurseUsersInGroup (get-adgroup $thegroup).DistinguishedName
That works fine, but appears to output every security group, oppose to the connecting ones. Certainly a step in the right direction though! If I find the source I will post the credit as well.
The following version is not less verbose (could probably be written a lot more terse, but I'm hoping the script is at least semi-readable), but it does a search for the group and returns the Active Directory group objects for each group along the branch in which the group was found.
function Get-GroupConnection
{
[CmdletBinding()]
PARAM (
$Username,
$GroupName
)
$User = Get-AdUser -Identity $Username -Properties MemberOf
if (-Not ($User))
{
return;
}
$SearchedGroups = #()
function Find-GroupBranches
{
[CmdletBinding()]
PARAM (
$GroupNameList,
$SearchForGroupName
)
$ADGroups = $GroupNameList | Foreach { Get-ADGroup -Identity $_ -Properties MemberOf }
foreach($group in $ADGroups)
{
Write-Verbose "Testing if either '$($Group.SamAccountName)' or '$($Group.DistinguishedName)' are equal to '$SearchForGroupName'"
if ($Group.SamAccountName -eq $SearchForGroupName -OR $Group.DistinguishedName -eq $SearchForGroupName)
{
Write-Verbose "Found $($Group.DistinguishedName)"
Write-Output $Group
return
}
}
Write-Verbose "No match in current collection, checking children"
foreach ($currentGroup in $ADGroups)
{
if ($SearchedGroups -Contains $currentGroup.DistinguishedName)
{
Write-Verbose "Already checked children of '$($currentGroup.DistinguishedName)', ignoring it to avoid endless loops"
continue
}
$SearchedGroups += $currentGroup.DistinguishedName
if ($currentGroup.MemberOf)
{
Write-Verbose "Checking groups which $($currentGroup.DistinguishedName) is member of"
$foundGroupInTree = Find-GroupBranches -GroupNameList $currentGroup.MemberOf -SearchForGroupName $SearchForGroupName
if ($foundGroupInTree)
{
Write-Output $currentGroup
Write-Output $foundGroupInTree
break
}
}
else
{
Write-Verbose "$($currentGroup.DistinguishedName) is not member of any group, branch ignored"
}
}
}
Write-Verbose "Searching immediate group membership"
Find-GroupBranches -GroupNameList $User.MemberOf -SearchForGroupName $GroupName
}
Get-GroupConnection -Username MyUser -GroupName SubSubGroup -Verbose
A description of how it searches follows.
Given the following Active Directory structure:
MyUser
Domain Admins
AnotherSubGroup
Other Group
DirectMemberGroup
Domain Admins (same group as MyUser is direct member of, above)
AnotherSubGroup (which is of course the same as above too)
SubGroup
SubSubGroup
Some Other Group
If we search for the connection between MyUser and 'SubSubGroup' the script would search first the direct membership of the MyUser user, i.e. 'Domain Admins', 'Other Group', 'DirectMemberGroup' and 'Some Other Group'. None of these match the 'SubSubGroup' we search for, so it starts checking 'child'groups.
'Domain Admins' is member of 'AnotherSubGroup', but that does not match 'SubSubGroup'. 'AnotherSubGroup' is not member of any group, so that branch is ignored.
'Other Group' is not member of any group, so that branch are ignored.
'DirectMemberGroup' is member of other groups, so it iterates through those groups. It has already checked 'Domain Admins' for children, so that group is skipped to avoid getting stuck in a circular search. Therefore it check 'SubGroup'.
'SubGroup' does not match 'SubSubGroup' so it check the groups which 'SubGroup' is member of. 'SubGroup' is member of 'SubSubGroup', so it checks that group.
'SubSubGroup' does match 'SubSubGroup' and will therefore be chosen as a match.
In the above example, the output group object will be branches which lead to the 'SubSubGroup' group, in the following order:
DirectMemberGroup
SubGroup
SubSubGroup
Observe that this method will return the first connection it finds between the user and the group. If, for example, the 'Some Other Group' group would also be a member of 'SubSubGroup' this would not change the output, nor the search process, mentioned above.