SharePoint Online Management Shell has its own dedicated cmdlet family for site-collection-level group operations — distinct from the PnP PowerShell cmdlets covered elsewhere on this site for adding users to an existing group. This post covers the SPO Management Shell versions specifically: creating, modifying, and removing groups, plus a couple of real gotchas that don’t show up until a script runs against a site with more data than a quick test environment has, or against a parameter whose default behavior isn’t what its name implies.
In this post: Connecting and creating a group · Adding and removing members · Adding a security group instead of a user · A real gotcha in Remove-SPOUser · Renaming, changing owner, and permission levels · Deleting a group, and listing them all · Bulk membership from a CSV · Related reading
Connecting and creating a group
Connect-SPOService -Url "https://yourtenant-admin.sharepoint.com"
New-SPOSiteGroup -Site "https://yourtenant.sharepoint.com/sites/yoursite" -Group "New Group" -PermissionLevels "Read"
Calling `Connect-SPOService` with just the URL, no stored credential, prompts interactively — the correct approach now that legacy username/password authentication has been fully retired tenant-wide. `New-SPOSiteGroup` takes the permission level directly as a creation-time parameter, unlike changing permissions on an existing group later, which works differently (see below).
Adding and removing members
Add-SPOUser -Site "https://yourtenant.sharepoint.com/sites/yoursite" -Group "Existing Group" -LoginName "user@example.com"
Remove-SPOUser -Site "https://yourtenant.sharepoint.com/sites/yoursite" -Group "Existing Group" -LoginName "user@example.com"
Both cmdlets take the same three parameters, and both are real, current cmdlets in SharePoint Online Management Shell — worth confirming directly since it’s easy to assume these are PnP-only operations.
Adding a security group instead of a user
Worth knowing since it’s not obvious from the parameter name: `-LoginName` on `Add-SPOUser` isn’t limited to individual users — it also accepts a Microsoft 365 or Entra security group’s identifier, added to the SharePoint group the same way an individual user would be:
Add-SPOUser -Site "https://yourtenant.sharepoint.com/sites/yoursite" -Group "Existing Group" -LoginName "c:0t.c|tenant|11111111-1111-1111-1111-111111111111"
Managing access at the security-group level rather than adding individual people one at a time is usually the better long-term choice for anything beyond a handful of users — membership changes then happen in Entra ID, where they’re likely already being managed for other reasons, rather than needing a separate SharePoint-specific script run every time someone joins or leaves a team.
Remove-SPOUser’s -Group parameter is optional — omit it, and the user is removed from every group on the site, not left untouched.
A real gotcha in Remove-SPOUser
Worth knowing before running this against production: `-Group` on `Remove-SPOUser` is optional, not required. Leave it off, and the cmdlet doesn’t error or do nothing — it removes the user from every group on the site collection at once. A script meant to remove someone from one specific group, with a typo or a missing variable substitution in the `-Group` value, can silently strip that person’s access site-wide instead of the narrow change intended. Worth double-checking the parameter is actually populated before running this against anything that matters, not just trusting the script ran without an error.
Renaming, changing owner, and permission levels
Worth a direct correction here: there’s no `Set-SPOSiteGroupPermissions` cmdlet — it doesn’t exist in SharePoint Online Management Shell. Changing an existing group’s permission levels, its owner, or its name is all handled by `Set-SPOSiteGroup`, using an add/remove pair for permission changes rather than a single overwrite parameter:
# Change permission level from Full Control to View Only
Set-SPOSiteGroup -Site "https://yourtenant.sharepoint.com/sites/yoursite" `
-Identity "Existing Group" -PermissionLevelsToRemove "Full Control" -PermissionLevelsToAdd "View Only"
# Change the group's owner
Set-SPOSiteGroup -Site "https://yourtenant.sharepoint.com/sites/yoursite" `
-Identity "Existing Group" -Owner "owner@example.com"
# Rename the group
Set-SPOSiteGroup -Site "https://yourtenant.sharepoint.com/sites/yoursite" `
-Identity "Existing Group" -Name "Renamed Group"
Worth noting directly: adding a permission level doesn’t replace the existing one automatically — a group with both “Full Control” and “View Only” simultaneously is a real, valid (if usually unintended) state if `-PermissionLevelsToRemove` is left off a change that was meant to replace rather than add.
Deleting a group, and listing them all
Remove-SPOSiteGroup -Site "https://yourtenant.sharepoint.com/sites/yoursite" -Identity "Existing Group"
# List every group on the site, with members
$groups = Get-SPOSiteGroup -Site "https://yourtenant.sharepoint.com/sites/yoursite" -Limit 500
foreach ($group in $groups) {
Write-Output "Group Name: $($group.Title)"
foreach ($member in $group.Users) {
Write-Output " - $($member.LoginName)"
}
}
Worth flagging directly, since it’s a real silent-truncation trap: `Get-SPOSiteGroup` defaults to returning only the first 200 groups on a site collection if `-Limit` isn’t specified — not an error, just an incomplete list with no indication anything was cut off. A site with more than 200 groups (larger than it sounds once nested project-level groups accumulate over time) needs `-Limit` set explicitly above that count to get a genuinely complete report, not the default.
Bulk membership from a CSV
Adding a batch of users from a spreadsheet is the same `Add-SPOUser` call, just looped over imported rows rather than run individually by hand:
$users = Import-Csv -Path "C:\Path\To\Users.csv" # columns: LoginName, Group
foreach ($user in $users) {
Add-SPOUser -Site "https://yourtenant.sharepoint.com/sites/yoursite" `
-Group $user.Group -LoginName $user.LoginName
Write-Output "Added $($user.LoginName) to $($user.Group)"
}
Worth adding a pre-check before running this against a list that might include people already in the group — checking current membership first with `Get-SPOUser -Site $siteUrl -Group $groupName` and filtering the CSV against that list avoids depending on assumptions about how the cmdlet handles an already-existing member, and makes the script safe to re-run against the same file without extra manual bookkeeping either way.
Related reading
- Add Users to SharePoint Group with PowerShell — the PnP PowerShell equivalent of the add/remove operations covered here, if that’s the module already in use.
SPO Management Shell and PnP PowerShell both offer real, working ways to manage SharePoint groups — worth picking one module consistently for a given script rather than mixing cmdlets from both, since their underlying object models and gotchas (like the two covered above) don’t carry over between them. Whichever module a script ends up using, the same underlying discipline applies: verify what a parameter actually does — optional versus required, additive versus replacing — before running a change against production, rather than assuming based on what the parameter name suggests.
App Catalog Authentication Automation Backup Compliance Content Type CSS Flows Google Javascript Limitations List Metadata MFA Microsoft Node NodeJs O365 OneDrive Permissions PnP PnPJS Policy PowerApps Power Automate PowerAutomate PowerPlatform PowerShell React ReactJs Rest API Rest Endpoint Security Send an HTTP Request to SharePoint SharePoint SharePoint List SharePoint Modern SharePoint Online SPFX SPO Sync Tags Teams Termstore Versioning


