Does it spark joy? PowerShell scripts for keeping your development environment tidy and spotless
Last updated on September 3, 2023
If you implement a lot of different kinds of workspace provisioning processes for Office 365 like me, you know that your development tenant tends to look like a massive mess for most of the time. Does this look familiar?
Yup.
Last month I was speaking at two large conferences: the SharePoint Conference 2019 in Las Vegas (USA) and European Collaboration Summit in Wiesbaden (Germany). I had quite a few demos prepared for both of my sessions and showed them using my developer tenant.
Before holding my presentations, I wanted to clean up all the junk I had accumulated over time. Doing all the tidying up by hand would have been a lot of work, so I figured I’d whip up a couple of useful PowerShell scripts for doing that. And why not share the scripts with you, huh? 🙂 So here goes!
To make your lives easier, I’ve packaged all the PowerShell scripts into a zip file! Click the link below to download.
Table of contents
- Preparations and the general structure
- Delete all custom color themes from SharePoint
- Delete all SharePoint site templates and site scripts
- Delete all Office 365 groups
- Delete all (non-group connected) modern SharePoint sites
- Empty the tenant recycle bin
- Afterword & download
Preparations and the general structure
Before you can run these PowerShell scripts, you need to install (or possibly update) the PnP PowerShell module if you have not yet done so. You can do that by following these steps:
- Start Windows PowerShell as an administrator and run this command:
Install-Module SharePointPnPPowerShellOnline -AllowClobber
. If you’ve already previously installed the module and just need to update it, you can do that by runningUpdate-Module SharePointPnPPowerShellOnline
- If you want to run the scripts by executing .ps1 files, you also need to run this command:
Set-ExecutionPolicy Unrestricted
All of the scripts in this blog post follow the same pattern:
- Configurations:
- If available, set your admin site URL to the
$adminUrl
variable. It will be used for connecting. - If there are some workspaces that you’d like to keep, you can add them into the
$sparksjoy
variable and they won’t get deleted.
- If available, set your admin site URL to the
- When the script starts, we first connect and authenticate to your tenant. You’ll get prompted for a username and password. We use web login to support MFA.
- We get all other items except the ones defined in the
$sparksjoy
variable. - If no items were returned, the script ends.
- If items were found, we print a list of them in the shell, and allow you to make sure all of them can be deleted. You can terminate the script at this point if you notice the list contains something you want to keep.
- After confirming that all the items can be deleted, hit Enter and the removal will begin. You’ll be able to monitor the progress.
- The script execution will end after the last item has been deleted.
Delete all custom color themes from SharePoint
Have you been creating a lot of beautiful themes lately and testing them in your dev tenant, but don’t want to keep them anymore? If yes, then this PowerShell script is for you.
$adminUrl= "https://mytenant-admin.sharepoint.com" $sparksjoy = "Cat Lovers United", "Multicolored theme" Connect-PnPOnline -Url $adminUrl -UseWebLogin $themes = Get-PnPTenantTheme | where {-not ($sparksjoy -contains $_.Name)} $themes | Format-Table Name if ($themes.Count -eq 0) { break } Read-Host -Prompt "Press Enter to start deleting (CTRL + C to exit)" $progress = 0 $total = $themes.Count foreach ($theme in $themes) { $progress++ write-host $progress / $total":" $theme.Name Remove-PnPTenantTheme -name $theme.Name }
Delete all SharePoint site templates and site scripts
Site templates and especially site scripts can be something that ends up just hanging around in your tenant for a long time, even though you no longer need them for anything. Use the scripts below to get rid of them. You might also find some site scripts that are not linked to any site template and hence never get executed!
Site templates
$adminUrl = "https://mytenant-admin.sharepoint.com" $sparksjoy = "Project Site", "Issues List" Connect-PnPOnline $adminUrl -UseWebLogin $siteDesigns = Get-PnPSiteDesign | where { -not ($sparksjoy -contains $_.Title) } if ($siteDesigns.Count -eq 0) { break } $siteDesigns | Format-Table Title, SiteScriptIds, Description Read-Host -Prompt "Press Enter to start deleting (CTRL + C to exit)" $progress = 0 $total = $siteDesigns.Count foreach ($siteDesign in $siteDesigns) { $progress++ write-host $progress / $total":" $siteDesign.Title Remove-PnPSiteDesign -Identity $siteDesign.Id -Force }
Site scripts
$adminUrl = "https://mytenant-admin.sharepoint.com" $sparksjoy = "Project Site", "Issues List" Connect-PnPOnline $adminUrl -UseWebLogin $siteScripts = Get-PnPSiteScript | where { -not ($sparksjoy -contains $_.Title) } if ($siteScripts.Count -eq 0) { break } $siteScripts | Format-Table Title, Id Read-Host -Prompt "Press Enter to start deleting (CTRL + C to exit)" $progress = 0 $total = $siteScripts.Count foreach ($siteScript in $siteScripts) { $progress++ write-host $progress / $total":" $siteScript.Title Remove-PnPSiteScript -Identity $siteScript.Id -Force }
Delete all Office 365 groups
There are so many different ways to create Office 365 groups. Teams, Planner, SharePoint team sites, etc. — you can accumulate a lot of them very fast. Use the PowerShell script below to delete the ones you no longer need.
PnP PowerShell
If you want to see the site URLs before you start deleting, omit the -ExcludeSiteUrl
flag and uncomment SharePointSiteUrl
to show the column.
$sparksjoy = "All Company", "TEMPLATE Project", "We have cats in this team! Join!" Connect-PnPOnline -Graph -LaunchBrowser $groups = Get-PnPUnifiedGroup -ExcludeSiteUrl | where {-not ($sparksjoy -contains $_.DisplayName)} if ($groups.Count -eq 0) { break } $groups | Format-Table DisplayName #, SharePointSiteUrl Write-Host "Total:" $groups.Count Read-Host -Prompt "Press Enter to start deleting (CTRL + C to exit)" $progress = 0 $total = $groups.Count foreach ($group in $groups) { $progress++ write-host $progress / $total":" $group.DisplayName Remove-PnPUnifiedGroup -Identity $group.GroupId }
Exchange Online
Here’s also another version of the same script which connects to Exchange Online instead of PnP Online.
$sparksjoy = "All Company", "TEMPLATE Project", "We have cats in this team! Join!" $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential (Get-Credential) -Authentication Basic -AllowRedirection Import-PSSession $session $groups = Get-UnifiedGroup | where {-not ($sparksjoy -contains $_.DisplayName)} if ($groups.Count -eq 0) { break } $groups | Format-Table DisplayName, SharePointSiteUrl Read-Host -Prompt "Press Enter to start deleting (CTRL + C to exit)" $progress = 0 $total = $groups.Count foreach ($group in $groups) { $progress++ write-host $progress / $total":" $group.DisplayName Remove-UnifiedGroup -identity $group.Id -Confirm:$false } Remove-PSSession $session
The script works as is if your credentials don’t have multi-factor authentication enabled. If you are using MFA, there are some extra steps you need to take:
- Get the Microsoft Exchange Online Powershell Module by following the steps carefully.
- Replace these lines of code
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential (Get-Credential) -Authentication Basic -AllowRedirection Import-PSSession $session
with
Connect-EXOPSSession
- Remove the very last line of code (
Remove-PSSession $session
) - Run the script in the shell that opens when you run the Microsoft Exchange Online Powershell Module executable.
Delete all (non-group connected) modern SharePoint sites
When you delete Office 365 groups, the modern group-connected team sites get deleted with them. The PowerShell script below handles the remaining modern sites: communication sites and groupless team sites. However, if you want, you can also add -or $_.template -eq "GROUP#0"
to the where
clause and also delete group-connected team sites with this same script. Note that it will then also delete the underlying Office 365 groups.
$adminUrl = "https://mytenant-admin.sharepoint.com" $sparksjoy = "Cat Lovers United", "Extranet", "Hub" Connect-PnPOnline -Url $adminUrl -UseWebLogin $sites = Get-PnPTenantSite | where { $_.template -eq "SITEPAGEPUBLISHING#0" -or $_.template -eq "STS#3" -and -not ($sparksjoy -contains $_.Title)} # -or $_.template -eq "GROUP#0" if ($sites.Count -eq 0) { break } $sites | Format-Table Title, Url, Template Read-Host -Prompt "Press Enter to start deleting (CTRL + C to exit)" $progress = 0 $total = $sites.Count foreach ($site in $sites) { $progress++ write-host $progress / $total":" $site.Title Remove-PnPTenantSite -Url $site.Url -Force }
Empty the tenant recycle bin
Permanently delete Office 365 groups from the recycle bin
When you delete Office 365 groups, they will, by default, remain in the group recycle bin for 30 days. If you want to re-use the same mail nicknames of those deleted Office 365 groups, you can speed up the process by permanently removing the groups from the recycle bin with the script below.
Connect-PnPOnline -Graph -LaunchBrowser $groups = Get-PnPDeletedUnifiedGroup if ($groups.Count -eq 0) { break } $groups | Format-Table DisplayName Write-Host "Total:" $groups.Count Read-Host -Prompt "Press Enter to start permanently deleting (CTRL + C to exit)" $progress = 0 $total = $groups.Count foreach ($group in $groups) { $progress++ write-host $progress / $total":" $group.DisplayName Remove-PnPDeletedUnifiedGroup -Identity $group.GroupId }
Permanently delete SharePoint sites from the recycle bin
Your deleted modern SharePoint sites are not going to disappear from the UI before they have been removed from the tenant recycle bin. You can either wait for three months, delete them manually via the SharePoint admin center, or run the PowerShell script below.
$adminUrl = "https://mytenant-admin.sharepoint.com" Connect-PnPOnline -Url $adminUrl -UseWebLogin $deletedSites = Get-PnPTenantRecycleBinItem $deletedSites | Format-Table Url if ($deletedSites.Count -eq 0) { break } Read-Host -Prompt "Press Enter to start deleting (CTRL + C to exit)" $progress = 0 $total = $deletedSites.Count foreach ($deletedSite in $deletedSites) { $progress++ write-host $progress / $total":" $deletedSite.Url Clear-PnPTenantRecycleBinItem -Url $deletedSite.Url -Force }
Afterword & download
Now we’ve uncluttered our DEV environment of all the useless things we didn’t want to keep! The only downside of this nearly empty tenant can be if you also need to have a lot of test data available. Maybe there’s an idea for a future blog post? 😉 Stay tuned!
If you are interested in more Office 365 development related things, feel free to follow me on Twitter. There I’ll give you a heads-up whenever I publish new articles and also share some other tips and tricks as well as links to other great resources that I think are worth studying. If you want to learn alongside me, Twitter is the best place to do that!
I will be out-of-office for most of July. Have a wonderful summer and until next time!
Laura
Hi Laura,
Love this post. I recognize a LOT of this powershell, because the last 3 days, I have been trying to eradicate a given URL from SharePoint Online. Its a URL of an online site that I have created and removed repeatedly during my testing of SharePoint Migration (SPMT) of on-prem to online.
How do I know th URL is still “known” to SharePoint Online? I can Connect-PnpOnline to it (but yet Get-PnpSite fails). Prior to 3 days ago, these are the following things I would do to “sufficiently” remove the URL fro SharePoint Online:
First go into Sharepoint Online Modern Portal, delete the Active site.
Then do all this stuff:
Get-SPODeletedSite
Remove-SPODeletedSite -Identity https://org.sharepoint.com/sites/0000000000 -Confirm:$false
# Maybe make sure UnifiedGroup is removed. Not sure how necessary this is.
$targetGroup = “0000000000@geoengineers.com”
$ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $cred -Authentication Basic -AllowRedirection
Import-PSSession $ExchangeSession -DisableNameChecking -AllowClobber
Remove-UnifiedGroup -Identity $targetGroup -confirm:$False
Remove-PSSession $ExchangeSession
# Removed O365 Groups that don’t go away easily, so now we do this
$projectName = “Portland General Electric”
Connect-AzureAD -Credential $cred
Get-AzureADMSDeletedGroup
Get-AzureADMSDeletedGroup | Where-Object {($_.DisplayName -eq $projectName)} | foreach {Remove-AzureADMSDeletedDirectoryObject -id $_.Id}
Disconnect-AzureAD
# Log into the Tenant and issue a remove from there. Even after the above commands, I was able to Connect-PnpOnline to a site that seemingly was not there.
Connect-PnPOnline https://org-admin.sharepoint.com -Credentials $cred
Remove-PnPTenantSite -Url $Global:SPOProjectURL
So like I mentioned, all these steps above usually have worked to free up an online site url. I’ve triple checked every where, recycle bins etc, and its all clean, except for some reason now I can still Connect-PnpOnline to that site.
If you have any ideas I would sure love to hear them! Thanks you so much. Again, great post.