Script to remove user(s) from all groups in Office 365

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

59 thoughts on “Script to remove user(s) from all groups in Office 365

  1. Hugo Gerryts says:

    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

    Reply
    1. Vasil Michev says:

      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

      Connect-ExchangeOnline

      would do. And you can remove all the other checks.

      Reply
      1. Hugo Gerryts says:

        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

        Reply
        1. Vasil Michev says:

          Ah, you’re correct, that method cannot be used any longer. I’ll try to push an update to the script by EOW.

  2. Mike Dean says:

    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?)

    Reply
    1. Vasil Michev says:

      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.

      Reply
      1. Mike Dean says:

        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?

        Reply
        1. Vasil Michev says:

          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

  3. Johan Van Cauwenberghe says:

    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

    Reply
    1. Vasil Michev says:

      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:

      Connect-ExchangeOnline -UseRPSSession

      Also need to update the AAD part to use the Graph cmdlets… I’ll get to it eventually.

      Reply
  4. Ronald van der Meer says:

    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

    Reply
  5. Marcin Winiarczyk says:

    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.

    Reply
  6. Parker Garrett says:

    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?

    Reply
    1. Vasil Michev says:

      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.

      Reply
      1. Parker Garrett says:

        Looks like it was being throttled by Microsoft.
        Thanks for this script brotha, works like a dream.

        Reply
  7. ned says:

    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

    Reply
  8. Ian T Chechet says:

    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!

    Reply
  9. Hugo says:

    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.

    Reply
    1. James says:

      Hi Hugo,

      Do you have a link to the script you mention that works with on-prem AD?

      Thanks,

      Reply
  10. Akin Akinsete says:

    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

    Reply
    1. Vasil Michev says:

      You’ll have to add a check for the Team name/Id.

      Reply
  11. Brian Witsken says:

    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.

    Reply
    1. Vasil Michev says:

      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 🙂

      Reply
  12. Prakash says:

    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.

    Reply
  13. Doug Traylor says:

    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.

    Reply
  14. Doug Traylor says:

    Where will you be hosting your code once Microsoft shuts down the Technet Gallery?

    Reply
    1. Vasil Michev says:

      All of them are over at GitHub, but there’s always the occasional link I’ve failed to update… 😀

      Reply
  15. Doug Traylor says:

    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.

    Reply
  16. Niraaz says:

    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

    Reply
    1. Vasil Michev says:

      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.

      Reply
        1. Mike Latonick says:

          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?

  17. Muhamed Rafeeq says:

    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

    Reply
  18. Sam says:

    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.

    Reply
    1. Vasil Michev says:

      Simply call the script with the -Identity parameter? Or if you prefer you can dot-source it and expose the cmdlet directly.

      Reply
  19. manoj kumar says:

    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.

    Reply
    1. Vasil Michev says:

      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.

      Reply
  20. Magnus says:

    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.

    Reply
    1. Vasil Michev says:

      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.

      Reply
    1. Vasil Michev says:

      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.

      Reply
  21. nvader2000 says:

    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.

    Reply
    1. Vasil Michev says:

      You are calling the cmdlet, not the script. If you want to use the cmdlet, you need to dot-source the script beforehand.

      Reply
  22. Gino says:

    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

    Reply
    1. Vasil Michev says:

      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.

      Reply
  23. gino says:

    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 }

    Reply
    1. Vasil Michev says:

      It’s not *in* the script, you’re basically going to call the script numerous times, once for each user in the CSV file.

      Reply
  24. eys says:

    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.

    Reply
    1. Vasil Michev says:

      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.

      Reply
  25. John says:

    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

    Reply
    1. Vasil Michev says:

      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…

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.