UPDATE Feb 2023: This version of the script is now obsolete due to changes in Exchange Online. An updated version of the script can be found in this article.
Often times when a user leaves the company, or for whatever other reason, you are tasked with making sure said user is removed as member from any and all groups. In the Office 365 world, this means going over each Security, Distribution, Mail-Enabled Security and Office 365 (Modern) group and removing the user. If you are a fan of the UI approach, you can simply navigate to the Office 365 Admin Center, find and click on the user in the list of Active users, then click the Edit button next to Group membership and press the small X next to each group object.
While this approach works and is easy enough to follow, having to perform the same task over and over again is definitely not something I enjoy, so prepared a short script for this scenario. We have already discussed the quickest way to list all groups given user is a member of in a previous article, so half of the script was already done. After obtaining the list, all that’s left is to iterate over each group and depending on the group type, use the relevant cmdlet to remove the user. Now, as we deal with few very different group types, different cmdlets and even modules will be needed. For any Exchange-related groups, we can of course use the Remove-DistributionGroupMember cmdlet. Exchange remote PowerShell also exposes the cmdlet needed to handle Office 365 Groups: Remove-UnifiedGroupLinks. To handle Azure AD Security groups however, we need the Remove-AzureADGroupMember cmdlet and thus the AzureAD PowerShell module.
To add some flexibility to the script, few parameters have been introduced to handle the different group types. By default, only Exchange-related groups will be handled (DGs and MESGs). To include Office 365 Groups, use the –IncludeOffice365Groups switch. To include Azure AD Security groups, use the –IncludeAADSecurityGroups switch. The -Identity parameter has been coded to accept multiple user objects as an array, and to also handle pipeline input. Any valid user identifier will be accepted, but it’s strongly recommended to use unique-valued properties such as UPN or PrimarySmtpAddress.
To simulate the script action without making any actual changes, which is a very good idea when trying to use it against a large number of objects, use the –WhatIf switch. Lastly, for troubleshooting purpose you can specify the –Verbose switch and get additional information about each step of the script execution. While the script includes some basic code to handle connectivity to Exchange Online and/or Azure AD PowerShell, this will not work in all scenarios. Make sure to execute your “connect to Office 365” script first and establish any needed sessions, otherwise the script will halt.
Combining all of the above, the script does the following:
- Checks for connectivity to ExO and/or AzureAD
- Builds a list of all the users to process, removing any invalid entries
- For each user, obtains the list of all Exchange groups
- If the –IncludeOffice365Groups switch is used, the list will include Office 365 Groups as well
- For each group in the list, the relevant cmdlet is issued to remove the user as member
- If the switch –IncludeAADSecurityGroups is used, Azure AD groups are enumerated next
- For any Azure AD group returned, the Remove-AzureADGroupMember cmdlet is run to remove the user as member
If you are running the script against a large number of users, or if the user is member of to many groups, potential throttling issues might arise. To address those, a small artificial delay is added on lines 104 and 117, feel free to adjust it as needed. Additional information about the script usage and parameters can be found in the built-in help.
You can find the script over at GitHub or on the TechNet Gallery here
Hi Vasil
What a beautiful script that I used for a long time now. Unfortunately I realized this year that it stopped working. After some investigation I found it was because the script still connects to Exchange with basic authentication. Microsoft depreciated basic authentication 31 Dec 2022 so that is why the script stopped working.
Fails here
$script:session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential (Get-Credential) -Authentication Basic -AllowRedirection -ErrorAction Stop
Import-PSSession $session -ErrorAction Stop | Out-Null
I’m currently scouring the internet for something to replace your script. Would actually appreciate it if you could modify yours to use the new exchange connection method. I had a look to see if I could modify it but the script a bit too complex for me.
Regards
H
Sorry, I’ve been updating my scripts to work with the latest module versions, but it takes some time. All you have to do here is change the connectivity bit, when using the V2/V3 module a simple
would do. And you can remove all the other checks.
Hi
Think the change is a bit more complex than that because you refer to the variable $session throughout the script, and this variable won’t exist anymore as line
Import-PSSession $session -ErrorAction Stop | Out-Null
is replaced by
Connect-ExchangeOnline -Credential $secret
I understand that it must be a massive undertaking to change all your scripts. I will regularly come and check if you had a chance to get to this script and change it.
Keep up the good work
Ah, you’re correct, that method cannot be used any longer. I’ll try to push an update to the script by EOW.
Here you go: https://www.michev.info/Blog/Post/4409/remove-user-from-all-microsoft-365-groups-updated-2023-version-of-the-script
Definitely a newb question, I have only just got into powershell! How would I add this to an existing “leaver” PowerShell script I have that uses a variable to pass through the leaver email address ie:
$LeaverUsername = Read-Host “Enter the email of the leaver e.g firstname.surname@companyxyz.com”
Set-AzureADUser -ObjectID $LeaverUsername -AccountEnabled $false
Set-Mailbox -Identity $LeaverUsername -HiddenFromAddressListsEnabled $true
(insert the above script?)
Download the script, place it in the working dir, then add the following line at the bottom of your script:
.\Remove_User_All_Groups.ps1 -Identity $LeaverUsername
Use the other parameters as needed, especially the -WhatIf/-Verbose one to “preview” the changes.
Hello Vasil,
Thank you for the reply, I have tried calling the script via the below and get the following error
Call to script:
.\Allgroups.ps1 -Identity $LeaverUsername
Error:
.\Allgroups.ps1 : The term ‘.\Allgroups.ps1’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or
if a path was included, verify that the path is correct and try again.
At C:\Users\mike\OneDrive\Powershell Leaver\Final Master.ps1:394 char:1
+ .\Allgroups.ps1 -Identity $LeaverUsername
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (.\Allgroups.ps1:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException:
Any thoughts?
Again, make sure you account for the working directory. If you want to be on the safe side, provide the full path to the file, i.e. C:\temp\script.ps1
Dear Vasil,
Thanks for the script, it would make my admin life so much better 🙂
Unfortunately when I run it without the -Whatif parameter I get these errors for every group the user belongs too.
PSMessageDetails :
Exception : System.Management.Automation.RuntimeException: The value of the using variable ‘$using:WhatIfPreference’ cannot be retrieved because it has not been set in the local session.
at System.Management.Automation.ScriptBlockToPowerShellConverter.GetUsingValues(Ast body, Boolean isTrustedInput, ExecutionContext context, Dictionary`2 variables, Boolean filterNonUsingVariables)
at System.Management.Automation.ScriptBlockToPowerShellConverter.Convert(ScriptBlockAst body, ReadOnlyCollection`1 functionParameters, Boolean isTrustedInput, ExecutionContext context, Dictionary`2 variables, Boolean filterNonUsin
gVariables, Nullable`1 createLocalScope, Object[] args)
at System.Management.Automation.Language.ScriptBlockAst.System.Management.Automation.Language.IParameterMetadataProvider.GetPowerShell(ExecutionContext context, Dictionary`2 variables, Boolean isTrustedInput, Boolean filterNonUsin
gVariables, Nullable`1 createLocalScope, Object[] args)
at System.Management.Automation.ScriptBlock.GetPowerShell(Boolean isTrustedInput, Object[] args)
at Microsoft.PowerShell.Commands.PSExecutionCmdlet.ConvertToPowerShell()
at Microsoft.PowerShell.Commands.PSExecutionCmdlet.GetPowerShellForPSv2()
at Microsoft.PowerShell.Commands.PSExecutionCmdlet.CreatePipeline(RemoteRunspace remoteRunspace)
at Microsoft.PowerShell.Commands.PSExecutionCmdlet.CreateHelpersForSpecifiedRunspaces()
at Microsoft.PowerShell.Commands.PSExecutionCmdlet.BeginProcessing()
at Microsoft.PowerShell.Commands.InvokeCommandCommand.BeginProcessing()
at System.Management.Automation.Cmdlet.DoBeginProcessing()
at System.Management.Automation.CommandProcessorBase.DoBegin()
TargetObject :
CategoryInfo : InvalidOperation: (:) [Invoke-Command], RuntimeException
FullyQualifiedErrorId : UsingVariableIsUndefined,Microsoft.PowerShell.Commands.InvokeCommandCommand
ErrorDetails :
InvocationInfo : System.Management.Automation.InvocationInfo
ScriptStackTrace : at Remove-UserFromAllGroups, C:\scripts\m365RemoveUserFromAllGroups.ps1: line 128
at , C:\scripts\m365RemoveUserFromAllGroups.ps1: line 181
at , : line 1
PipelineIterationInfo : {}
Any idea what I am overlooking or doing wrong?
Thanks in advance.
Johan Van Cauwenberghe
Which version of PowerShell is that? And the Exchange module? I still haven’t updated the script to run in the V3 version of the module, you have to use an RPS session:
Also need to update the AAD part to use the Graph cmdlets… I’ll get to it eventually.
I get this error since beginning of July 2022
Check-Connectivity : No active Exchange Remote PowerShell session detected, please connect first. To connect to ExO:
https://technet.microsoft.com/en-us/library/jj984289(v=exchg.160).aspx
At X:\Remove_User_All_Groups.ps1:96 char:13
+ if (Check-Connectivity -IncludeAADSecurityGroups:$IncludeAADS …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Check-Connectivity
Are you using the latest (preview) version of the ExO module? If so, by default no remote session is established, so the connectivity check fails. Read more about it here: https://www.michev.info/Blog/Post/3883/exchange-online-powershell-module-gets-rid-of-the-winrm-dependence
I’ll update the script once a GA version is released, until then simply remove the check.
Hi,
Is it still compatible with Version 2.0.6-Preview6 of the EXO V2 module? Looks like the script does not detect session connected to EXO. I might be wrong of course. Can you confirm that it works well with 2.0.6 EXO V2 module? Thanks in advance and also huge thanks for the PowerShell script itself.
There is no remote session at all when you are connecting via the latest preview, I explained how it all works here: https://www.michev.info/Blog/Post/3883/exchange-online-powershell-module-gets-rid-of-the-winrm-dependence
I’ll update the script once a GA version is released, until then simply remove the check.
I know this is an old thread, but reaching out in hopes you may get this message.
I managed to get everything running and importing users from a csv.
however when running for every group it tries to remove a user from Verbose gives me the following message:
Unable to remove members (member name here) from group object
PSMessageDetails :
Exception : System.Management.Automation.RemoteException: We failed to update the group mailbox. Please
try again later.
TargetObject :
CategoryInfo : NotSpecified: (:) [Remove-UnifiedGroupLinks], AADException
FullyQualifiedErrorId : [Server=SA1PR14MB4723,RequestId=2180ebc0-dece-4967-ba4e-54f061b2284b,TimeStamp=6/9/2022
5:57:04 PM] [FailureCategory=Cmdlet-AADException]
B4CDADC1,Microsoft.Exchange.Management.RecipientTasks.RemoveUnifiedGroupLinks
ErrorDetails :
InvocationInfo :
ScriptStackTrace : at , : line 35
PipelineIterationInfo : {}
Any thoughts?
Seems like a transient error, you get those every now and then with Groups, especially since they introduced the dual-write model. Try again, or remove the member manually.
Looks like it was being throttled by Microsoft.
Thanks for this script brotha, works like a dream.
i want to remove a user from all distribution/M365 groups in an AD/Azure environment. i dont know how to go about it and most of the codes i see are confusing. please help me out
Hi Vasil,
This script looks AMAZING, thank you so much for putting it together and sharing it with the world!
I realize this is probably a newb question, but when we run the script we’re getting an error authenticating.
“Check-Connectivity : No active Exchange Remote PowerShell session detected, please connect first. To connect to ExO: https://technet.microsoft.com/en-us/library/jj984289(v=exchg.160).aspx
At C:\Remove_User_All_Groups.ps1:91 char:13″
Would you have any direction we can look to at correcting this error? Thanks again for your help, and have a great weekend!
It’s best to connect to ExO PowerShell first before running the script, follow the steps here: https://docs.microsoft.com/en-us/powershell/exchange/connect-to-exchange-online-powershell?view=exchange-ps
Hi
Seriously impressed by script. It does however throw out an error for groups that are synced from on-premises AD. But that no issue. I did see someone had a solution for it but I just let it run as is with errors. I actually call this from another script that makes sure everything is set/changed for on-prem AD accounts that are disabled, so it also removes them from on-prem AD groups. So after the next sync all is well and accounts are out of all groups.
Hi Hugo,
Do you have a link to the script you mention that works with on-prem AD?
Thanks,
Hey, thanks for this script. Well done. How do I use this script to remove a list of users from a specific list of Teams?
Thanks
You’ll have to add a check for the Team name/Id.
I get the error below:
Only members who are not owners can be removed from group.
Please remove ‘PersonsName’ as owners before removing them as members.
Is there a way to force this to work? If i go to that user and remove it, it works no problem, just want to save the manual stuff.
PowerShell wont allow you to remove an user that is owner, without “demoting” it first, and demoting in turn can only be done if there is at least one other owner, so I decided to leave it as is. Feel free to adjust the code as you see fit though 🙂
or you can add bypass manager check.
Hey Vasil,
I am learning admin for office365. Please could you advise how can I remove a user from all shared mailbox without going into each shared mailbox?
Thank you for your help.
I have a script that calls this script with all the switches and verbose. What would be the best way to send all the verbose output to a TXT file as a log?
Thanks again.
You can simply redirect the Verbose stream to a file, see for example here: https://petri.com/understanding-how-streams-work-in-powershell-7
Where will you be hosting your code once Microsoft shuts down the Technet Gallery?
All of them are over at GitHub, but there’s always the occasional link I’ve failed to update… 😀
Thank you! Great script and exactly what I/we needed. Using it with a CSV list to remove users from groups in Office 365 upon termination.
Since AD DirSynced groups error out for us, I added a filter to exclude them in the Group accumulation command.
Get-Recipient -Filter “(Members -eq ‘$($using:user.Value.DistinguishedName)’) -and (IsDirSynced -eq ‘$false’)”
Don’t know if that is the best way to do it, but it works. 🙂
Kinda new at all this.
Thanks again.
Hi,
I am absolutely new at this but here goes. I have run the script with the following :
.\Remove_User_All_Groups.ps1 -Identity (Import-Csv .\Cookery.csv | select -ExpandProperty User) -Whatif -Verbose -IncludeOffice365Groups.
It completes successfully with the following outputs for each of the users:
VERBOSE: Processing user “MCS00001F8@sccm.edu.au”…
VERBOSE: Obtaining group list for user “MCS00001F8@sccm.edu.au”…
VERBOSE: User “MCS00001F8@sccm.edu.au” is a member of 2 group(s).
VERBOSE: Removing user “MCS00001F8@sccm.edu.au” from group “SIT40516 Certificate IV in Commercial Cookery”
What if: Removing links from unified group “79f6493f-6d5a-42e5-9fea-3a6e26e5211f” will remove the links permanently.
VERBOSE: Removing user “MCS00001F8@sccm.edu.au” from group “SCCM Current Students”
What if: Removing links from unified group “2a391430-eb2d-4bf2-b2c5-588cdf9b3515” will remove the links permanently.
VERBOSE: Processing user “MCS00001FA@sccm.edu.au”…
…. and so on…
however, when I verify the results in the Admin Centre, the users are still in their relative groups. Is there something I am missing?
Niraaz
When you run this with the -WhatIf switch, no changes are actually made. It simply shows you what will be changed once you rerun the cmdlet without the switch.
Thank you. The learning journey continues. 🙂
When I run it with the -WhatIf switch removed, I get…
System.Management.Automation.RuntimeException: The value of the using variable ‘$using:WhatIfPreference’ cannot be retrieved because it has not been set in the local session.
Any ideas?
Dear Vasil,
I am in urgent requirement to remove all the owners and members from a list of 600+ teams and add a new user id as the new owner.
( School academic year ended and needs to remove the staffs and students urgently)
Can you please help me witth a powershell script that calls the teams details from a csv file and do the above action? I can download the group id’s, display name, email id of the teams from Azure active directory as csv.
Regards,
Muhamed
Hi Vasil,
I’m using another script that is already connected to exchange using PowerShell and the script converts the $user mailbox to a shared mailbox and I want this script to be the second part of my script.
I use param($user) to feed in the username within my script. How can I include this script into the same session so that it doesn’t check to reconnect to exchange and also picks the identity $user
This would be such a time saver and I really appreciate your help.
Simply call the script with the -Identity parameter? Or if you prefer you can dot-source it and expose the cmdlet directly.
Hi Vasil
Goal is to remove users from Single office 365 group and this script will let you remove from all Office 365 group and its very powerful. How will i run against single Office 365 group.
Well if it’s a single group, why do you even need a script 🙂 Simply use the corresponding cmdlet, either Remove-DistributionGroupMember or Remove-UnifiedGroupLink.
How does the script works with dynamic groups in AAD ? Will it be error or will it just try to remove the users?
It doesn´t looks like it´s an attribute for dynamic groups on the group itself in AAD.
It doesn’t, Dynamic DG’s membership is governed by recipient filters, to remove the user you have to edit the filter. The script doesn’t support DDGs.
Do you know what happens if the user is the only member and owner of a Microsoft Team?
It will throw an error, that’s a limitation of the cmdlet. You can certainly update the script to auto-add/promote another user as owner for such groups/teams.
This script does not work under PS 5.1, or am I missing something?
“Remove-UserFromAllGroups : The term ‘Remove-UserFromAllGroups’ is not recognized as the name of a cmdlet, function, script file, or
operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.”
Please advise.
You are calling the cmdlet, not the script. If you want to use the cmdlet, you need to dot-source the script beforehand.
Vasil, So the script is is called everytime a user in the csv is entered so if i had 50 users the script will run 50 times?
IS there a way for the script to just call the csv once and run thru a loop and exceute the remove cmdlt
Doesn’t make much of a difference really, whether it’s a single script execution or 50x, it will still do the loop over each user 50 times to handle this case. If you want to do it on a “single” run, use this:
.\Remove_User_All_Groups.ps1 -Identity (Import-Csv .\test.csv | select -ExpandProperty User) -WhatIf -Verbose
where you have a property User in the test.csv file.
Where on the script can you place this line so I don’t have to input each user:
Import-CSV blabla.csv | % { .\Remove_User_All_Groups.ps1 -Identity $_.User }
It’s not *in* the script, you’re basically going to call the script numerous times, once for each user in the CSV file.
Question, I am a newbie when it comes to PS. I was wondering how do I pass on a CSV file to the script. It only asks for identity when I ran the script.
Something like this:
Import-CSV blabla.csv | % { .\Remove_User_All_Groups.ps1 -Identity $_.User }
where blabla.csv has a column named User, containing the user identifier.
Hey Vasil,
Great job on the script. Since this handles O365 Groups, I was wondering if removing users like this as part of a deprovisioning process will auto-remove them from any MS Teams of which they were members?
Also, have you had any success dealing with ‘orphaned’ members in Teams that show up as ‘Unknown User’? Can this script or a variant be used to perform housekeeping on existing orphaned Team members?
Thanks,
J
It should, although there might be slight delay due to the “loose coupling” model utilized by O365 Groups. But it’s best to use the Team cmdlets/APIs when dealing with Team members.
The UU members are a known issue, and it’s beyond me what exactly is taking Microsoft so long to fix it. You’d figure concept as basic as membership should be polished out from the start, but alas…