Exchange/Powershell - Add data to new column rather than joining - powershell

I am trying to export list of users in mail enabled security groups to csv but want to have each member in a separate column rather than joining the existing column.
$Csvfile = "C:\SPOgroupmembers.csv"
$Groups = Get-DistributionGroup -Filter "Alias -like '*.spo'" -ResultSize Unlimited
$Groups | ForEach-Object {
$GroupDN = $_.DistinguishedName
$DisplayName = $_.DisplayName
$PrimarySmtpAddress = $_.PrimarySmtpAddress
$Members = Get-DistributionGroupMember $GroupDN -ResultSize Unlimited
[PSCustomObject]#{
DisplayName = $DisplayName
PrimarySmtpAddress = $PrimarySmtpAddress
Members = ($Members.Name -join ',')
}
} | Sort-Object DisplayName | Export-CSV -Path $Csvfile -NoTypeInformation -Encoding UTF8 #-Delimiter ";"
This is how it currently outputs:
DisplayName
PrimarySmtpAddress
Member
Test.SPO
Test.SPO#test.com
User1,User2,User3
This is what I am trying to achieve:
DisplayName
PrimarySmtpAddress
Test.SPO
Test.SPO#test.com
User1
User2
I may be missing something simple but any help would be appreciated

If you want to export the data where each member has it's own row, which in my opinion, would be the proper way to do it, you can have an inner loop to create a new pscustomobject per member of the Group:
Get-DistributionGroup -Filter "Alias -like '*.spo'" -ResultSize Unlimited | ForEach-Object {
foreach($member in Get-DistributionGroupMember $_.DistinguishedName -ResultSize Unlimited) {
[PSCustomObject]#{
DisplayName = $_.DisplayName
PrimarySmtpAddress = $_.PrimarySmtpAddress
Member = $member
}
}
} | Sort-Object DisplayName | Export-CSV -Path ....

The simplest way to construct a [pscustomobject] dynamically is to construct an ordered hashtable first - which is easy to extend iteratively - and cast it to [pscustomobject] when done.
However, in the context of creating CSV output, you need to commit to a fixed number of properties (columns) ahead of time - if feasible[1]; e.g.:
$maxMembers = 10 # <- adjust this number to the max. count of members you expect
$Groups | ForEach-Object { ...
# ...
# Initialize an ordered hashtable with the static entries...
$oht = [ordered] #{
DisplayName = $DisplayName
PrimarySmtpAddress = $PrimarySmtpAddress
}
# ... then iteratively add the Member1, Member2, ... entries
foreach ($i in 1..$maxMembers) {
$oht["Member$i"] = $Members[$i-1]
}
# Convert to a [pscustomobject] and output
[pscustomobject] $oht
} | Sort-Object DisplayName | Export-CSV -Path $Csvfile -NoTypeInformation -Encoding UTF8
If no max. member count can / should be assumed, consider denormalizing the data by using a single member column combined with creating a separate row for each member, as shown in Santiago's helpful answer, which is unquestionably the better approach for subsequent programmatic processing of the data vs. the multi-column approach you're seeking, which may be simpler to grasp for the human observer.
[1] That is, you need to know how many members a group can have at most. You could even try to determine that count programmatically, ahead of time, but either way the resulting number may be too large to be practical.

Related

Importing CSV splits certain lines

