Skip to content

Instantly share code, notes, and snippets.

@guillaumemeyer
Last active April 24, 2024 15:17
Show Gist options
  • Star 48 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save guillaumemeyer/fe4ed1b818c5d99eabff1c792a366f71 to your computer and use it in GitHub Desktop.
Save guillaumemeyer/fe4ed1b818c5d99eabff1c792a366f71 to your computer and use it in GitHub Desktop.
Exports the owners and members of a Microsoft Teams team as a CSV file
// ****************************************************************************************************************************
// Abstract:
// This script is just a quick hack to export the owners and members of a team as a CSV file without administrator permissions.
//
// Usage:
// 1. Open your team
// 2. Select "Manage team" from its menu
// 3. Select the "Members" tab
// 4. Expand the "Owners" and "Members and guests" sections
// 5. Make sure to scroll down to the end of the owners and members lists to include all of them in your export (As the members are loaded on demand)
// 6. Open your browser console
// 7. Copy and paste all the content of this script to the console and type "Enter"
// 8. The CSV file download should start automatically
//
// ToDo:
// - Parse tags to include them in the export
// ****************************************************************************************************************************
$(function() {
// **************
// Initialization
// **************
const csvFileName = 'team-membership-roster-export.csv'
const csvDelimiter = ','
const csvHeader = 'Display Name' + csvDelimiter + 'Title' + csvDelimiter + 'Location' + csvDelimiter + 'Role' + csvDelimiter + 'UPN' + '\r\n' // CSV header row
let csvContent = csvHeader // Initialize CSV content
const rosterLength = $('.td-member-display-name').length // Number of visible members
// Check if we're an owner of the team
let roleSelector = '.td-member-role' // Consider we're not an owner by default
if ($('.td-member-editable-role').length > 0) {
roleSelector = '.td-member-editable-role' // Override if we're an owner
}
// ************************
// Iterate over each member
// ************************
for (let index = 0; index < rosterLength; index++) {
// Extract the display name, title, location and role
const displayName = $('.td-member-display-name').eq(index).text()
const title = $('.td-member-title').eq(index).text()
const location = $('.td-member-location').eq(index).text()
const role = $(roleSelector).eq(index).text()
const upn = $('.td-member-photo img').eq(index).attr('upn')
// Append to the CSV content
const csvRow = displayName + csvDelimiter + title + csvDelimiter + location + csvDelimiter + role + csvDelimiter + upn + '\r\n'
csvContent += csvRow
}
// Debug the export to console
console.info(rosterLength + ' members exported:')
console.info(csvContent)
// **********************************************************
// Dynamically generates a CSV file and triggers its download
// **********************************************************
// Create a dynamic "a" tag
var element = document.createElement('a')
// Set href link with content
element.setAttribute(
'href',
'data:application/json;charset=utf-8,' + encodeURIComponent(csvContent)
)
// Set downloaded file name
element.setAttribute('download', csvFileName)
// Hide the elemement and add it to the page
element.style.display = 'none'
document.body.appendChild(element)
// Launch download
element.click()
// Remove element
document.body.removeChild(element)
})
@guillaumemeyer
Copy link
Author

@ChrisFre3500 Ce script est tres tres hacky, et peut ne plus fonctionner a tout instant... Essaye le fork de @joegasper ici pour une version probablement plus a jour: https://gist.github.com/joegasper/05afb0dac27f1ed2379c8ab296295e5d

@suviskr
Copy link

suviskr commented Jul 13, 2023

Salut Guillaume,

I am not very code literate (yet), but I would like to tweak the code so that all the information is not in one column, but instead different ones? Could that be done and how?

@guillaumemeyer
Copy link
Author

Looks like an issue here @suviskr , as on my side the generated CSV has multiple columns and renders properly... can you share the generated CSV? (without names of course)

@suviskr
Copy link

suviskr commented Jul 13, 2023

Sure, but I would have to delete all the rows then as it is a big list :) Is this helpful? https://www.icloud.com/iclouddrive/0d9gUL_pJifIbHBR2kS7g-1nw#team-membership-roster-export

@guillaumemeyer
Copy link
Author

Looks like the CSV is properly formatted...
image
Maybe I'm not understanding the issue here

@suviskr
Copy link

suviskr commented Jul 13, 2023

Hmm, for me it is all in one cell:
image

@guillaumemeyer
Copy link
Author

ok I understand now.
The generated export file is a CSV (for "comma-separated"), therefore columns are separated by a "," as defined l25

const csvDelimiter = ','

image

