PowerShell : Exporting multi-valued attributes with Export-Csv – how to tame the beast (SysAdmins, rejoice!)
July 28th, 2014, by Ben Hungerford
The Scenario:
You want to query AD, Exchange, SharePoint, etc. to quickly retrieve some basic, but really useful information about something really common. Examples: users, sites, etc. And of course you want to export these values to a CSV so you can sort, filter, aggregate, count, etc. in Excel or some similar tool. One or more of the values that you’ll retrieve – usually the most important ones, in our experience – is not a single value, but instead, a set of values (e.g., a user belongs to multiple groups, or multiple distribution lists.) Easy, right? Not so much.
The problem:
PowerShell queries on fields/objects that naturally yield multiple results don’t return the values when you use the Export-Csv cmdlet, even when the values are displayed in the PowerShell console itself.
Example
In our sample (simple) Query, we’re looking for all of the Exchange mailbox “proxyaddresses” for a single specific user.
Result from the PowerShell console (using | fl for formatting)
Command run:
Note how all the values we’re looking for are present.
Result using the exact same query with “Export-Csv”
Command run:
Note how the multi-valued attributes (in this case, “proxyaddresses”) show up in the CSV. We only get “System.String[]”. Booo!
How to fix it: replace multi-valued attributes with Join functions
By replacing each multi-valued attribute with a join function, we can have Powershell create a single string, using any character we specify to concatenate the values. In my examples, example, I’m using semicolons.
Original command (w/o the join function)
Note: this is exactly the same command , using better user (“seth”) to illustrate results.
Result: no values.
New command, replacing the attribute with a join function for that attribute
Result: all “proxyaddresses” values concatenated by semicolons.
Slightly more complex (and practical) Examples
- Query the Exchange Message tracking Log; focus in on a specific timeframe, and export entries for messages with sortable lists of recipients
get-messagetrackinglog -EventID “RECEIVE” -Start “7/1/2014 3:59:00 PM” -End “7/4/2014 4:09:00 PM” | Select timestamp, eventid, Source,Messageid, MessageSubject, sender,@{Name=’recipients‘;Expression={[string]::join(“;”, ($_.recipients))}}, clientip, clienthostname, serverip, serverhostname, messageinfo | export-csv C:\temp\message_log.csv
- Query all AD groups in a domain, list all the members of each group in a single concatenated field, and then use Export-CSV to review/manipulate results in Excel
Get-Group -ResultSize ‘Unlimited’ | Select-Object -Property ‘Name’,’DisplayName’,’SamAccountName’,’GroupType’,@{Name=
‘members’;Expression={[string]::join(“;”, ($_.members))}} | export-csv C:\temp\group_members.csv - Query all users in a domain, and list each user’s group memberships – all of them – in a single concatenated field
Get-ADUser -Properties * | Select ‘Name’,’DisplayName’,’SamAccountName’, @{Name=’MemberOf’;Expression={[string]::join(“;”, ($_.MemberOf))}}| export-csv C:\temp\users_with_all_groups.csv
Credit: Part of this post contains source material from this article: PowerShell : Exporting multi-valued attribute via Export-Csv cmdlet
Thanks Ben, that exactly what i needed!!! It works perfectly fine….
your example,
Expression={[string]::Join(“;”,($_.proxyaddresses))
does not work.
this one
Expression={$_.proxyaddresses -join “;”}
works.
Works like charm 🙂
thanks
You’re a star, great documentation, thanks
Exactly what I needed. Thanks for this
This allows me to export the proxyaddress attributes for all user within the given OU of “HQ, Users” in this example and separates them with a ;
get-aduser -Filter * -SearchBase “ou=Users,ou=HQ,dc=tailspintoys,dc=co,dc=uk” -properties * | select-object samaccountname, @{“name”=”proxyaddresses”;”expression”={$_.proxyaddresses -join “;”}} | Export-Csv C:\test.csv
Then this imports the created .csv back in..
$admail=Import-CSV C:\test.csv
Foreach ($a in $admail) {
Set-ADUser $a.SamAccountName -Clear proxyaddresses
$a.proxyaddresses -split “;” | % {Set-ADUser $a.samaccountname -Add @{proxyaddresses=$_ +” “}}#
}
I used these to copy attributes across to a new domain after using ADMT to migrate users and passwords. The client is using Office 365 and no longer wanted to maintain Exchange.
Thanks for all your help!
that’s brilliant – thanks
Hi, Can anyone help me out with an issue I am running with for a month now. 🙁
I wanted to export Multi-Value Properties such as MemberOf, ProxyAddresses, etc., I am able to do it either by using Join() or creating custom object array.
The problem is I need these multi-value property exported in one cell in multiple lines in CSV.
Is it possible ?
Any positive answers much appreciated.
Regards,
Karthi
Hi Ben,
that indeed fixes the issues I had with the transportlog in exchange. The problem I have is that I’ve got too many recipients and they’re cut.. I get something like this:
ben@test.com;simone@test2.com;name.lastnam-
And it ends it with a dash.. $FormatEnumerationLimit =-1 didn’t help. Thanks!
Hello,
Is it possible to use one-liner query to make output from the “get-messagetrackinglog” example above with single recipient on each line ?
example:
timestamp,sender,recipient
7/1/2014 16:04, netappopsmgr@millersystms.com, @millersystms.com
7/1/2014 16:04, netappopsmgr@millersystms.com, svc_swalertcentral@millersystms.com
OK, I have found one solution:
Import-Csv -Path C:\temp\message_log.csv |
% {$row = $_; $_.Recipients.split(“;”)} |
% {$row.Recipients=$_; $row} |
Export-Csv C:\temp\message_log2.csv
or maybe this one (not tested)
Get-MessageTrackingLog -EventID “RECEIVE” -Start “7/1/2014 3:59:00 PM” -End “7/4/2014 4:09:00 PM” |
% {$row = $_; $_.Recipients} | % {$row.Recipients=$_; $row} |
Select Timestamp, EventId, Source,MessageId, MessageSubject, Sender,Recipients, ClientIp, ClientHostname, ServerIp, ServerHostname, MessageInfo |
Export-Csv C:\temp\message_log.csv
The second one-liner throws error:
“% : ‘Recipients’ is a ReadOnly property.”
“Select” moved before % {} to make it work:
Get-MessageTrackingLog -EventID “RECEIVE” -Start “7/1/2014 3:59:00 PM” -End “7/4/2014 4:09:00 PM” |
Select Timestamp, Recipients, MessageSubject, Sender |
% {$row = $_; $_.Recipients} |
% {$row.Recipients=$_; $row} |
Export-Csv C:\temp\message_log2.csv
Hello Ben,
First thanks for this very detailed explanation it was super helpful.
I’m wondering if I can’t ask for a tip on how to use this exported data, specifically the Proxy addresses. We’ve found we need to export all distribution groups from our legacy exchange, remove them & then re-create them with the same attributes in O365.
Long story short this has been a bit of a nightmare.
I’m wondering if you know of a way to import the proxy address field exported above in such a way that I wont have to reformat it? Or failng that another approch to export the proxy addresses so that it will place nice with “Set-DistributionGroup -Identity $group.DisplayName -EmailAddresses”?
Actually nm! I found a way to export the data in such a way myself 🙂
http://blogs.technet.com/b/heyscriptingguy/archive/2013/07/22/export-user-names-and-proxy-addresses-to-csv-file.aspx
Hi Chris,
Glad you found your answer! Thanks for stopping by and happy to hear that you found the post useful.
– Ben
Thank you Very much, Awesome article!!
Ben,
Thanks for sharing. When using Export-Csv it is always helpful to use the -Encoding parameter to preserve any non ASCII character, e.g. -Encoding UTF8
Thanks very much. Very useful
Copy pasting example 2 into powershell 4.0, I get the following value under the heading “members” in my CSV:
System.Collections.ArrayList
It would be great if there was a way to retrieve individual values from multivalued properties, but I don’t think this is it.
Revisiting this because it still doesn’t work for me at all, and I’m perplexed that others report that it does. Let’s look at the AD account for someguy, who has a half dozen proxyaddresses:
get-aduser someguy | select name, @{Name =’proxyaddresses’;Expression={[string]::Join(“;”,($_.proxyaddresses))}}
Returns this for proxyaddresses:
Microsoft.ActiveDirectory.Management.ADPropertyValueCollection
I’ve tried ‘get-aduser -properties proxyaddresses’ and ‘get-aduser -properties *’, but no dice.
The example shown as example 2 still is giving me the result reported earlier. Here is some sample output for one of my groups:
Name : SCIT
DisplayName : SCIT
SamAccountName : SCIT525811860015973
GroupType : Universal
members : System.Collections.ArrayList
Is there a special config needed for Powershell for this? I find it odd that others report this working, yet when I run the identical command, nope.
Found an answer that DOES work on a different blog:
To split up group members, as shown in your Example 2, here is what does work for me:
@{Name=‘Members’;Expression={$_.members -join “;”}}
Your example shows:
@{Name=‘Members’;Expression={[string]::join(“;”, ($_.members))}}
This does not work for me at all. Powershell 4.0 on Windows 8.1.
I’d be interested in understanding why the difference, as I encounter this problem frequently. It’s hard to customize to fit my own solutions without understanding why or when or which works.
I had the same problem, copied you and it worked great.
Thanks for this comment, I was using something similar and just kept getting the System.Collections.ArrayList
I don’t fully understand but this article goes into the [string]::join, and it shows later on where the output of the join is listing objects, which is what I think we are seeing. Not show why it seems to be working for some people, but maybe something has changed. I am using powershell 5.1
https://devblogs.microsoft.com/scripting/join-me-in-a-few-string-methods-using-powershell/
Just a note about Example 3
“Query all users in a domain, and list each user’s group memberships – all of them – in a single concatenated field”
It doesn’t pull the Primary group of the user (and will return blank if the user is only in one group). At least not in older versions of powershell.
What I am trying to figure out is how to turn this on its head and import-csv when you have the example above and then be able to use the multiple entry field. As an example:
csv Test,Hi;helloheya
$csv = import-csv c:\PathToCsv
$csv.tst
and then get an array to work with so it ends up looking like
hi
hello
heya
in order to perform say a foreach loop against each one.
Nice Article Ben,
I’ve been working with this type of thing a lot lately. Very useful and very powerful when used correctly! I know it’s saved me tons of time! One thing that I would recommend when using Export-Csv is to use the -NoTypeInformation parameter which will spit out the same csv file but without the “#Type.bla.bla.bla” header information so you can save a step in having to delete it in the spreadsheet.
Just a nice little tip I’ve learned while working with PowerShell. I hope this info come in handy in your PowerShell travels!
Thanks Andrew, good tip! Never let it be said that PowerShell doesn’t have enough switches…
Great Info Ben and thanks Andrew for the tip about -NoTypeInformation