I have a fairly simple script that needs to check around 20,000 AD Groups for their membership count. That all works fine, I can take the list of groups run it through the script and for the most entries it works fine. However I was getting some errors that I couldn't figure out and hopefully someone here can point me in the right direction.
I am using the DN of the object to query AD and for around 10% it fails, but when I copy the DN from the file, paste it into a command window and run the command manually it works fine. Some more checking and it seems that when I read an offending line into my variable there is a line break in the middle for some reason.
When looking at the value of the variable I get the following:
Working Example - "CN=ABC, OU=Location, OU=Distribution Lists, DC=Domain, DC=COM"
Error Example - "CN=ABC, OU=Location, OU=Distribution
Lists, DC=Domain, DC=COM"
It seems to insert a return in-between Distribution and Lists on certain entries in the file. I have tried deleting the character in-between and replacing it with a space but I get the same result.
Could it be the length? I am still looking for a common factor but any suggestions would be great.
Thanks
Updated with requested content.
$Groups = Import-Csv C:\Temp\DLName.csv
write-host ($Groups).Count
$i=1
foreach ($Group in $Groups)
{
$GroupInfo = Get-ADGroupMembersRecursive -Groups $Group.Name
$MembersCount = ($GroupInfo | Measure-Object).Count
$MembersList = $GroupInfo | Select Name -ExcludeProperty Name
$FriendlyName = Get-ADGroup -Identity $Group.Name
$Export = $FriendlyName.Name + ", " + $MembersCount
$Export | Out-File C:\Temp\DLMembers.csv -Append
Write-host $FriendlyName "," $MembersCount
$i
$i++
}
Entry 1 and 3 work 2 doesn't, but the formatting here seems to have wrapped the entries.
Name
"CN=Company - DL Name1,OU=Country1 Distribution Lists,OU=Europe,OU=Acc,DC=Domain,DC=Domain,DC=com"
"CN=Company - DL Name2,OU=Country2 Distribution Lists,OU=Europe,OU=Acc,DC=Domain,DC=Domain,DC=com"
"CN=Company - DL Name3,OU=Country3 Distribution Lists,OU=America,OU=Acc,DC=Domain,DC=Domain,DC=com"
Top pic is the failure second pic works.
List Creation:
$SearchScope = "OU=OUName,DC=Domain,DC=Domain,DC=com"
$SearchFilter = {GroupCategory -eq 'Distribution'}
$Groups = Get-ADGroup -SearchBase $SearchScope -Filter
$SearchFilter | Sort-Object Name
foreach ($Group in $Groups)
{
$Group.DistinguishedName | Select Name -ExpandProperty Name
$Group.DistinguishedName | Out-File C:\Temp\DLName.csv -Append
}
Do not use a self-combined comma separated string and Out-File to create CSV files, because that will get you into trouble when fields happen to contain the delimiter character like in this case the comma (which will lead to mis-aligned data).
Your List Creation code should be like this:
$SearchBase = "OU=OUName,DC=Domain,DC=Domain,DC=com"
$SearchFilter = "GroupCategory -eq 'Distribution'"
Get-ADGroup -SearchBase $SearchBase -Filter $SearchFilter |
Sort-Object Name | Select-Object Name, DistinguishedName |
Export-Csv -Path 'C:\Temp\DLName.csv' -NoTypeInformation
Then you can use that csv later to do:
$Groups = Import-Csv -Path 'C:\Temp\DLName.csv'
Write-Host $Groups.Count
$result = foreach ($Group in $Groups) {
$GroupInfo = Get-ADGroupMember -Identity $Group.DistinguishedName -Recursive
# unnecessary.. $MembersCount = ($GroupInfo | Measure-Object).Count
# unused.. $MembersList = $GroupInfo.Name
# unnecessary.. $FriendlyName = Get-ADGroup -Identity $Group.Name
# output an object with the wanted properties
[PsCustomObject]#{
GroupName = $Group.Name
MemberCount = #($GroupInfo).Count # #() in case there is only one member in the group
}
}
# show on screen
$result | Format-Table -AutoSize
# output to CSV file
$result | Export-Csv -Path 'C:\Temp\DLMembers.csv' -NoTypeInformation
As you can see, I'm not using your custom function Get-ADGroupMembersRecursive because I have no idea what that outputs.. Also, there is no need for that because you can use the Get-ADGroupMember cmdlet with the -Recursive switch added

List Members of multible AD Groups with more than 5000 Users