Looks like Excel (the one you're using at least) doesn't splits columns properly.
You can fix that from Excel:

  • Select column A
  • Data tab, "text-to column"
  • Select a fixed delimiter as a comma
    image

@moeba2008
Copy link

Ditto, thank you. I've added support for Tags (and commas in the data).

Perfect. Cheers mate

@moeba2008
Copy link

@ChrisFre3500 Ce script est tres tres hacky, et peut ne plus fonctionner a tout instant... Essaye le fork de @joegasper ici pour une version probablement plus a jour: https://gist.github.com/joegasper/05afb0dac27f1ed2379c8ab296295e5d

Ca marche parfaitement. Nickel! Merci

@suviskr
Copy link

suviskr commented Jul 13, 2023

ok I understand now. The generated export file is a CSV (for "comma-separated"), therefore columns are separated by a "," as defined l25

const csvDelimiter = ','

Thanks! That helps, indeed!

@LaurinchenMe
Copy link

ok I understand now. The generated export file is a CSV (for "comma-separated"), therefore columns are separated by a "," as defined l25

const csvDelimiter = ','

image

Looks like Excel (the one you're using at least) doesn't splits columns properly. You can fix that from Excel:

  • Select column A
  • Data tab, "text-to column"
  • Select a fixed delimiter as a comma
    image

I saw your comment just now, thank you! I encountered the same problem with excel, my solution was to fork your script and only using the upn, as this is the only info that I need.

@poachedeggswithhollandaise

Thanks for this, I have got it to work for 1 team, scrolled all the way down but only got the owners exported, all other attempts haven't worked
image

@guillaumemeyer
Copy link
Author

@poachedeggswithhollandaise Anything from the console logs?

@poachedeggswithhollandaise

@poachedeggswithhollandaise Anything from the console logs?

Sorry, I'm not sure what you mean?

@poachedeggswithhollandaise

@poachedeggswithhollandaise Anything from the console logs?

Sorry, I'm not sure what you mean?

image
This is the response I get in the console, the 6 are just the owners, there's another 100 members

@UtahITPro
Copy link

@guillaumemeyer I have the same issue as @poachedeggswithhollandaise. It doesn't matter if I am an owner of the team or not. The code is only exporting the list of Owners and missing the additional list of Members and Guests.

I tried to troubleshoot, and this code appears to only return the count for the top list.
const rosterLength = $('.td-member-display-name').length // Number of visible members

When searching, I find two instances of '.td-member-display-name', so it looks like it's only iterating through the first list. I don't know what I'm doing and can't get past this issue.

Not sure if there was a recent change with the Teams or if it has to do with a limitation in the Edge browser. (That's all I have available on my work PC.)

Any ideas?

@jromano1
Copy link

This is incredible and worked perfectly for me. As long as I scrolled down so that the entire membership was pulled into view (I also had better luck with the zoom set as far out as possible).
Thank you so much for sharing this code it saved me hours!

@mrvuyk
Copy link

mrvuyk commented Oct 19, 2023

So not sure if there has been an update since last week. I was getting only Owners. Now it runs and says undefined. Tried in Edge and Chrome.

@guillaumemeyer
Copy link
Author

Hi folks,
I'm extremely busy these days and have no time to look at the issues.
Please have a look at @joegasper 's fork to see if you have the same issue with it's version.
@joegasper ever encountered this kind of issues?

@dkagan0312
Copy link

@guillaumemeyerI have the same issue as@poachedeggswithhollandaise. It doesn't matter if I am an owner of the team or not. The code is only exporting the list of Owners and missing the additional list of Members and Guests.

I tried to troubleshoot, and this code appears to only return the count for the top list. const rosterLength = $('.td-member-display-name').length // Number of visible members

When searching, I find two instances of '.td-member-display-name', so it looks like it's only iterating through the first list. I don't know what I'm doing and can't get past this issue.

Not sure if there was a recent change with the Teams or if it has to do with a limitation in the Edge browser. (That's all I have available on my work PC.)

Any ideas?

Hi @guillaumemeyer - I'm also only able to export the Owners of the group. Any ideas on how to fix that? I'm following all of the steps that have been outlined at the beginning of the code.

Screenshot 2023-11-03 142511

Screenshot 2023-11-03 142531

I'm thinking it could have something to do with this section of the code:
// Check if we're an owner of the team let roleSelector = '.td-member-role' // Consider we're not an owner by default if ( $ ( '.td-member-editable-role' ) . length > 0 ) { roleSelector = '.td-member-editable-role' // Override if we're an owner }

Thanks!

@LouiseB1
Copy link

Hi @guillaumemeyer ! I read your recent comment that you are very busy however when you find time, could you help to check why for some it only downloads the Owner and not the members even if the members list are completely listed/expanded?

@pjgroeneveld
Copy link

Same issue here. Only the administrators are currently being exported.
I hope someone can resolve this issue, since this script has been very handy for us so far

@joegasper
Copy link

I have slightly updated code here with a couple of enhancements. I am still able to export full membership for teams I'm in (owner or just a member) testing up to a couple hundred members. I am having problems getting a full export for a larger team (1200+ members) that I used to be able to export.

I believe we are starting to see changes coming to Teams web, as Microsoft has the new Teams web client now available (depending on how your organization is rolling out new Teams).

In Classic Teams, the Javascript code has clear methods to decern the appropriate sections of the webpage to grab and export. For example, the code keys on the CLASS attribute to find the member's display name against a DIV tag like below:

<div class="td-member-display-name">
    <span>
        James T. Kirk
    </span>
</div>

In new Teams web client, the code would be sifting through this new HTML code:

<div class="fui-Flex ___dkbdks0 f1bxpd7w">
	<div class="___87nhvx0 fau6ura f3052tw lpcCommonWeb-hoverTarget container-161">
		<span role="img" id="avatar-12580" class="fui-Avatar r81b29z ___1ywy82q f1tlnv9o f13ar0e0 fid048z">
				<div role="img" id="presence-pill-8:orgid:e2d2f6e0" class="fui-PresenceBadge r832ydo fui-Avatar__badge ___1i6sv73 f11d4kpn f1euv43f f1yab3r1 f1e31b4d">
				</div>
			</span>
		</div>
		<span class="fui-StyledText ___1a76sp3 fz5stix f1p9o1ba f1sil6mw f1cmbuwj f1g0x7ka fhxju0i f1qch9an frdkuqy" aria-label="James T. Kirk (Guest)">James T. Kirk (Guest)</span>
	</div>
</div>

In the new Teams web client code above, I think it gets harder to drill into the proper attribute to retrieve the data we want.

@tahlzdavis
Copy link

tahlzdavis commented Nov 14, 2023

I'm only able to export the owners in Edge, but this still works in Chrome :)

@pjgroeneveld
Copy link

For me, it's also only exporting the administrators, not the members :-(

@dkagan0312
Copy link

I have slightly updated code here with a couple of enhancements. I am still able to export full membership for teams I'm in (owner or just a member) testing up to a couple hundred members. I am having problems getting a full export for a larger team (1200+ members) that I used to be able to export.

I believe we are starting to see changes coming to Teams web, as Microsoft has the new Teams web client now available (depending on how your organization is rolling out new Teams).

In Classic Teams, the Javascript code has clear methods to decern the appropriate sections of the webpage to grab and export. For example, the code keys on the CLASS attribute to find the member's display name against a DIV tag like below:

<div class="td-member-display-name">
    <span>
        James T. Kirk
    </span>
</div>

In new Teams web client, the code would be sifting through this new HTML code:

<div class="fui-Flex ___dkbdks0 f1bxpd7w">
	<div class="___87nhvx0 fau6ura f3052tw lpcCommonWeb-hoverTarget container-161">
		<span role="img" id="avatar-12580" class="fui-Avatar r81b29z ___1ywy82q f1tlnv9o f13ar0e0 fid048z">
				<div role="img" id="presence-pill-8:orgid:e2d2f6e0" class="fui-PresenceBadge r832ydo fui-Avatar__badge ___1i6sv73 f11d4kpn f1euv43f f1yab3r1 f1e31b4d">
				</div>
			</span>
		</div>
		<span class="fui-StyledText ___1a76sp3 fz5stix f1p9o1ba f1sil6mw f1cmbuwj f1g0x7ka fhxju0i f1qch9an frdkuqy" aria-label="James T. Kirk (Guest)">James T. Kirk (Guest)</span>
	</div>
</div>

In the new Teams web client code above, I think it gets harder to drill into the proper attribute to retrieve the data we want.

Hey @joegasper, great insight. Do you think there is a way to write the code so that it tries to pick up every time is says "member"? (see image)
image

@Mast3rXL
Copy link

Hey, I just copy paste it directly from teams in excel (New Teams). Select 'Name' under Manage channels>Members&guest till the last member (four columns, not using tags). Next I paste it in column B in excel. In column A I indicate if it is a Name, Title, Location or Role before the record.
This VBA script will make a decent table as from column D to G:

Sub ListMaker()
Dim lastRow As Long
Dim i As Long
Dim destRow As Long

Range("D2:G389").Select
Selection.ClearContents
Range("C10").Select

' Find the last row with data in column A
lastRow = Cells(Rows.Count, 1).End(xlUp).Row

' Initialize the destination row
destRow = 2

' Loop through each row
For i = 2 To lastRow
    ' Check the value in column A and transfer the corresponding value to the appropriate column
    Select Case UCase(Cells(i, 1).value)
        Case "NAME"
            Cells(destRow, 4).value = Cells(i, 2).value
        Case "TITLE"
            Cells(destRow, 5).value = Cells(i, 2).value
        Case "LOCATION"
            Cells(destRow, 6).value = Cells(i, 2).value
        Case "ROLE"
            Cells(destRow, 7).value = Cells(i, 2).value
        ' Add more cases as needed
    End Select

    ' Move to the next destination row after processing each category
    If UCase(Cells(i, 1).value) = "ROLE" Then
        destRow = destRow + 1
    End If
Next i

Columns("B:B").Select
Selection.ClearContents
Range("B10").Select

End Sub

@gk4delltech
Copy link

gk4delltech commented Apr 5, 2024

Does this work for https://teams.microsoft.com/v2/ because I don't get a CSV file.

image

@PodyPoe
Copy link

PodyPoe commented Apr 24, 2024

Does this work for https://teams.microsoft.com/v2/ because I don't get a CSV file.

image

I've been unable to get this to function in "New Teams" myself.

@guillaumemeyer
Copy link
Author

This script is not easily maintainable due to its reliance on unpredictable MS UI updates.
I'm currently investigating a completely different solution to achieve the same result.
Stay tuned

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment