diff --git a/scripts/spo-cleanup-spfx-solution/README.md b/scripts/spo-cleanup-spfx-solution/README.md new file mode 100644 index 00000000..973668d2 --- /dev/null +++ b/scripts/spo-cleanup-spfx-solution/README.md @@ -0,0 +1,81 @@ +# Cleanup a SPFx Solution from every Site and App Catalog + +## Summary + +This script fully removes a SPFx solution from a SharePoint Online tenant. It iterates over all site collections, uninstalls the app where it is deployed, and clears any leftover entries from the site recycle bins. Finally, it removes the app package from the tenant app catalog and empties the app catalog recycle bin as well — ensuring no traces of the solution remain across the tenant. + +# [PnP PowerShell](#tab/pnpps) + +```powershell +# Set the SharePoint Tenant name +$tenantName = "contoso" +# Set App catalog Url +$appCatalogUrl = "appcatalog" +# Set App name (SPFx solution) +$appName = "my-spfx-solution" + +# Connect to the SharePoint admin center to retrieve tenant-wide app and site data +Connect-PnPOnline -Url "https://$($tenantName)-admin.sharepoint.com" -Interactive + +# Resolve the app ID from the tenant app catalog by matching the title +$app = Get-PnPApp | Where-Object { $_.Title -eq $appName } + +# Get all site collections in the tenant +$sites = Get-PnPTenantSite + +foreach ($site in $sites) { + try { + Connect-PnPOnline -Url $site.Url -Interactive + + # Check if this site has the app listed (not necessarily installed) + $installedApp = Get-PnPApp | Where-Object { $_.Id -eq $app.Id } + + # InstalledVersion is only set when the app is actively deployed on the site + if ($installedApp -and $installedApp.InstalledVersion) { + Write-Host "Removing app from $($site.Url)" + + Uninstall-PnPApp -Identity $installedApp.Id + } + + # After uninstall, the app package moves to the recycle bin — clear it to fully remove it + $recycleItems = Get-PnPRecycleBinItem | Where-Object { + $_.Title -like $appName + } + + foreach ($item in $recycleItems) { + Clear-PnPRecycleBinItem -Identity $item.Id -Force + } + + } catch { + Write-Host "Error on $($site.Url): $_" + } +} + +# Connect to the tenant app catalog site to remove the app package itself +Connect-PnPOnline -Url "https://$($tenantName).sharepoint.com/sites/$($appCatalogUrl)" -Interactive + +# Remove the app package from the app catalog +Remove-PnPApp -Identity $appName -Force + +# Clear the app catalog recycle bin to ensure the package is fully gone +$recycleItems = Get-PnPRecycleBinItem | Where-Object { + $_.Title -like $appName +} + +foreach ($item in $recycleItems) { + Clear-PnPRecycleBinItem -Identity $item.Id -Force +} +``` +[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)] + +*** + +## Contributors + +| Author(s) | +|-----------| +| [Fabian Hutzli](https://github.com/fabianhutzli)| + + +[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)] + diff --git a/scripts/spo-cleanup-spfx-solution/assets/example.png b/scripts/spo-cleanup-spfx-solution/assets/example.png new file mode 100644 index 00000000..72a9255d Binary files /dev/null and b/scripts/spo-cleanup-spfx-solution/assets/example.png differ diff --git a/scripts/spo-cleanup-spfx-solution/assets/sample.json b/scripts/spo-cleanup-spfx-solution/assets/sample.json new file mode 100644 index 00000000..8ff6729f --- /dev/null +++ b/scripts/spo-cleanup-spfx-solution/assets/sample.json @@ -0,0 +1,52 @@ +[ + { + "name": "spo-cleanup-spfx-solution", + "source": "pnp", + "title": "Cleanup a SPFx Solution from every Site and App Catalog", + "shortDescription": "Remove a SPFx Solution from every Site installed, from Recycle bin and from App Catalog. This is needed if you need a complete cleanup or reinstall.", + "url": "https://pnp.github.io/script-samples/spo-cleanup-spfx-solution/README.html", + "longDescription": [ + "" + ], + "creationDateTime": "2026-04-10", + "updateDateTime": "2026-04-10", + "products": [ + "SharePoint" + ], + "metadata": [ + { + "key": "PNP-POWERSHELL", + "value": "3.1.0" + } + ], + "categories": [ + "Deploy" + ], + "tags": [ + "Get-PnPTenantSite", + "Get-PnPApp", + "Uninstall-PnPApp", + "Remove-PnPApp", + "Get-PnPRecycleBinItem", + "Clear-PnPRecycleBinItem" + ], + "thumbnails": [ + { + "type": "image", + "order": 100, + "url": "https://raw.githubusercontent.com/pnp/script-samples/main/scripts/spo-cleanup-spfx-solution/assets/example.png", + "alt": "Preview of the sample Cleanup a SPFx Solution from every Site and App Catalog" + } + ], + "authors": [ + { + "gitHubAccount": "fabianhutzli", + "pictureUrl": "https://github.com/fabianhutzli.png", + "name": "Fabian Hutzli" + } + ], + "references": [ + null + ] + } +]