How to Export SharePoint Group Members to CSV Using PnP PowerShell
SharePoint groups are commonly used to manage access to sites, lists, libraries, folders, and documents. During migrations, permission reviews, security audits, or general administration, you may need to generate a report containing all users who belong to a specific SharePoint group.
In this article, we will use PnP PowerShell to retrieve the direct members of a SharePoint group and export the following information to a CSV file:
- Display name
- Email address
- SharePoint login name or claims value
The resulting CSV can be opened in Microsoft Excel, imported into Power BI, or processed by another PowerShell, C#, or reporting solution.
Important distinction: SharePoint groups and Microsoft 365 groups
This article focuses on a SharePoint group, such as:
- Site Owners
- Site Members
- Site Visitors
- A custom SharePoint permission group
A SharePoint group belongs to a specific SharePoint site collection and is generally used to grant SharePoint permissions.
This is different from:
- Microsoft 365 groups
- Microsoft Entra ID security groups
- Distribution lists
- Microsoft Teams membership
The Get-PnPGroupMember cmdlet retrieves members of a SharePoint group in the currently connected site. It does not automatically behave as a recursive Microsoft Entra ID group-expansion tool.
Prerequisites
You need:
- Access to the target SharePoint site
- Permission to read the SharePoint group membership
- PowerShell 7
- PnP PowerShell
- A Microsoft Entra ID application registration for interactive authentication
Current PnP PowerShell documentation requires PowerShell 7.4 or later for the latest supported module versions. PnP PowerShell is a cross-platform module that can be used on Windows, Linux, and macOS.
Step 1: Check whether PnP PowerShell is installed
Open PowerShell 7 and run:
Get-Module PnP.PowerShell -ListAvailable
If no module is returned, install PnP PowerShell:
Install-Module PnP.PowerShell -Scope CurrentUser
If the module is already installed, it can be updated with:
Update-Module PnP.PowerShell
You can confirm the installed version with:
Get-InstalledModule PnP.PowerShell
Step 2: Define the SharePoint site URL
Create a variable containing the URL of the site where the SharePoint group exists:
$SiteUrl = "https://contoso.sharepoint.com/sites/Finance"
The connection must target the correct site because SharePoint groups are associated with a specific site collection.
A group called Finance Members, for example, may exist in the Finance site but not in another site collection.
Step 3: Connect to SharePoint Online
Use Connect-PnPOnline with interactive authentication:
Connect-PnPOnline ` -Url $SiteUrl ` -Interactive ` -ClientId "YOUR-APPLICATION-CLIENT-ID"
Replace YOUR-APPLICATION-CLIENT-ID with the client ID of your Microsoft Entra ID application registration.
Interactive authentication supports modern authentication requirements such as multifactor authentication and Conditional Access. Current PnP PowerShell guidance uses an Entra ID application client ID for interactive connections.
After authentication, confirm that the connection is working:
Get-PnPWeb
This should return information about the current SharePoint web, including its title and server-relative URL.
Step 4: List the SharePoint groups
Before retrieving members, list the groups available in the current site:
Get-PnPGroup | Select-Object Id, Title, LoginName | Format-Table -AutoSize
The output should look similar to this:
Id Title LoginName-- ----- ---------3 Finance Owners Finance Owners4 Finance Members Finance Members5 Finance Visitors Finance Visitors
Copy the exact title of the group you want to export.
For example:
$GroupName = "Finance Members"
Using the exact title avoids errors caused by spelling differences, spaces, or unexpected group names.
Step 5: Retrieve the group members
Use Get-PnPGroupMember to retrieve the direct members of the selected SharePoint group:
$Members = Get-PnPGroupMember -Group $GroupName
The Get-PnPGroupMember cmdlet retrieves members from a SharePoint group in the current PnP connection.
To inspect the returned values before generating the CSV, run:
$Members | Select-Object Title, Email, LoginName | Format-Table -AutoSize
Example output:
Title Email LoginName----- ----- ---------John Smith john.smith@contoso.com i:0#.f|membership|john.smith@contoso.comMaria Silva maria.silva@contoso.com i:0#.f|membership|maria.silva@contoso.com
The relevant SharePoint user properties are:
| SharePoint property | CSV column | Description |
|---|---|---|
Title | DisplayName | User-friendly display name |
Email | Email | Email address stored in SharePoint |
LoginName | Claims | SharePoint login or claims identity |
Understanding the claims value
In SharePoint Online, a normal user login frequently appears in the following format:
i:0#.f|membership|john.smith@contoso.com
This is the SharePoint claims-encoded login name.
The value can be divided into two general parts:
i:0#.f|membership|
This is the claims provider prefix.
john.smith@contoso.com
This is the user principal name associated with the account.
Keeping the complete claims value in the report is useful when:
- Comparing SharePoint permissions
- Migrating users
- Investigating unresolved accounts
- Updating person fields
- Creating permission reports
- Processing users with PowerShell or C#
Step 6: Create the destination folder
Define the CSV path:
$CsvPath = "C:\Temp\SharePointGroupMembers.csv"
Make sure the destination folder exists:
$OutputFolder = Split-Path -Path $CsvPath -Parentif (-not (Test-Path -Path $OutputFolder)) { New-Item ` -Path $OutputFolder ` -ItemType Directory ` -Force | Out-Null}
This prevents Export-Csv from failing because the destination directory does not exist.
Step 7: Prepare the report objects
Instead of exporting the original SharePoint objects directly, create new PowerShell objects containing only the required properties:
$Report = $Members | ForEach-Object { [PSCustomObject]@{ DisplayName = $_.Title Email = $_.Email Claims = $_.LoginName }}
This gives us full control over:
- Column names
- Column order
- Which values are included
- Future transformations
- Additional validation
The resulting objects contain exactly three properties:
DisplayNameEmailClaims
Step 8: Export the report to CSV
Export the report with:
$Report | Sort-Object DisplayName | Export-Csv ` -Path $CsvPath ` -NoTypeInformation ` -Encoding UTF8 ` -Delimiter ";"
Export-Csv converts PowerShell objects into CSV rows and saves them to a file. Each object becomes one row, and each selected object property becomes one column.
Why use -NoTypeInformation?
This prevents PowerShell type metadata from being written to the beginning of the CSV file.
Why use -Encoding UTF8?
UTF-8 helps preserve accented and international characters in names such as:
João GonçalvesJosé MaríaFrançois Dupont
Why use a semicolon delimiter?
In many regional versions of Microsoft Excel, including common Brazilian configurations, the semicolon is more reliably recognized as the column separator.
The output will look similar to this:
"DisplayName";"Email";"Claims""John Smith";"john.smith@contoso.com";"i:0#.f|membership|john.smith@contoso.com""Maria Silva";"maria.silva@contoso.com";"i:0#.f|membership|maria.silva@contoso.com"
If your environment expects a comma-separated file, remove the delimiter parameter or use:
-Delimiter ","
Complete PowerShell script
The following script combines all the previous steps:
# ============================================================# Configuration# ============================================================$SiteUrl = "https://contoso.sharepoint.com/sites/Finance"$GroupName = "Finance Members"$ClientId = "YOUR-APPLICATION-CLIENT-ID"$CsvPath = "C:\Temp\SharePointGroupMembers.csv"# ============================================================# Create the output folder# ============================================================$OutputFolder = Split-Path -Path $CsvPath -Parentif (-not (Test-Path -Path $OutputFolder)) { New-Item ` -Path $OutputFolder ` -ItemType Directory ` -Force | Out-Null}# ============================================================# Connect to SharePoint Online# ============================================================Connect-PnPOnline ` -Url $SiteUrl ` -Interactive ` -ClientId $ClientId# ============================================================# Validate the SharePoint group# ============================================================$Group = Get-PnPGroup -Identity $GroupNameif ($null -eq $Group) { throw "The SharePoint group '$GroupName' was not found."}Write-Host ""Write-Host "Group found: $($Group.Title)" -ForegroundColor Cyan# ============================================================# Retrieve the direct group members# ============================================================$Members = @( Get-PnPGroupMember -Group $Group)if ($Members.Count -eq 0) { Write-Warning "The SharePoint group does not contain any direct members." return}# ============================================================# Prepare the report# ============================================================$Report = $Members | ForEach-Object { [PSCustomObject]@{ DisplayName = $_.Title Email = $_.Email Claims = $_.LoginName }}# ============================================================# Export the report# ============================================================$Report | Sort-Object DisplayName | Export-Csv ` -Path $CsvPath ` -NoTypeInformation ` -Encoding UTF8 ` -Delimiter ";"# ============================================================# Display the result# ============================================================Write-Host ""Write-Host "Export completed successfully." -ForegroundColor GreenWrite-Host "Site: $SiteUrl"Write-Host "Group: $($Group.Title)"Write-Host "Members exported: $($Report.Count)"Write-Host "CSV file: $CsvPath"Invoke-Item $OutputFolder
Adding error handling
For production use, the main operations can be placed inside a try/catch block:
try { Connect-PnPOnline ` -Url $SiteUrl ` -Interactive ` -ClientId $ClientId ` -ErrorAction Stop $Group = Get-PnPGroup ` -Identity $GroupName ` -ErrorAction Stop $Members = @( Get-PnPGroupMember ` -Group $Group ` -ErrorAction Stop ) $Report = $Members | ForEach-Object { [PSCustomObject]@{ DisplayName = $_.Title Email = $_.Email Claims = $_.LoginName } } $Report | Sort-Object DisplayName | Export-Csv ` -Path $CsvPath ` -NoTypeInformation ` -Encoding UTF8 ` -Delimiter ";" ` -ErrorAction Stop Write-Host "Export completed: $CsvPath" -ForegroundColor Green}catch { Write-Error "The export failed: $($_.Exception.Message)"}finally { Disconnect-PnPOnline}
The finally block disconnects the active PnP PowerShell session even when an error occurs.
Handling users without email addresses
Some returned SharePoint principals may have an empty Email property.
Possible examples include:
- Legacy user accounts
- Deleted or disabled users
- SharePoint system accounts
- Security groups
- Microsoft Entra ID groups
- Users whose information has not been fully synchronized
- Special claims principals
To identify records without an email address, add a status column:
$Report = $Members | ForEach-Object { [PSCustomObject]@{ DisplayName = $_.Title Email = $_.Email Claims = $_.LoginName EmailStatus = if ([string]::IsNullOrWhiteSpace($_.Email)) { "Missing" } else { "Available" } }}
This produces:
DisplayNameEmailClaimsEmailStatus
You can also export only members with an email address:
$Members | Where-Object { -not [string]::IsNullOrWhiteSpace($_.Email) } | ForEach-Object { [PSCustomObject]@{ DisplayName = $_.Title Email = $_.Email Claims = $_.LoginName } } | Export-Csv ` -Path $CsvPath ` -NoTypeInformation ` -Encoding UTF8 ` -Delimiter ";"
However, excluding users without email addresses may hide important permission assignments. For audit reports, it is generally safer to keep them and identify the missing value.
Direct members versus nested group members
The script exports the principals added directly to the SharePoint group.
For example, consider the following membership:
Finance Members├── John Smith├── Maria Silva└── Finance Department Security Group
The report may return three principals:
John SmithMaria SilvaFinance Department Security Group
It does not necessarily return every user contained inside the Finance Department Security Group.
Expanding Microsoft Entra ID groups requires an additional process, usually involving Microsoft Graph permissions and group membership cmdlets. This is separate from retrieving direct SharePoint group membership.
SharePoint group members versus all site users
Get-PnPGroupMember should not be confused with Get-PnPUser.
Get-PnPGroupMember retrieves users or principals from a particular SharePoint group.
Get-PnPUser retrieves users stored in the site collection’s User Information List. That list can contain users who accessed the site in the past but no longer have permissions through the selected group.
Therefore, this command:
Get-PnPUser
is not a replacement for:
Get-PnPGroupMember -Group $GroupName
when the requirement is specifically to report the members of one SharePoint group.
Common errors
The group cannot be found
Example:
The SharePoint group 'Finance Members' was not found.
List the groups and copy the exact title:
Get-PnPGroup | Select-Object Id, Title | Format-Table -AutoSize
Also confirm that you connected to the correct site collection.
Access denied
The connected account may not have permission to read the group membership.
Test whether the account can access the site and retrieve groups:
Get-PnPWebGet-PnPGroup
The CSV contains formatting information
Do not pass output from Format-Table to Export-Csv.
Incorrect:
$Members | Format-Table Title, Email | Export-Csv -Path $CsvPath
Format-Table creates formatting instructions for console display rather than normal data objects.
Correct:
$Members | Select-Object Title, Email | Export-Csv ` -Path $CsvPath ` -NoTypeInformation
The CSV opens in one Excel column
Use a delimiter compatible with the local Excel configuration:
-Delimiter ";"
PowerShell also supports the current culture’s list separator through:
-UseCulture
For example:
$Report | Export-Csv ` -Path $CsvPath ` -NoTypeInformation ` -Encoding UTF8 ` -UseCulture
Conclusion
PnP PowerShell provides a straightforward way to retrieve direct members from a SharePoint group and export them into a structured CSV report.
The main process is:
- Connect to the correct SharePoint site.
- Locate the SharePoint group.
- Retrieve its direct members.
- Select the required user properties.
- Export the resulting objects to CSV.
The essential command is:
Get-PnPGroupMember -Group $GroupName
Combined with PSCustomObject and Export-Csv, it produces a clean report containing display names, email addresses, and SharePoint claims identities.
This report can support permission audits, migrations, access reviews, governance processes, troubleshooting, and integration with external reporting solutions.
