I'm using Exchange 2010 and Powershell.
I'm looking to export all 'nested' distribution groups that a exchange contact is a part of.
For example, if a contact is a memberOf two DG, A and B. And group A is also a memberOf Group C. I'd like a list the show all three groups.
Group C
----|-----------|
Group A Group B
|
Contact
Here is my very noob attempt at this. I guess it needs to be done recursively?
$contact = get-contact email#domain.com.au
$members = Get-ADObject -Identity $contact.Guid -Properties 'MemberOf'
foreach ($group in $members.MemberOf) {
foreach ($_ in $group.memberof ){
get-distributiongroup $_
}
}
Must be able to do this for a 'contact', not a user.
Thanks in advance!
You don't really need Exchange to get distribution groups.
Here's my example to retrieve names of groups assuming your contact is AD user with mail property filled:
function Get-GroupsRec
{
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]
$Identity
)
BEGIN {}
PROCESS{
if($Identity -eq $null){return}
Write-Output $Identity | select -ExpandProperty samaccountname
$Identity | Get-ADPrincipalGroupMembership | ?{$_.GroupCategory -eq "Distribution"} | Get-GroupsRec}
END{}
}
Get-ADUser -Filter {mail -eq 'email#domain.com.au'} | Get-ADPrincipalGroupMembership | ?{$_.GroupCategory -eq "Distribution"} | Get-GroupsRec
But as long as you can retrieve AD user, this solution is valid.
UPDATE:
Version basing on Get-ADObject only. (Same structure, only different selectors. Left ObjectClass in filter for readability):
function Get-GroupsRec
{
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$true)]
$Identity
)
BEGIN {}
PROCESS{
if($Identity -eq $null){return}
Write-Output $Identity | select -ExpandProperty Name
$Identity | select -ExpandProperty MemberOf | Get-ADObject -Properties GroupType,MemberOf |?{$_.GroupType -gt 0 -and $_.ObjectClass -eq 'group'} | Get-GroupsRec }
END{}
}
Get-ADObject -Filter {mail -eq 'email#domain.com.au'} -Properties MemberOf | select -ExpandProperty MemberOf | Get-ADObject -Properties GroupType,MemberOf |?{$_.GroupType -gt 0 -and $_.ObjectClass -eq 'group'} | Get-GroupsRec
If you want restrict group type to a specific type, you may modify GroupType in code to any value with -band operator. The enumerator values are available at: https://msdn.microsoft.com/en-us/library/aa772263(v=vs.85).aspx
You are on the right track. You indeed need recursion for this. I quickly put something together which should recusively resvolves membership. Hope this helps!
Function Get-Groups {
Param(
[string]$Identity
)
$members = Get-ADObject -Identity $Identity -Properties "MemberOf"
$members.MemberOf | % {
if($(Get-ADObject $_ -Properties "GroupType").grouptype -gt 0) {
Write-Host $_
}
Get-Nestedmember $_ "->"
}
}
Function Get-Nestedmember {
Param(
[String]$Identity,
[string]$Depth
)
$members = (Get-ADobject -Identity $Identity -Properties "MemberOf").memberof
$count = $members | measure
if($count -ne 0) {
$members | % {
if($(Get-ADObject $_ -Properties "GroupType").grouptype -gt 0) {
Write-host "$Depth $_"
}
Get-Nestedmember $_ "-$Depth"
}
}
}
Get-Groups -Identity '<Your Identity goes here>'
It gets a MemberOf relationships and does that again for the resulting set. If MemberOf returns 0 that means there are no more MemberOf relationships so we stop.
Related
I am trying to run a command where I get all active directory users in the parent OU (Users) and filter out the child OU's (Admin accounts, service accounts, disabled accounts) as well as filter out any user account that does not have a surname in the surname field.
At the moment I have
Get-ADUser -Filter{enabled -eq $true} -SearchBase 'OU=Users,OU=Company,DC=CompanyName,DC=local' | Where-Object { $_.DistinguishedName -notlike "*,$Disabled" } | Where {$_.Surname -notlike "$Null"} | select samAccountName
When I add another child OU after 'Disabled' there is an error
Where-Object : A positional parameter cannot be found that accepts argument 'Where'.
Please may someone advise on how to filter out additional child OU's?
Good day Smoore
The problem is you are using multiple Where-object cmdlets but you only need one and separate them using () and adding the -and option, also to refer to $null value you don't need to use the "" marks
Get-ADUser -Filter {Enabled -eq $true} -SearchBase "OU=Users,OU=Company,DC=CompanyName,DC=local" | Where-Object {($_.DistinguishedName -notlike "*,$Disabled*") -and ($_.Surname -notlike $Null)} | select samAccountName
With this options you should be able to get all the users you want
Have a nice day!
I would use a regex -notmatch so it would be possible to combine all OU Distinguished names in just one variable.
Something like this:
$Admins = 'OU=Administrators,OU=Company,DC=CompanyName,DC=local'
$Service = 'OU=ServiceAccounts,OU=Company,DC=CompanyName,DC=local'
$Disabled = 'OU=DisabledUsers,OU=Company,DC=CompanyName,DC=local'
# build a regex string from the above OU DistinguishedNames
$Exclude = '({0}|{1}|{2})$' -f [regex]::Escape($Admins), [regex]::Escape($Service), [regex]::Escape($Disabled)
Get-ADUser -Filter 'Enabled -eq $true' -SearchBase 'OU=Users,OU=Company,DC=CompanyName,DC=local' |
Where-Object { ![string]::IsNullOrWhiteSpace($_.Surname) -and $_.DistinguishedName -notmatch $Exclude } |
Select-Object SamAccountName
As per your comment:
$Admins = 'OU=Administrators,OU=Company,DC=CompanyName,DC=local'
$Service = 'OU=ServiceAccounts,OU=Company,DC=CompanyName,DC=local'
$Disabled = 'OU=DisabledUsers,OU=Company,DC=CompanyName,DC=local'
# the group you want to add the users to
$TargetGroup = 'Company Team'
# build a regex string from the above OU DistinguishedNames
$Exclude = '({0}|{1}|{2})$' -f [regex]::Escape($Admins), [regex]::Escape($Service), [regex]::Escape($Disabled)
$users = Get-ADUser -Filter 'Enabled -eq $true' -SearchBase 'OU=Users,OU=Company,DC=CompanyName,DC=local' |
Where-Object { ![string]::IsNullOrWhiteSpace($_.Surname) -and $_.DistinguishedName -notmatch $Exclude }
# get the AD group as object
$GroupObject = Get-ADGroup -Filter "Name -eq '$TargetGroup'"
# now add these users that have Surnames to the security group all in one go
try {
Write-Host "Adding $(#($users).Count) users to group $TargetGroup"
$GroupObject | Add-ADGroupMember -Members $users -ErrorAction Stop -Verbose
}
catch {
Write-Warning "Error: $($_.Exception.Message)"
}
# or if you prefer loop through the users and add each one individually then use this instead
# foreach ($user in $users) {
# try {
# Write-Host "Adding user $($users.Name) to group $TargetGroup"
# $GroupObject | Add-ADGroupMember -Members $user -ErrorAction Stop -Verbose
# }
# catch {
# Write-Warning "Error adding user $($users.Name) to group $($TargetGroup): $($_.Exception.Message)"
# }
# }
My code:
$searchOU = "OU=a,OU=b,OU=c,OU=d,OU=e,DC=f,DC=g,DC=com"
Get-ADGroup -Filter 'GroupCategory -eq "Security"' -SearchBase $searchOU | sort name | ForEach- Object{
$group = $_
Get-ADGroupMember -Identity $group | Get-ADUser | Where-Object { $_.Enabled -eq $false} | ForEach-Object{
$user = $_
$uname = $user.Name
$gname = $group.Name
Write-Host "Removing $uname from $gname" -Foreground Yellow
Remove-ADGroupMember -Identity $group -Member $user -Confirm:$false #-whatif
}
}
It runs, but it's dog slow. Any suggestions on ways to make it run faster?
You need to remember that Get-ADGroupMember can return users, groups, and computers, not just user objects..
If you want to search for user objects only, you need to add a Where-Object clause there.
Unfortunately, while Get-ADUser has a -Filter parameter that enables you to find disabled users much more quickly than filtering afterwards on the collection of users, using a filter while piping user DN's to it will totally ignore the pipeline and collect all users that are disabled..
So, in this case, we're stuck with appending a Where-Object clause.
You could change your code to rule out all objects from Get-ADGroupMember that are not uders:
$searchOU = "OU=a,OU=b,OU=c,OU=d,OU=e,DC=f,DC=g,DC=com"
Get-ADGroup -Filter "GroupCategory -eq 'Security'" -SearchBase $searchOU | Sort-Object Name | ForEach-Object {
$group = $_
Get-ADGroupMember -Identity $group | Where-Object { $_.objectClass -eq 'user' } |
Get-ADUser | Where-Object { $_.Enabled -eq $false} | ForEach-Object {
Write-Host "Removing $($_.Name) from $($group.Name)" -Foreground Yellow
Remove-ADGroupMember -Identity $group -Member $_ -Confirm:$false #-whatif
}
}
The above removes one disabled user at a time and for each of them writes a line on the console.
You could make it work faster if you can cope with getting a different output on screen like this:
$searchOU = "OU=a,OU=b,OU=c,OU=d,OU=e,DC=f,DC=g,DC=com"
$result = foreach ($group in (Get-ADGroup -Filter "GroupCategory -eq 'Security'" -SearchBase $searchOU)) {
$users = Get-ADGroupMember -Identity $group | Where-Object { $_.objectClass -eq 'user' } |
Get-ADUser | Where-Object { $_.Enabled -eq $false}
if ($users) {
# the Remove-ADGroupMember cmdlet can take an array of users to remove at once
Remove-ADGroupMember -Identity $group -Member $users -Confirm:$false #-whatif
# output an object that gets collected in variable $result
[PsCustomObject]#{Group = $group.Name; RemovedUsers = ($users.Name -join '; ')}
}
}
# if you like, output to console as table
$result | Sort-Object Group | Format-Table -AutoSize -Wrap
# or write to CSV file
$result | Export-Csv -Path 'D:\Test\RemovedUsers.csv' -NoTypeInformation
I have some code to get the groups of a user and write them down into an Arraylist, however ît will only find the groups where a user is directly in. It won't find groups deeper then 1 level.
For example: User is member of Group 1, Group 1 is member of Groups 2, etc.
I will only find Group 1. Group 2 won't be written down into my ArrayList.
$Groups = Get-ADPrincipalGroupMembership -Server ESX-DC $GroupName
$GroupArrayList = New-Object System.Collections.ArrayList
foreach ($Group in $Groups)
{
$GroupArrayList.Add($Group.Name) | Out-Null
}
Can someone provide me some help here? Thanks.
I'm not aware of a recurse parameter so I think you have to write that for your own. How ever, I wrote a scripts for similar tasks. Might that helps.
function Get-ADPrincipalGroupMembershipRecurse
{
param
(
[Parameter(Mandatory = $true)]
[System.String]$Identity
)
$script:Groups = #()
function Get-NestedAdGroups
{
param
(
[Parameter(Mandatory = $true)]
[System.String]$Identity
)
$ADGroup = Get-ADGroup -Identity $Identity -Properties MemberOf, Description
$script:Groups += $ADGroup
foreach ($Group in $ADGroup.MemberOf)
{
if ($script:Groups.DistinguishedName -notcontains $Group)
{
Get-NestedAdGroups -Identity $Group
}
}
}
foreach ($Group in (Get-ADUser -Identity $Identity -Properties MemberOf).MemberOf)
{
Get-NestedAdGroups -Identity $Group
}
return ($script:Groups | Sort-Object -Unique)
}
Get-ADPrincipalGroupMembershipRecurse -Identity $SamAccountName
Recently I found out about tokenGroups attribute, which is way faster than LDAP_MATCHING_RULE_IN_CHAIN, so I'm spreading the word:
To get all AD object groups recursively:
((Get-ADUser username | Get-ADUser -Properties tokenGroups).tokenGroups | Get-ADGroup).Name
Or, if you don't need an ADGroup object, this returns a String instead, but is way faster:
(Get-ADUser username | Get-ADUser -Properties tokenGroups).tokenGroups.Translate([System.Security.Principal.NTAccount]).Value
It's almost instantaneous in our directory:
PS C:\windows\System32> (Get-ADUser -Filter *).Count
86816
PS C:\windows\System32> (Get-ADGroup -filter *).Count
1808
PS C:\windows\System32> (Get-ADComputer -filter *).Count
2666
Just for reference, here how much time it takes in this instance:
# this returns String objects
1..10 | % {
Measure-Command {
(Get-ADUser marcos | Get-ADUser -Properties tokenGroups).tokenGroups.Translate([System.Security.Principal.NTAccount]).Value
}
} | Measure-Object -Average TotalSeconds | select #{l="Type";e={"String"}},Average
# this returns ADGroup objects
1..10 | % {
Measure-Command {
((Get-ADUser marcossantos | Get-ADUser -Properties tokenGroups).tokenGroups | Get-ADGroup).Name
}
} | Measure-Object -Average TotalSeconds | select #{l="Type";e={"ADGroup"}},Average
Type Average
---- -------
String 0.01415692
ADGroup 0.25427236
This also returns nested membership of primaryGroup (usually Domain users), which most solutions does not account for.
One downside of this approach is that it does not retrieve distribution groups. If you need that, following query returns just distribution groups, using LDAP_MATCHING_RULE_IN_CHAIN (way faster than retrieving all groups, though):
Get-ADGroup -LDAPFilter "(&(groupType>=0)(member:1.2.840.113556.1.4.1941:=CN=myuser,OU=MyUsers,DC=example,DC=com))"
List all users that have mailboxes but are not in a group called Metalogix*. I need a PowerShell script that will check whether specific user is a part of certain group or not and if the user is part of any of those groups.
I already have working script:
Import-Module ActiveDirectory
$Users = Get-Mailbox -ResultSize "unlimited"
$Group = "Metalogix*"
foreach ($user in $Users) {
$Check = Get-ADPrincipalGroupMembership -Identity $User.sAMAccountName |
? { $_.Name -like $Group }
if ($Check -eq $null) {
Write-Output "$User.sAMAccountName is NOT part of this group"
} else {
$Results = Get-Mailbox -Identity $User.sAMAccountName |
select Name, sAMAccountName, PrimarySmtpAddress, Database |
Export-csv "c:\results1.csv" -NTI -Append
}
}
But script doesn't list groups recursively, e.g tester4-6 are members of 'Test Group 2', which is a member of 'Test Group 1'. The rest are direct. Just I can see direct membership, not recursive membership.
2nd question : I want to get all users with samaccountname that begins with "STR" prefix.
Test Group 1
tester1
tester2
-> Test Group 2
tester4
tester6
I'd probably use a recursive function. Something like this:
function Test-GroupMembership {
Param(
[Parameter(Mandatory=$true)]
[string]$Identity,
[Parameter(Mandatory=$true)]
[string]$Name
)
$groups = Get-ADPrincipalGroupMembership -Identity $Identity
if ($groups | Where-Object { $_.Name -like $Name }) {
return $true
} elseif ($groups -ne $null) {
foreach ($g in $groups) {
if (Test-GroupMembership -Identity $g -Name $Name) {
return $true
}
}
}
return $false
}
Get-ADPrincipalGroupMembership isn't recursive, but Get-ADGroupMember is.
$Users = Get-Mailbox -ResultSize "unlimited"
$Group = 'Metalogix*'
$GroupMembers = Get-ADGroupMember -Identity $Group | Get-ADGroupMember -Recursive | Select-Object -ExpandProperty samAccountName
foreach ($User in $Users) {
if ($User -in $GroupMembers) {
Write-Output "User $User is in group $Group."
}
else {
Write-Output "User $User is not in group $Group."
}
}
This is also more efficient because you're only fetching group membership once.
I'm away from my servers, so treat the above as pseudocode.
I've created a form to create new AD Accounts. Part of the script determines which groups the new user will be added to based on their role (Doctor, Nurse, Admin or Other) which is captured in the following code in the form of a drop down pick box:
Write-Host "Based on this information" $FFN "has been added to the following Active Directory Groups:"
Write-Host
$ADGroup01 = Get-ADGroup "_XA_App_XenApp" |select -expandproperty name -first 1
Write-Host $ADGroup01
$ADGroup02 = Get-ADGroup "Web Proxy Users" |select -expandproperty name -first 1
Write-Host $ADGroup02
if($RadioButton1.Checked -eq $true)
{
$ADGroup03 = Get-ADGroup "allrot" |select -expandproperty name -first 1
Write-Host $ADGroup03
}
Else
{
$ADGroup03 = Get-ADGroup "alltpo" |select -expandproperty name -first 1
Write-Host $ADGroup03
}
if ($Role -eq "Doctor" -Or $Role -eq "Nurse")
{
$ADGroup04 = Get-ADGroup "PACS Web Access" |select -expandproperty name -first 1
Write-Host $ADGroup04
}
if ($Role -eq "Doctor")
{
$ADGroup05 = Get-ADGroup "CH-MFD" |select -expandproperty name -first 1
Write-Host $ADGroup05
$ADGroup06 = Get-ADGroup "ED-MFP" |select -expandproperty name -first 1
Write-Host $ADGroup06
$ADGroup07 = Get-ADGroup "SU-MFD" |select -expandproperty name -first 1
Write-Host $ADGroup07
}
Write-Host
Further on in the script this piece of code is called during the actual account creation process:
Add-ADPrincipalGroupMembership -Identity $UN -memberof $ADGroup01, $ADGroup02, $ADGroup03, $ADGroup04, $ADGroup05, $ADGroup06, $ADGroup07
The issue I'm facing is that if the user selects Nurse, Admin or Other I get the following error:
"Add-ADPrincipalGroupMembership : Cannot validate argument on parameter 'MemberO
f'. The argument is null, empty, or an element of the argument collection conta
ins a null value. Supply a collection that does not contain any null values and
then try the command again."
I know this is because there are no values being captured in the last $ADGroup[x] and short of creating a bunch of if statements to check if each $ADGroup contains data I'm wondering if there is a more elegant solution.
As always, thank you for taking the time review and happy to provide more information if required.
UPDATE - As per #Martin's advice I've implemented the following code into my script
$UN = "zooz"
$Role = "Nurse"
$Department = "Surgical"
If ($Role -eq "Doctor" -and $Department -eq "Surgical")
{
$ADGroups = #(
"PACS Web Access"
"CH-MFD"
"ED-MFP"
"SU-MFD"
)
}
If ($Role -eq "Nurse" -and $Department -eq "Surgical")
{
$ADGroups = #(
"_XA_App_XenApp"
"Web Proxy Users"
"allrot"
)
}
for ($i=0; $i -lt $ADGroups.length; $i++) {
Add-ADPrincipalGroupMembership -Identity $UN -memberof $adgroups[$i]
}
Make an object $adgroups and add your desired groups to it.
$adgroups = #()
At the end use a foreach Loop:
$adgroups | Add-ADPrincipalGroupMembership -Identity $UN or (weather or not the cmdlet likes pipelined Input)
$adgroups | % { Add-ADPrincipalGroupMembership -Identity $UN -memberof $_ }