How could I get a List of Members on multible AD Groups with more than 5000 Users
Example:
Group1 = includes 6000 Members and Group2
Group2 = includes 7000 Members
the result of the get-adgroupmember of Group1 should 13000
how can I do that? Here I have the Problem, that it will not look in sub groups recursive will not work with get-adgroup
$group = "group1"
$ADInfo = Get-ADGroup -Identity $Group -Properties Members
$outputfile = $group
$ADInfo.Members | get-aduser | Select name, enabled, UserPrincipalName, SamAccountName
#$ADInfo.Members | get-aduser | Select name, enabled, UserPrincipalName, SamAccountName | Export-Csv c:\temp\$outputfile-member.csv -Delimiter "," -NoTypeInformation
# to show output
$members = #()
$members = $ADInfo.members
$members.count
With groups that large, it will be slow, but this should do what you want:
$groups = 'group1', 'group2' # array of group names
foreach ($group in $groups) {
Write-Host "Working on group '$group'"
$result = Get-ADGroupMember -Identity $group -Recursive | Where-Object { $_.objectClass -eq 'user' } | ForEach-Object {
Get-ADUser -Identity $_.distinguishedName | Select-Object Name, Enabled, UserPrincipalName, SamAccountName
}
# show result on screen
$result | Format-Table -AutoSize
# write to export file
$result | Export-Csv -Path "c:\temp\$group-members.csv" -NoTypeInformation
}
Hope that helps
The easiest solution would be to adjust the MaxGroupOrMemberEntries parameter in ADWS on the DC you are targeting. You can see information on ADWS defaults here.
You could do something like the following, which is potentially convoluted:
function Get-ADGroupMembers
{
param ($groupname)
Get-ADGroupMember $groupname | where ObjectClass -eq 'Group' | ForEach-Object {
$_.Name
Get-ADGroupMembers $_.Name
}
}
$maingroup = 'group1'
$subgroups = Get-ADGroupMembers $maingroup
$allGroups = #($maingroup)+#($subgroups)
$regexEscapes = $allGroups |% { [regex]::Escape($_) }
$filter = "CN=({0})" -f ($regexEscapes -join "|")
$output = foreach ($group in $allGroups) {
Get-AdGroup $group -Properties Members | Select #{n='Members';e={$_.Members -notmatch $filter}}
}
$output.Members
Explanation:
The function will list the Name property value for each recursively discovered member group.
Since the -notmatch regex operator is used in filtering, a regex match string needs to be constructed. There could be multiple groups so the | (regex OR) character needs to be used.
The [regex]::Escape method escapes all backslashes and other special regex characters that may appear in the name strings.
$output is an array of PSCustomObjects that contain the Members property. The Members property contains the DN of all members that are users.
Non-PowerShell commands may be better suited for this particular case if the ADWS default limits are not modified.

Grouping data from Active Directory

