Skip to content

Commit 4bd8363

Browse files
authored
Merge pull request #943 from fabianhutzli/main
New Sample - Cleanup a SPFx Solution from every Site and App Catalog
2 parents 6d5e2c7 + cb37a7f commit 4bd8363

6 files changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Cleanup a SPFx Solution from every Site and App Catalog
2+
3+
## Summary
4+
5+
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.
6+
7+
# [PnP PowerShell](#tab/pnpps)
8+
9+
```powershell
10+
# Set the SharePoint Tenant name
11+
$tenantName = "contoso"
12+
# Set App catalog Url
13+
$appCatalogUrl = "appcatalog"
14+
# Set App name (SPFx solution)
15+
$appName = "my-spfx-solution"
16+
17+
# Connect to the SharePoint admin center to retrieve tenant-wide app and site data
18+
Connect-PnPOnline -Url "https://$($tenantName)-admin.sharepoint.com" -Interactive
19+
20+
# Resolve the app ID from the tenant app catalog by matching the title
21+
$app = Get-PnPApp | Where-Object { $_.Title -eq $appName }
22+
23+
# Get all site collections in the tenant
24+
$sites = Get-PnPTenantSite
25+
26+
foreach ($site in $sites) {
27+
try {
28+
Connect-PnPOnline -Url $site.Url -Interactive
29+
30+
# Check if this site has the app listed (not necessarily installed)
31+
$installedApp = Get-PnPApp | Where-Object { $_.Id -eq $app.Id }
32+
33+
# InstalledVersion is only set when the app is actively deployed on the site
34+
if ($installedApp -and $installedApp.InstalledVersion) {
35+
Write-Host "Removing app from $($site.Url)"
36+
37+
Uninstall-PnPApp -Identity $installedApp.Id
38+
}
39+
40+
# After uninstall, the app package moves to the recycle bin — clear it to fully remove it
41+
$recycleItems = Get-PnPRecycleBinItem | Where-Object {
42+
$_.Title -like $appName
43+
}
44+
45+
foreach ($item in $recycleItems) {
46+
Clear-PnPRecycleBinItem -Identity $item.Id -Force
47+
}
48+
49+
} catch {
50+
Write-Host "Error on $($site.Url): $_"
51+
}
52+
}
53+
54+
# Connect to the tenant app catalog site to remove the app package itself
55+
Connect-PnPOnline -Url "https://$($tenantName).sharepoint.com/sites/$($appCatalogUrl)" -Interactive
56+
57+
# Remove the app package from the app catalog
58+
Remove-PnPApp -Identity $appName -Force
59+
60+
# Clear the app catalog recycle bin to ensure the package is fully gone
61+
$recycleItems = Get-PnPRecycleBinItem | Where-Object {
62+
$_.Title -like $appName
63+
}
64+
65+
foreach ($item in $recycleItems) {
66+
Clear-PnPRecycleBinItem -Identity $item.Id -Force
67+
}
68+
```
69+
[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)]
70+
71+
***
72+
73+
## Contributors
74+
75+
| Author(s) |
76+
|-----------|
77+
| [Fabian Hutzli](https://github.com/fabianhutzli)|
78+
79+
80+
[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]
81+
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-cleanup-spfx-solution" aria-hidden="true" />
58.7 KB
Loading
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[
2+
{
3+
"name": "spo-cleanup-spfx-solution",
4+
"source": "pnp",
5+
"title": "Cleanup a SPFx Solution from every Site and App Catalog",
6+
"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.",
7+
"url": "https://pnp.github.io/script-samples/spo-cleanup-spfx-solution/README.html",
8+
"longDescription": [
9+
""
10+
],
11+
"creationDateTime": "2026-04-10",
12+
"updateDateTime": "2026-04-10",
13+
"products": [
14+
"SharePoint"
15+
],
16+
"metadata": [
17+
{
18+
"key": "PNP-POWERSHELL",
19+
"value": "3.1.0"
20+
}
21+
],
22+
"categories": [
23+
"Deploy"
24+
],
25+
"tags": [
26+
"Get-PnPTenantSite",
27+
"Get-PnPApp",
28+
"Uninstall-PnPApp",
29+
"Remove-PnPApp",
30+
"Get-PnPRecycleBinItem",
31+
"Clear-PnPRecycleBinItem"
32+
],
33+
"thumbnails": [
34+
{
35+
"type": "image",
36+
"order": 100,
37+
"url": "https://raw.githubusercontent.com/pnp/script-samples/main/scripts/spo-cleanup-spfx-solution/assets/example.png",
38+
"alt": "Preview of the sample Cleanup a SPFx Solution from every Site and App Catalog"
39+
}
40+
],
41+
"authors": [
42+
{
43+
"gitHubAccount": "fabianhutzli",
44+
"pictureUrl": "https://github.com/fabianhutzli.png",
45+
"name": "Fabian Hutzli"
46+
}
47+
],
48+
"references": [
49+
null
50+
]
51+
}
52+
]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Export and Import TermGroups
2+
3+
## Summary
4+
5+
This script exports Term Groups from a source SharePoint Online tenant and imports them into a target tenant using PnP PowerShell. It connects interactively to both tenants, retrieves all (or a specified subset of) Term Groups, exports each one to a local XML file, and then imports those XML files into the target tenant. Before importing, the script performs an in-place string replacement in the XML to update tenant-specific domain references from the source tenant to the target tenant.
6+
7+
# [PnP PowerShell](#tab/pnpps)
8+
9+
```powershell
10+
# XML Export / Import Path
11+
$xmlPath = "./Terms/xml"
12+
13+
# Source
14+
$cnSource = Connect-PnPOnline -Url "https://sourcetenant-admin.sharepoint.com" -Interactive -ReturnConnection
15+
16+
# Target
17+
$cnTarget = Connect-PnPOnline -Url "https://targettenant-admin.sharepoint.com" -Interactive -ReturnConnection
18+
19+
# 1) Get all TermGroups
20+
$TermGroups = (Get-PnPTermGroup -Connection $cnSource).Name
21+
22+
<# 2) Get TermGroups
23+
$TermGroups = @(
24+
"TermGroup1"
25+
"TermGroup2"
26+
)
27+
#>
28+
29+
# Export Terms to local disk
30+
$TermGroups | ForEach-Object {
31+
$TermGroup = $_
32+
Write-Host "Export Termgroup $($TermGroup)"
33+
Export-PnPTermGroupToXml -Identity $TermGroup -Out "$($xmlPath)/$($TermGroup).xml" -Force -Connection $cnSource
34+
}
35+
36+
# Import Terms
37+
$confirm = Read-Host "Importing Terms to $($cnTarget.Url)? [y/n]"
38+
if ($confirm -eq "y") {
39+
$TermGroups | ForEach-Object {
40+
$TermGroup = $_
41+
Write-Host "Importing Termgroup $($TermGroup) to Tenant $($cnTarget.Url)..."
42+
(Get-Content -Path "$($xmlPath)/$($TermGroup).xml").Replace("@sourcetenant.ch","@targettenant.ch") | Set-Content -Path "$($xmlPath)/$($TermGroup).xml"
43+
Import-PnPTermGroupFromXml -Path "$($xmlPath)/$($TermGroup).xml" -Connection $cnTarget
44+
}
45+
}
46+
else {
47+
Write-Host "No action performed" -ForegroundColor DarkGray
48+
}
49+
```
50+
[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)]
51+
52+
***
53+
54+
## Contributors
55+
56+
| Author(s) |
57+
|-----------|
58+
| [Fabian Hutzli](https://github.com/fabianhutzli)|
59+
60+
61+
[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]
62+
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-cleanup-spfx-solution" aria-hidden="true" />
63+
58.7 KB
Loading
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
[
2+
{
3+
"name": "spo-export-import-termgroups",
4+
"source": "pnp",
5+
"title": "Export and Import TermGroups",
6+
"shortDescription": "Export and Import TermGroups between Tenants or save the export as backup.",
7+
"url": "https://pnp.github.io/script-samples/spo-export-import-termgroups/README.html",
8+
"longDescription": [
9+
""
10+
],
11+
"creationDateTime": "2026-04-13",
12+
"updateDateTime": "2026-04-13",
13+
"products": [
14+
"SharePoint"
15+
],
16+
"metadata": [
17+
{
18+
"key": "PNP-POWERSHELL",
19+
"value": "3.1.0"
20+
}
21+
],
22+
"categories": [
23+
"Data",
24+
"Deploy",
25+
"Configure"
26+
],
27+
"tags": [
28+
"Get-PnPTermGroup",
29+
"Export-PnPTermGroupToXml",
30+
"Import-PnPTermGroupFromXml"
31+
],
32+
"thumbnails": [
33+
{
34+
"type": "image",
35+
"order": 100,
36+
"url": "https://raw.githubusercontent.com/pnp/script-samples/main/scripts/spo-export-import-termgroups/assets/preview.png",
37+
"alt": "Preview of the sample Export and Import TermGroups"
38+
}
39+
],
40+
"authors": [
41+
{
42+
"gitHubAccount": "fabianhutzli",
43+
"pictureUrl": "https://github.com/fabianhutzli.png",
44+
"name": "Fabian Hutzli"
45+
}
46+
],
47+
"references": [
48+
null
49+
]
50+
}
51+
]

0 commit comments

Comments
 (0)