I have listed users in our AD with this:
Get-ADUser -Filter * -Properties department |
Where-Object {$_.department -Like "F0*"} |
Select sAMAccountName, department
It outputs all users of interest.
Now I wanna go through these users and find all of them that are managers of one or several Exchange distribution Groups. And have an output with username and name of distribution Group(s) , is this possible?
As Paxz already commented, you should go 'the other way' on this. If you want to get names and departments of managers of distribution groups, start with getting those first.
Something like this would probably do it:
# get the distribution groups where the ManagedBy property is set
Get-DistributionGroup | Where-Object { $_.ManagedBy } | ForEach-Object {
# then go through all possible listed managers and get their DisplayName and Department
foreach ($id in $_.ManagedBy) {
try {
# use '-ErrorAction Stop' to make sure the catch block is entered upon failure
$manager = Get-AdUser -Identity $id -Properties DisplayName, Department -ErrorAction Stop
$mgrName = $manager.DisplayName
$mgrDept = $manager.Department
}
catch {
# output failed result(s)
$mgrName = 'Obsolete user'
$mgrDept = 'Unknown'
}
# output the result(s) as PSObjects
New-Object -TypeName PSObject -Property #{
'Distribution Group' = $_.Name
'Manager' = $mgrName
'Department' = $mgrDept
}
}
}
If you would like to store the results in a csv file, you can extend the script to something like this:
$fileName = '<Enter the full path and filename here for the output csv file>'
# collect all results in an array
$results = #()
Get-DistributionGroup | Where-Object { $_.ManagedBy } | ForEach-Object {
foreach ($id in $_.ManagedBy) {
try {
# use '-ErrorAction Stop' to make sure the catch block is entered upon failure
$manager = Get-AdUser -Identity $id -Properties DisplayName, Department -ErrorAction Stop
$mgrName = $manager.DisplayName
$mgrDept = $manager.Department
}
catch {
# output failed result(s)
$mgrName = 'Obsolete user'
$mgrDept = 'Unknown'
}
$results += New-Object -TypeName PSObject -Property #{
'Distribution Group' = $_.Name
'Manager' = $mgrName
'Department' = $mgrDept
}
}
$results | Export-Csv -Path $fileName -UseCulture -NoTypeInformation -Force
Related
I can get the user accounts in the group with the following script. But I want to get both computer object and user object in some groups. how can I do it?
Import-Module ActiveDirectory
$groups = Get-ADGroup -Filter "Name -like 'TST*'"
ForEach ($Group in $Groups) {
Get-ADGroupMember -Identity $group |
Get-ADUser -Properties samaccountname,mail,AccountExpires,manager,employeeid,employeetype |
Select-Object samaccountname,mail,employeeid,employeetype,#{l="expiration_date";e={ accountExpiresToString($_.AccountExpires)}},#{n=”Manager Name”;e={(Get-ADuser -identity $_.Manager -properties displayname).DisplayName}} |
Export-CSV -Path "C:tmp\$($group.Name).csv" -NoTypeInformation
}
Just test for the objectclass and deal with accordingly. Use a custom object to construct the output.
Import-Module ActiveDirectory
$groups = Get-ADGroup -Filter "Name -like 'TST*'"
$groups | ForEach-Object {
$Results = Get-ADGroupMember -Identity $_ | ForEach-Object {
#
# For each objectclass you must output the same properties else
# the custom PS object will not construct across object types
# I find this the best way to make it obvious what your code is doing:
#
If ($_.objectclass -eq 'user') {
$ObjDetails = Get-ADUser -Identity $_ -Properties mail,AccountExpires,manager,employeeid,employeetype
$Mail = $ObjDetails.mail
$EmployeeID = $ObjDetails.employeeid
$EmployeeType = $ObjDetails.employeetype
# Unless you're SURE all these fields are populated correctly use Try...Catch or test for the field before assigning
Try {
$ManagerName = (Get-ADuser -identity $ObjDetails.Manager -properties displayname -ErrorAction Stop).DisplayName
}
Catch {
$ManagerName = 'Not found'
}
# Not in a User object, but you must define empty fields
$DNSHostName = ''
}
If ($_.objectclass -eq 'computer') {
$ObjDetails = Get-ADComputer -Identity $_ -Properties DNSHostName
# These fields are not in a Computer object but you must define them
$Mail = ''
$EmployeeID = ''
$EmployeeType = ''
$ManagerName = ''
# This field unique to computer object
$DNSHostName = $ObjDetails.DNSHostName
}
[pscustomobject]#{
SamAccountName = $ObjDetails.SamAccountName
Mail = $Mail
EmployeeID = $EmployeeID
EmployeeType = $EmployeeType
ManagerName = $ManagerName
DNSHostName = $DNSHostName
}
}
# You can then display/export results object
$Results # | Export-CSV -Path "C:\tmp\$($group.Name).csv" -NoTypeInformation
}
I have a script which adds users to Teams from a .csv, but there are likely to be additions/removals which will need to be accounted for in the first weeks after creation.
# Read team users from CSV file
$TeamUsers = Import-CSV "File_Path"
$i = 0
# Group the objects by their TeamDesc property, so we only query each group once
$TeamUsers | Group-Object TeamDesc | ForEach-Object {
# Get the GroupId of this Group
$groupId = (Get-Team -DisplayName $_.Name).GroupId
# If the Group couldn't be found, just skip below logic
if(-not $groupId) { return }
# If the Group could be found, create a hashtable for future splatting
$params = #{ GroupId = $groupId }
# now we can enumerate each object in each group of objects
foreach($user in $_.Group) {
try {
# create a hashtable for splatting progress
$progress = #{
PercentComplete = $i++ / $TeamUsers.Count * 100
Activity = 'Adding Users to MS Teams!!'
Status = 'Working on Team: "{0}" and User: "{1}"' -f $_.Name, $user.UserPrincipleName
}
Write-Progress #progress
# add this user with this role to the hashtable
$params['User'] = $user.UserPrincipleName
$params['Role'] = $user.Role
Add-TeamUser #params
}
catch {
('Error occurred for {0} - {1}' -f $user.TeamName, $user.UserPrincipleName),
$_.ToString() | Write-Warning
}
}
}
Currently, I can add users to the Team by adding their user information to the .csv but I want to be able to remove users if they are not found in the file. I found, in another answer, this selection:
$validUsers = Import-Csv 'C:\path\to\your.csv' | Select-Object -Expand dn
$invalidUsers = Get-ADGroupMember 'groupname' |
Where-Object { $validUsers -notcontains $_.distinguishedName }
Remove-ADGroupMember 'groupname' $invalidUsers -WhatIf
I am unsure of the best way to include this. Where in my current script can I incorporate checking for and removing users NOT found in a .csv from the populated Teams?
Can you please try the below logic:
[array]$teams = "T1","T2","T3"
$users = gc C:\temp\UserList.txt
foreach ($user in $users) {
$user = ($user -split "#")[0] # SamAccountName from UPN
foreach($t in $teams) {
$tMems = Get-AdGroupMember $t |select -exp SamAccountName
if ($tMems -match $user) {
RemoveAdGroupMember $t $user
}
}
}
My goal is to dump a CSV of our AD groups, their members, and whether those member objects are enabled, but I'm running into a strange (probably self-inflicted) issue, wherein a Foreach-Object loop is behaving unexpectedly.
The output almost works. It dumps a CSV file. The file has rows for each group, populated with the correct group-related data, and the right number of rows, following the number of group members. However, group member properties on those rows is repeated, showing the same user data over and over for each groupmember result, apparently following the properties of the last returned object from Get-ADGroupMember.
To try to diagnose the issue, I added the line Write-Host $GroupMember.Name -ForegroundColor Gray. This is how I knew the entries in the CSV were the last-returned results for each group. Confusingly, the console correctly echoes each group member's display name.
I'm assuming there's some kind of logic error at work here, but I have had no luck finding it. Any help would be appreciated!
clear
Import-Module ActiveDirectory
# CONFIG ========================================
# Plant Number OU to scan. Used in $CSV and in Get-ADComputer's search base.
$PlantNumber = "1234"
# FQDN of DC you want to query against. Used by the Get-AD* commands.
$ServerName = "server.com"
# Output directory for the CSV. Default is [Environment]::GetFolderPath("Desktop"). Used in $CSV. NOTE: If setting up as an automated task, change this to a more sensible place!
$OutputDir = [Environment]::GetFolderPath("Desktop")
# CSV Output string. Default is "$OutputDir\$PlantNumber"+"-ComputersByOS_"+"$(get-date -f yyyy-MM-dd).csv" (+'s used due to underscores in name)
$CSV = "$OutputDir\$PlantNumber"+"GroupMembers_"+"$(get-date -f yyyy-MM-dd).csv"
# Create empty array for storing collated results
$collectionTable = #()
# Get AD groups, return limited properties
Get-AdGroup -filter * -Property Name, SamAccountName, Description, GroupScope -SearchBase "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM" -server $ServerName | Select SamAccountName, Description, GroupScope | Foreach-Object {
Write-Host "Querying" $_.SamAccountName "..."
#Initialize $collectionRow, providing the columns we want to collate
$collectionRow = "" | Select GroupName, GroupScope, GroupDesc, MemberObjectClass, MemberName, MemberDisplayName, Enabled
# Populate Group-level collectionRow properties
$collectionRow.GroupName = $_.SamAccountName
$collectionRow.GroupDesc = $_.Description
$collectionRow.GroupScope = $_.GroupScope
# Process group members
Get-ADGroupMember -Identity ($collectionRow.GroupName) -Server $ServerName -Recursive | ForEach-Object {
$GroupMember = $_
# Echo member name to console
Write-Host $GroupMember.Name -ForegroundColor Gray
$collectionRow.MemberName = $GroupMember.SamAccountName
$collectionRow.MemberDisplayName = $GroupMember.name
$collectionRow.MemberObjectClass = $GroupMember.ObjectClass
# If the member object is a user, collect some additional data
If ($collectionRow.MemberObjectClass -eq "user") {
Try {
$collectionRow.Enabled = (Get-ADUser $GroupMember.SamAccountName -Property Enabled -ErrorAction Stop).Enabled
If ($collectionRow.Enabled -eq "TRUE") {$collectionTable += $collectionRow}
}
Catch {
$collectionRow.Enabled = "ERROR"
$collectionTable += $collectionRow
}
}
}
}
Write-Host "`n"
# Attempt to save results to CSV. If an error occurs, alert the user and try again.
$ExportSuccess = 'false'
while ($ExportSuccess -eq 'false') {
Try
{
# Export results to $CSV
$collectionTable| Export-csv $CSV -NoTypeInformation -ErrorAction Stop
# If the above command is successful, the rest of the Try section will execute. If not, Catch is triggered instead.
$ExportSuccess = 'true'
Write-Host "`nProcessing complete. Results output to"$CSV
}
Catch
{
Write-Host "Error writing to"$CSV"!" -ForegroundColor Yellow
Read-Host -Prompt "Ensure the file is not open, then press any key to try again"
}
}
There are many things from your code you need to fix, I'll just point out the most important ones:
Don't use #() and +=
You keep using 'True' and 'False' which are strings, PowerShell booleans are $true and $false.
There is also too much redundant code. Also ForEach-Object is slow, if your groups have many members and since you're using -Recursive it's better to use a fast loop instead.
$PlantNumber = "1234"
$ServerName = "server.com"
$OutputDir = [Environment]::GetFolderPath("Desktop")
$fileName = "${PlantNumber}GroupMembers_$(Get-Date -f yyyy-MM-dd).csv"
$CSV = Join-Path $OutputDir -ChildPath $fileName
# $collectionTable = #() => Don't do this to collect results, ever
$adGroupParams = #{
# Name and SAM are default, no need to add them
Properties = 'Description', 'GroupScope'
SearchBase = "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM"
Server = $ServerName
Filter = '*'
}
# Get AD groups, return limited properties
$collectionTable = foreach($group in Get-AdGroup #adGroupParams)
{
Write-Host "Querying $($group.samAccountName)..."
foreach($member in Get-ADGroupMember $group -Server $ServerName -Recursive)
{
# if this member is 'user' the Enabled property
# will be a bool ($true / $false) else it will be $null
$enabled = if($member.ObjectClass -eq 'User')
{
(Get-ADUser $member).Enabled
}
[pscustomobject]#{
GroupName = $group.SamAccountName
GroupDesc = $group.Description
GroupScope = $group.GroupScope
MemberName = $member.SamAccountName
MemberDisplayName = $member.Name
MemberObjectClass = $member.ObjectClass
Enabled = $enabled
}
}
}
as i understand, you need to export list of groups with members to a csv file and know if member accounts are enabled or not, if this what you want, you can check the below code
$output = #()
Import-Module ActiveDirectory
$ServerName = "server.com"
$PlantNumber = "1234"
$OutputDir = [Environment]::GetFolderPath("Desktop")
$CSV = "$OutputDir\$PlantNumber"+"GroupMembers_"+"$(get-date -f yyyy-MM-dd).csv"
$groups = Get-AdGroup -filter * -Property Description -SearchBase "OU=Security Groups,OU=$PlantNumber,OU=Plants,DC=SERVER,DC=COM" -server $ServerName
foreach ($group in $groups){
$members = Get-ADGroupMember -Identity $group.SamAccountName -Recursive
foreach ($member in $members){
$output += [pscustomobject]#{
GroupName = $group.SamAccountName
GroupDesc = $group.Description
GroupScope = $group.GroupScope
MemberName = $member.samaccountname
MemberDisplayName = $member.Name
MemberObjectClass = $member.ObjectClass
Enabled = $(Get-ADUser -Identity $member.samaccountname).enabled
}
}
}
$output | Export-Csv $CSV -NoTypeInformation
I am explicitly NOT refering your code. I'd just like to show how I would approach this task. I hope it'll help you anyway.
$Server = 'Server01.contoso.com'
$SearchBase = 'OU=BaseOU,DC=contoso,DC=com'
$CSVOutputPath = '... CSV path '
$ADGroupList = Get-ADGroup -Filter * -Properties Description -SearchBase $SearchBase -Server $Server
$ADUserList = Get-ADUser -Filter * -Properties Description -SearchBase $SearchBase -Server $Server
$Result =
foreach ($ADGroup in $ADGroupList) {
$ADGroupMemberList = Get-ADGroupMember -Identity $ADGroup.sAMAccountName -Recursive
foreach ($ADGroupmember in $ADGroupMemberList) {
$ADUser = $ADUserList | Where-Object -Property sAMAccountName -EQ -Value $ADGroupmember.sAMAccountName
[PSCustomObject]#{
ADGroupName = $ADGroup.Name
ADGroupDescription = $ADGroup.Description
ADGroupMemberName = $ADUser.Name
ADGroupMemberSamAccountName = $ADUser.sAMAccountName
ADGroupMemberDescription = $ADUser.Description
ADGroupMemberStatus = if ($ADUser.Enabled) { 'enabled' }else { 'diabled' }
}
}
}
$Result |
Export-Csv -Path $CSVOutputPath -NoTypeInformation -Delimiter ',' -Encoding utf8
It'll output only the a few properties but I hope you get the idea.
BTW: The properties DistinguishedName, Enabled, GivenName, Name, ObjectClass, ObjectGUID, SamAccountName, SID, Surname, UserPrincipalName are included in the default return set of Get-ADUser and the properties DistinguishedName, GroupCategory, GroupScope, Name, ObjectClass, ObjectGUID, SamAccountName, SID are included in the default return set of Get-ADGroup. You don't need to query them explicitly with the parameter -Properties.
I am a powershell beginner and self learner. I finally made my script to get AD Users from NestedGroup. I now want to make a cache for them to make a quicker result when I run the script on the second time. I am totally lost on what I am doing and I want to ask for some approach from professionals here. Thank you for guidance.
Here is my code.
function Get-CachedGroupMembership($groupname){
$groupName = "cached_$($groupName)"
$cachedResults = Get-Variable -Scope Global -Name $groupName -ErrorAction SilentlyContinue
if($null -ne $cachedResults){
"found cached result"
$existing = write-host "Check: i found existing"
return $cachedResults, $existing
}
else{
$searching = write-host "Check: Searching"
$results = get-adgroup $groupname -properties memberof, members
Set-CachedGroupMembership -groupName $groupName -value $results
return $searching
}
}
Function Set-CachedGroupMembership($groupName,$value){
Set-Variable -Scope Global -Name $groupName -Value $value
return $value
}
function Get-ADUsers_cached {
param (
[Parameter(ValuefromPipeline = $true, mandatory = $true)][String] $GroupName
)
[int]$circular = $null
# result holder
$resultHolder = #()
$table = $null
$nestedmembers = $null
$adgroupname = $null
# get members of the group and member of
# $ADGroupname = get-adgroup $groupname -properties memberof, members
$ADGroupname = Get-CachedGroupMembership -GroupName $groupName
# list all members as list (no headers) and save to var
$memberof = $adgroupname | select -expand memberof
if ($adgroupname) {
if ($circular) {
$nestedMembers = Get-ADGroupMember -Identity $GroupName -recursive
$circular = $null
}
else {
$nestedMembers = Get-ADGroupMember -Identity $GroupName | sort objectclass -Descending
# if get adgroupmember returns nothing, it uses the members for ordinary getADGroup
if (!($nestedmembers)) {
$unknown = $ADGroupname |select -expand members
if ($unknown) {
$nestedmembers = #()
foreach ($member in $unknown) {
$nestedmembers += get-adobject $member
}
}
}
}
# loops through each member
ForEach($nestedmember in $nestedmembers){
# creates the properties into a custom object.
$Props = #{
Type = $nestedmember.objectclass;
Name = $nestedmember.name;
DisplayName = "";
ParentGroup = $ADgroupname.name;
Enabled = "";
Nesting = $nesting;
DN = $nestedmember.distinguishedname;
Comment = ""
EmployeeNumber = "";
LastLogonDate = "";
PasswordLastSet = "";
}
# if member object is a user
if ($nestedmember.objectclass -eq "user") {
# saves all the properties in the table.
$nestedADMember = get-aduser $nestedmember.Name -properties enabled, displayname, EmployeeNumber, LastLogonDate, PasswordLastSet
$table = new-object psobject -property $props
$table.enabled = $nestedadmember.enabled
$table.name = $nestedadmember.samaccountname
$table.displayname = $nestedadmember.displayname
$table.EmployeeNumber = $nestedadmember.EmployeeNumber
$table.LastLogonDate = $nestedadmember.LastLogonDate
$table.PasswordLastSet = $nestedadmember.PasswordLastSet
#save all in 1 storage
$resultHOlder += $table | select type, name, displayname, parentgroup, nesting, enabled, dn, comment , EmployeeNumber, LastLogonDate, PasswordLastSet
}
# if member object is group
elseif ($nestedmember.objectclass -eq "group") {
$table = new-object psobject -Property $props
# if circular, meaning the groups member of list contains one of its members.
# e.g. if group 2 is a member of group 1 and group 1 is a member of grou 2
if ($memberof -contains $nestedmember.distinguishedname) {
$table.comment = "Circular membership"
$circular = 1
}
# for circular output
#$table | select type, name, displayname, parentgroup, nesting, enabled, dn, comment
#calling function itself
$resultHOlder += Get-ADUsers_cached -GroupName $nestedmember.distinguishedName
}
else {
if ($nestedmember) {
$table = new-object psobject -property $props
$resultHolder += $table | select type, name, displayname, parentgroup, nesting, enabled, dn, comment, EmployeeNumber, LastLogonDate, PasswordLastSet
}
}
}
}
return $resultHOlder
}
function Get-NestedGroupUsers_cached {
param (
[Parameter(Mandatory = $true)][String]$FileName,
[Parameter(Mandatory = $true)][String]$searchFileURL
)
$storageHolder = #()
$groupList = Get-Content $searchFileURL
$groupList | ForEach-Object {
$allusers = Get-ADUsers_cached -GroupName $_
$storageHolder += $allusers
}
$storageHolder | select ParentGroup, Name, EmployeeNumber, Enabled, LastLogonDate, PasswordLastSet |Export-Csv -Path "C:\Users\***\Desktop\$FileName.csv" -NoTypeInformation -Force
}
You can make use of a global or module scoped variable to store the info. Then, check for the presence of the variable on second execution and pull from that, if present.
For instance, your query of AD Groups would be a good one to cache.
I would make two functions to make this easier:
function Get-CachedGroupMembership($groupname){
$groupName = "cached_$($groupName)"
$cachedResults = Get-Variable -Scope Global -Name $groupName -ErrorAction SilentlyContinue
if($null -ne $cachedResults){
"found cached result"
return $cachedResults
}
else{
"need to cache"
$results = get-adgroup $groupname -properties memberof, members
Set-CachedGroupMembership -groupName $groupName -value $results
}
}
Then to cache the results, I'd use this function too.
Function Set-CachedGroupMembership($groupName,$value){
Set-Variable -Scope Global -Name $groupName -Value $value
return $value
}
Finally, replace the call to $ADGroupname = get-adgroup $groupname -properties memberof, members with a call to Get-CachedGroupMembership -GroupName $groupName.
If you really want to persist the cache, you could even write results out to JSON and modify these functions to retrieve them, but then you could have an issue with stale results. So, you should always leave a way to force an update to the cache, for instance you could modify these functions to add a -Force switch to update your cached value.
With a little copy and paste, you could modify these functions into Get-CachedADUser as well, and have your entire script caching results.
I found this script online. It was original designed to get all members of one security group and if there are nested group it will write to the host the nested group name and members in hierarchy form.
I tweaked it to import AD security groups from a CSV file and to export the results to CSV with table format. CSV files has two security group with both security groups has nested groups. Script will only list the users in the second security group and it doesn't list the nested security group.
CSV File format:
Groupname groupad name
test.testdl office\test.testdl test.testdl
test.testsg office\test.testsg test.testsg
Import-Module ActiveDirectory
$GroupList = #{}
$Table = #()
$Record = #{
"Name" = ""
"nested" = ""
"domain" = ""
"userName" =""
}
function Get-GroupHierarchy {
param()
$searchGroups = Import-Csv -Path C:\temp\ad1.csv
foreach ($item in $searchGroups) {
$groupMember = Get-ADGroupMember -Identity $item.Groupname |
Select-Object name, samaccountname, distinguishedName, objectClass
}
}
foreach ($member in $groupMember) {
$username = $member.samaccountname
$distinguishedName = $member.distinguishedName
$dc = [regex]::Match($distinguishedName,'DC=([^,|$]+)').Groups[1].Value
$domainuser = '{0}\{1}' -f $dc, $username
$Record."userName" = $member.samaccountname
$Record."Name" = $member.name
$Record."nested" = $member.objectclass
$Record."Domain" = $domainuser
$objRecord = New-Object PSObject -Property $Record
$Table += [array]$objrecord
if ($member.ObjectClass -eq "group") {
$GroupList.add($member.name, $member.name)
Get-GroupHierarchy $member.name
}
Get-GroupHierarchy
}
$Table | Export-Csv "C:\temp\SecurityGroups01.csv" -NoTypeInformation
Error message:
Get-ADGroupMember : Cannot validate argument on parameter 'Identity'. The
argument is null or empty. Provide an argument that is not null or empty, and
then try the command again.
At line:1 char:48
+ $groupMember = Get-ADGroupMember -Identity $item.name | Select-Object name, ...
+ ~~~~~~~~~~
I Know it has been ages since you asked this question. But i was working last week on something similar and obtained some results through some work. I saw this question here working on that piece of work and thought to share my work if it can help somebody.
$members = Get-ADGroupMember 'GroupName'
foreach ($member in $members){
if ($member.objectClass -eq 'Group')
{$NestGroupUsers = Get-ADGroupMember $member | select name, objectclass }
Else {
$hash = [pscustomobject]#{
'name' = $member.name
'objectclass' = $member.objectClass
}
$hash | Export-Csv C:\users.csv -Append -NoTypeInformation
}
}
$NestGroupUsers |Export-Csv C:\users.csv -Append -NoTypeInformation