wondering if i could get some help with my powershell script. I am halfway (or maybe even 3/4 of the way there) but im just struggling to get my groups grouped on one line per user....
Ill explain
Right now i'm able to get ALL users from Multiple AD groups, took a bit of playing around but i got there in the end...
However it displays CSV like this:
first_name,last_name,email,group_list
John,Smith,JSmith#email.com,Group1
John,Smith,JSmith#email.com,Group2
Emily,Rogers,ERogers#email.com,Group1
Emily,Rogers,ERogers#email.com,Group3
Whilst thats OK, i would really like to format the data like this:
first_name,last_name,email,group_list
John,Smith,JSmith#email.com,Group1~Group2
Emily,Rogers,ERogers#email.com,Group1~Group3
This is my code so far
## Define the groups, This includes a wildcard which gets all users in groups with that pattern
$Groups = (Get-AdGroup -filter * | Where {$_.name -like "GroupName*"} | select name -expandproperty name)
## Var for array, empty
$Array = #()
## Var for data
$Data = [ordered]#{
}
## For Each loop to get members of each group
Foreach ($Group in $Groups)
{
## Define the search criteria for AD Search
$Members = Get-ADGroupMember -identity $Group | Get-ADUser -Properties * | select givenName,sn,sAMAccountName,mail
foreach ($Member in $Members)
{
$Data."first_name" = $Member.givenName
$Data."last_name" = $Member.sn
$Data."email" = $Member.mail
$Data."group_list" = $Group
## Store in PSObject
$DataPSObject = New-Object PSObject -property $Data
## Add to array so it is no longer empty
$Array += $DataPSObject
}
}
## Export array into CSV
$Array | export-csv "C:\temp\DataFromArray.csv" -NoTypeInformation
As the email is the unique identifier, i tried to Group-Object on the email property but the output is not useful for me
## Export array into CSV
$Array | Group-Object -Property email | export-csv "C:\temp\DataFromArray.csv" -NoTypeInformation
Also i tried to join the groups using a defined separator -join '~' but this just seemed to create one long string of joined groups (makes sense when i put it that way)
Hoping anyone has some ideas?
Thanks
You need to do a little more processing on the output of Group-Object, but you're almost there!
$Array |Group-Object -Property email |ForEach-Object {
[pscustomobject]#{
first_name = $_.Group[0].first_name
last_name = $_.Group[0].last_name
email = $_.Group[0].email
groups = $_.Group.group_list -join '~' # join all the group names together
}
} |Export-Csv "C:\temp\DataFromArray.csv" -NoTypeInformation
Just a quick thingy to get what you want:
## Define the groups, This includes a wildcard which gets all users in groups with that pattern
$Groups = Get-AdGroup -Filter 'Name -like "GroupName*"' | Select-Object -ExpandProperty Name
## For Each loop to get members of each group
$Array = foreach ($Group in $Groups) {
## Define the search criteria for AD Search and capture in variable $Array
Get-ADGroupMember -Identity $Group |
Get-ADUser -Properties GivenName,Surname,SamAccountName,EmailAddress |
Select-Object #{Name = 'first_name'; Expression = {$_.GivenName}},
#{Name = 'last_name'; Expression = {$_.Surname}},
#{Name = 'email'; Expression = {$_.EmailAddress}},
#{Name = 'GroupName'; Expression = {$Group}}
}
$out = $Array | Group-Object email | ForEach-Object {
# join the GroupName property of this user to get a delimited string
$grouplist = $_.Group.GroupName -join '; '
# output a new object with the 'group_list' property
# this will create duplicates objects, so we need Select-Object * -Unique at the end
$_.Group | Select-Object first_name, last_name, email, #{Name = 'group_list'; Expression = {$grouplist}}
} | Select-Object * -Unique
$out | Export-Csv "C:\temp\DataFromArray.csv" -NoTypeInformation
Hope that helps

Export Distribution Group

I need to export the following to a csv or excel file
-Distribution Group (PrimarySmtpAddress)
-Distribution Group members and each primarysmtpaddress
I tried adding
Group email: $($group) | PrimarySmtpAddress
in the code below but it does not add it.
#This is what works but is missing
$groups = Get-DistributionGroup -ResultSize Unlimited | Select -ExpandProperty name
ForEach ($group in $groups)
{
"Group Name: $($group)`nGroup Members:`n"
Get-DistributionGroupMember $group |ft name,alias,primarysmtpaddress
}
I am missing the Distribution group primary smtp address?
As Lee_Daily commented, you are stripping out all properties except the Name by doing Select -ExpandProperty name. Next, if you want to export to a CSV file, DO NOT USE Format-Table (ft), because that is only to format the result to the console.
What you should do is create an array of objects and pipe that to the Export-Csv cmdlet, like in the below (untested) code:
$outputFile = '<PATH AND FILENAME FOR THE EXPORTED CSV FILE>'
Get-DistributionGroup -ResultSize Unlimited | ForEach-Object {
# The Identity parameter for Get-DistributionGroupMember specifies the distribution group
# or mail-enabled security group. You can use any value that uniquely identifies the group.
# The cmdlet also accepts the Identity parameter as pipeline input, so
# $_ | Get-DistributionGroupMember will also work.
$Members = Get-DistributionGroupMember -Identity $($_.PrimarySmtpAddress)
foreach ($member in $Members) {
[PSCustomObject]#{
GroupName = $_.DisplayName
GroupAlias = $_.Alias
GroupEmail = $_.PrimarySMTPAddress
MemberName = $member.DisplayName
MemberEmail = $member.PrimarySMTPAddress
# Maybe also add RecipientType to distinguish between users and groups?
# MemberType = $member.RecipientType
}
}
} | Export-Csv -Path $outputFile -NoTypeInformation
Hope that helps

How to get list of selected AD Groups, that a large list of users are members of?

