Does it spark joy? PowerShell scripts for keeping your development environment tidy and spotless

Does it spark joy? PowerShell scripts for keeping your development environment tidy and spotless

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.

Download all the scripts (.zip)

Table of contents

  1. Preparations and the general structure
  2. Delete all custom color themes from SharePoint
  3. Delete all SharePoint site templates and site scripts
  4. Delete all Office 365 groups
  5. Delete all (non-group connected) modern SharePoint sites
  6. Empty the tenant recycle bin
  7. 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:

  1. 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 running Update-Module SharePointPnPPowerShellOnline
  2. 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:

  1. 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.
  2. 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.
  3. We get all other items except the ones defined in the $sparksjoy variable.
  4. If no items were returned, the script ends.
  5. 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.
  6. After confirming that all the items can be deleted, hit Enter and the removal will begin. You’ll be able to monitor the progress.
  7. 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:

  1. Get the Microsoft Exchange Online Powershell Module by following the steps carefully.
  2. 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

  3. Remove the very last line of code (Remove-PSSession $session)
  4. 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



1 thought on “Does it spark joy? PowerShell scripts for keeping your development environment tidy and spotless”

  • 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.

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.