I have the below working script that checks if a large list of users in a CSV file are a member of an AD group and writes the results to results.csv.
Not sure how to convert the script so I can change $group = "InfraLite" to $group = DC .\List_Of_AD_Groups.CSV.
So the script doesn't just return matches for one AD group but so it returns matches for the 80 AD groups contained in the List_of_AD_groups.csv also. Writing a YES/NO for each AD group in a new column in the CSV (or if that's not possible creating a seperate .csv file for each group with results would do also.
I could do this manually by changing the value of $group and export file name, and re-running the script 80 times but must be a quick was with PS to do this?
e.g. results.csv:
NAME AD_GROUP1 AD_GROUP2 AD_GROUP80 etc etc.
user1 yes no yes
user2 no no yes
user3 no yes no
echo "UserName`InfraLite" >> results.csv
$users = GC .\user_list.csv
$group = "InfraLite"
$members = Get-ADGroupMember -Identity $group -Recursive |
Select -ExpandProperty SAMAccountName
foreach ($user in $users) {
if ($members -contains $user) {
echo "$user $group`tYes" >> results.csv
} else {
echo "$user`tNo" >> results.csv
}
}
I played with this for a while, and I think I found a way to get you exactly what you were after.
I think Ansgar was on the right path, but I couldn't quite get it to do what you were after. He mentioned that he didn't access to an AD environment at the time of writing.
Here is what I came up with:
$UserArray = Get-Content 'C:\Temp\Users.txt'
$GroupArray = Get-Content 'C:\Temp\Groups.txt'
$OutputFile = 'C:\Temp\Something.csv'
# Setting up a hashtable for later use
$UserHash = New-Object -TypeName System.Collections.Hashtable
# Outer loop to add users and membership to UserHash
$UserArray | ForEach-Object{
$UserInfo = Get-ADUser $_ -Properties MemberOf
# Strips the LPAP syntax to just the SAMAccountName of the group
$Memberships = $UserInfo.MemberOf | ForEach-Object{
($_.Split(',')[0]).replace('CN=','')
}
#Adding the User=Membership pair to the Hash
$UserHash.Add($_,$Memberships)
}
# Outer loop to create an object per user
$Results = $UserArray | ForEach-Object{
# First create a simple object
$User = New-Object -TypeName PSCustomObject -Property #{
Name = $_
}
# Dynamically add members to the object, based on the $GroupArray
$GroupArray | ForEach-Object {
#Checking $UserHash to see if group shows up in user's membership list
$UserIsMember = $UserHash.($User.Name) -contains $_
#Adding property to object, and value
$User | Add-Member -MemberType NoteProperty -Name $_ -Value $UserIsMember
}
#Returning the object to the variable
Return $User
}
#Convert the objects to a CSV, then output them
$Results | ConvertTo-CSV -NoTypeInformation | Out-File $OutputFile
Hopefully that all makes sense. I commented as much of it as I could. It would be very simple to convert to using ADSI if you didn't have RSAT installed on whatever machine you're running this on. If you need that let me know, and I'll make some quick modifications.
I've also tossed a slightly modified version of this in a Gist for later reference.
The trivial solution to your problem would be to wrap your existing code in another loop and create an output file for each group:
$groups = Get-Content 'C:\groups.txt'
foreach ($group in $groups) {
$members = Get-ADGroupMember ...
...
}
A more elegant approach would be to create a group mapping template, clone it for each user, and fill the copy with the user's group memberships. Something like this should work:
$template = #{}
Get-Content 'C:\groups.txt' | ForEach-Object {
$template[$_] = $false
}
$groups = #{}
Get-ADGroup -Filter * | ForEach-Object {
$groups[$_.DistinguishedName] = $_.Name
}
Get-ADUser -Filter * -Properties MemberOf | ForEach-Object {
$groupmap = $template.Clone()
$_.MemberOf |
ForEach-Object { $groups[$_] } |
Where-Object { $groupmap.ContainsKey($_) } |
ForEach-Object { $groupmap[$_] = $true }
New-Object -Type PSObject -Property $groupmap
} | Export-Csv 'C:\user_group_mapping.csv' -NoType