SPClean
2.2.0
Minimum PowerShell version
7.0
Installation Options
Owners
Copyright
(c) 2026 David Pham. Licensed under the MIT License.
Package Details
Author(s)
- David Pham
Tags
SharePoint SPO SharePointOnline Orphaned Cleanup M365 MicrosoftGraph PnP Governance Remediation EntraID Storage Optimization
Functions
Connect-SPCTenant Disconnect-SPCTenant Get-SPCOrphanedUser Get-SPCMismatchUser Export-SPCReport Remove-SPCOrphanedUser Restore-SPCOrphanedUser Repair-SPCMismatchUser New-SPCScanSchedule Register-SPCLicense Get-SPCLicenseInfo Get-SPCGuestAccess Get-SPCPrivilegedUser Get-SPCOverPermissionedUser Get-SPCPermissionHealthScore Get-SPCBrokenInheritance Compare-SPCPermissionSnapshot Invoke-SPCDashboardReport Invoke-SPCPermissionAnalytics Get-SPCStorageWaste Get-SPCInactiveSite Get-SPCVersionWaste Get-SPCPreservationHoldWaste Clear-SPCRecycleBin Optimize-SPCFileVersion Export-SPCStorageReport
Dependencies
-
- Microsoft.Graph.Authentication (>= 2.0.0)
- PnP.PowerShell (>= 2.0.0)
Release Notes
## 2.1.1 - 2026-08-16
- Fix: Resolved JavaScript SyntaxError in `Export-SPCStorageReport` / `New-SPCStorageDashboardHtmlInternal` where ES6 template literals inside PowerShell here-strings caused empty Top Storage Waste Sites tables.
## 2.1.0 - 2026-08-16
- Fixed: Resolved site URLs via PnP SiteId when M365 privacy concealment is enabled in Microsoft 365 Admin Center reports.
- Fixed: Enhanced interactive token resolution to acquire dedicated SharePoint resource tokens for individual site connections and filter out redirect sites.
- Fixed: Properly hydrate CSOM Site.Usage properties via Get-PnPProperty to retrieve exact storage metrics in PnP PowerShell 3.2+.
- Fixed: Resilient scanning when auditing storage waste on preservation hold sites.
- Security & Compliance: Non-destructive Microsoft Purview Preservation Hold Library (PHL) immunity protection.
- Architecture: Centralized PnP wrapper layer in `Private/PnPWrappers.ps1` with PnP 3.2.0 compatibility and zero unmanaged memory residue (`ZeroFreeBSTR`).
- Performance: Exponential backoff with jitter retry on Microsoft Graph 429 throttling and automatic fallback to SharePoint Online Admin API.
## 1.6.0 - 2026-08-14
- Security: Connect-SPCTenant pipeline output sanitized to remove raw GraphAccessToken; provides IsGraphConnected and TokenExpiresAt.
- Security: Get-SPCOrphanedUser escapes single quotes and URI encodes UPNs in Graph OData queries.
- Refactor: Standardized cmdlet names Invoke-SPCDashboardReport and Invoke-SPCPermissionAnalytics with backward-compatible aliases.
- Refactor: Centralized per-site connection logic into Private/Connect-SPCSiteInternal.ps1.
- Refactor: Renamed scoring engine to Measure-SPCScoreInternal adhering to PowerShell approved verbs.
- Refactor: Separated skippedCount from errorCount in Remove-SPCOrphanedUser.
- Architecture: Snapshot schema bumped to v1.1 with isEmptyPermissionSet flag; Restore-SPCOrphanedUser supports both v1.0 and v1.1.
## 1.5.2 - 2026-08-01
- Feature: Permission Health Score calculation and broken inheritance analytics.
## 1.3.0 - 2026-07-17
- Feature: Added `Get-SPCMismatchUser` to detect User ID Mismatches between SharePoint UIL and Entra ID.
- Feature: Added `Repair-SPCMismatchUser` to automatically backup, clean, and restore Web and List level permissions for mismatched users.
## 1.2.3 - 2026-07-15
- Fix: Guest users are now correctly skipped during Mismatch Repair.
## 1.1.6 - 2026-06-27
- Fix: New-SPCScanSchedule scheduled task no longer opens a visible PowerShell window (-WindowStyle Hidden added)
## 1.1.5 - 2026-06-27
- Fix: New-SPCScanSchedule incorrectly detected Windows as non-Windows (Get-Variable $IsWindows unreliable inside module scope); replaced with [System.Environment]::OSVersion.Platform check
## 1.1.4 - 2026-06-27
- Fix: New-SPCScanSchedule -OutputPath alias added (parameter was named -ReportOutputPath, causing ParameterNotFound error)
- Docs: Restore-SPCOrphanedUser limitations - clarify soft-deleted accounts must be restored in Entra first
## 1.1.3 - 2026-06-27
- Fix: Export-SPCReport HTML footer shows correct version instead of System.Object[]
## 1.1.2 - 2026-06-27
- Fix: CI publish workflow now injects HMAC secret before packaging (license key validation works in published module)
- Fix: Interactive auth docs - add http://localhost redirect URI requirement (AADSTS50011)
## 1.1.1 - 2026-06-27
- Fix: exclude .git folder from PSGallery package
## 1.1.0 - 2026-06-26
- Register-SPCLicense: offline HMAC-SHA256 license key activation
- Get-SPCLicenseInfo: query current tier (FREE / PRO / CONSULTANT)
- Feature gates: HTML report, CreateSnapshot, Restore, Schedule require Pro/Consultant
- MkDocs Material documentation site (https://hungpham2802.github.io/SPClean)
## 1.0.0 - 2026-06-22
- Connect-SPCTenant: Interactive and AppOnly (certificate/secret) auth
- Get-SPCOrphanedUser: detects Deleted, SoftDeleted, Disabled, GuestOrphaned accounts
- Export-SPCReport: CSV, HTML (colour-coded risk badges), JSON output
- Remove-SPCOrphanedUser: removes users with WhatIf/Confirm/CreateSnapshot support
- Restore-SPCOrphanedUser: re-applies permissions from JSON snapshot
- New-SPCScanSchedule: Windows Scheduled Task automation
FileList
- SPClean.nuspec
- .gitignore
- docs\cmdlets\Get-SPCInactiveSite.md
- site\licensing\index.html
- site\cmdlets\export-spcreport\index.html
- CHANGELOG.md
- docs\cmdlets\get-spclicenseinfo.md
- site\search\search_index.json
- site\cmdlets\get-spclicenseinfo\index.html
- LICENSE
- docs\cmdlets\get-spcmismatchuser.md
- Tests\Integration\SPClean.Integration.Tests.ps1
- site\cmdlets\get-spcorphaneduser\index.html
- mkdocs.yml
- docs\cmdlets\get-spcorphaneduser.md
- Tests\Integration\SPClean.Phase1.Integration.Tests.ps1
- site\cmdlets\new-spcscanschedule\index.html
- README.md
- docs\cmdlets\get-spcoverpermissioneduser.md
- Tests\Unit\Clear-SPCRecycleBin.Tests.ps1
- site\cmdlets\register-spclicense\index.html
- docs\cmdlets\Get-SPCPreservationHoldWaste.md
- Tests\Unit\Compare-SPCPermissionSnapshot.Tests.ps1
- site\cmdlets\remove-spcorphaneduser\index.html
- SPClean.psd1
- docs\cmdlets\get-spcprivilegeduser.md
- Tests\Unit\Connect-SPCSiteInternal.Tests.ps1
- site\cmdlets\restore-spcorphaneduser\index.html
- SPClean.psm1
- docs\cmdlets\Get-SPCStorageWaste.md
- Tests\Unit\Connect-SPCTenant.Tests.ps1
- site\getting-started\authentication\index.html
- docs\comparison.md
- docs\cmdlets\Get-SPCVersionWaste.md
- Tests\Unit\Disconnect-SPCTenant.Tests.ps1
- site\getting-started\installation\index.html
- docs\index.md
- docs\cmdlets\index.md
- Tests\Unit\Export-SPCReport.Tests.ps1
- site\getting-started\quickstart\index.html
- docs\licensing.md
- docs\cmdlets\new-spcscanschedule.md
- Tests\Unit\Export-SPCStorageReport.Tests.ps1
- site\assets\javascripts\lunr\tinyseg.js
- docs\Sign-Module.ps1
- docs\cmdlets\Optimize-SPCFileVersion.md
- Tests\Unit\Get-SPCBrokenInheritance.Tests.ps1
- site\assets\javascripts\lunr\wordcut.js
- Private\Connect-SPCSiteInternal.ps1
- docs\cmdlets\register-spclicense.md
- Tests\Unit\Get-SPCGraphSiteUsageInternal.Tests.ps1
- site\assets\javascripts\workers\search.2c215733.min.js
- Private\Get-SPCGraphSiteUsageInternal.ps1
- docs\cmdlets\remove-spcorphaneduser.md
- Tests\Unit\Get-SPCGuestAccess.Tests.ps1
- site\assets\javascripts\workers\search.2c215733.min.js.map
- Private\Get-SPCLibraryBrokenInheritanceInternal.ps1
- docs\cmdlets\repair-spcmismatchuser.md
- Tests\Unit\Get-SPCInactiveSite.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.ar.min.js
- Private\Get-SPCRiskLevel.ps1
- docs\cmdlets\restore-spcorphaneduser.md
- Tests\Unit\Get-SPCMismatchUser.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.da.min.js
- Private\Invoke-SPCGraphBatch.ps1
- docs\getting-started\authentication.md
- Tests\Unit\Get-SPCOrphanedUser.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.de.min.js
- Private\Invoke-SPCSafeRecycleBinPurgeInternal.ps1
- docs\getting-started\installation.md
- Tests\Unit\Get-SPCOverPermissionedUser.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.du.min.js
- Private\Invoke-SPCTempElevationInternal.ps1
- docs\getting-started\quickstart.md
- Tests\Unit\Get-SPCPreservationHoldWaste.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.el.min.js
- Private\Invoke-SPCVersionTrimmingInternal.ps1
- Public\Auth\Connect-SPCTenant.ps1
- Tests\Unit\Get-SPCPrivilegedUser.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.es.min.js
- Private\LicenseManager.ps1
- Public\Auth\Disconnect-SPCTenant.ps1
- Tests\Unit\Get-SPCRiskLevel.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.fi.min.js
- Private\Measure-SPCScoreInternal.ps1
- Public\License\Get-SPCLicenseInfo.ps1
- Tests\Unit\Get-SPCStorageWaste.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.fr.min.js
- Private\New-SPCDashboardHtmlInternal.ps1
- Public\License\Register-SPCLicense.ps1
- Tests\Unit\Get-SPCVersionWaste.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.he.min.js
- Private\New-SPCStorageDashboardHtmlInternal.ps1
- Public\Remediate\Clear-SPCRecycleBin.ps1
- Tests\Unit\Invoke-SPCDashboardReport.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.hi.min.js
- Private\PnPWrappers.ps1
- Public\Remediate\Optimize-SPCFileVersion.ps1
- Tests\Unit\Invoke-SPCGraphBatch.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.hu.min.js
- Private\Save-SPCPermissionSnapshot.ps1
- Public\Remediate\Remove-SPCOrphanedUser.ps1
- Tests\Unit\Invoke-SPCPermissionAnalytics.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.hy.min.js
- Private\Test-SPCConnection.ps1
- Public\Remediate\Repair-SPCMismatchUser.ps1
- Tests\Unit\Invoke-SPCTempElevationInternal.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.it.min.js
- Private\Test-SPCPurviewHoldInternal.ps1
- Public\Remediate\Restore-SPCOrphanedUser.ps1
- Tests\Unit\LicenseManager.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.ja.min.js
- Private\Write-SPCAuditLogInternal.ps1
- Public\Report\Compare-SPCPermissionSnapshot.ps1
- Tests\Unit\New-SPCDashboardHtmlInternal.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.jp.min.js
- site\404.html
- Public\Report\Export-SPCReport.ps1
- Tests\Unit\New-SPCStorageDashboardHtmlInternal.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.kn.min.js
- site\index.html
- Public\Report\Export-SPCStorageReport.ps1
- Tests\Unit\Optimize-SPCFileVersion.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.ko.min.js
- site\sitemap.xml
- Public\Report\Get-SPCPermissionHealthScore.ps1
- Tests\Unit\Phase2.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.multi.min.js
- site\sitemap.xml.gz
- Public\Report\Invoke-SPCDashboardReport.ps1
- Tests\Unit\Remove-SPCOrphanedUser.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.nl.min.js
- tools\Setup-SPCleanApp.ps1
- Public\Report\Invoke-SPCPermissionAnalytics.ps1
- Tests\Unit\Repair-SPCMismatchUser.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.no.min.js
- .github\ISSUE_TEMPLATE\beta_feedback.md
- Public\Scan\Get-SPCBrokenInheritance.ps1
- Tests\Unit\Restore-SPCOrphanedUser.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.pt.min.js
- .github\ISSUE_TEMPLATE\bug_report.md
- Public\Scan\Get-SPCGuestAccess.ps1
- Tests\Unit\Test-SPCPurviewHoldInternal.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.ro.min.js
- .github\ISSUE_TEMPLATE\feature_request.md
- Public\Scan\Get-SPCInactiveSite.ps1
- Tests\Unit\Write-SPCAuditLogInternal.Tests.ps1
- site\assets\javascripts\lunr\min\lunr.ru.min.js
- .github\workflows\ci.yml
- Public\Scan\Get-SPCMismatchUser.ps1
- site\assets\images\favicon.png
- site\assets\javascripts\lunr\min\lunr.sa.min.js
- .github\workflows\mkdocs.yml
- Public\Scan\Get-SPCOrphanedUser.ps1
- site\assets\javascripts\bundle.79ae519e.min.js
- site\assets\javascripts\lunr\min\lunr.stemmer.support.min.js
- .github\workflows\publish.yml
- Public\Scan\Get-SPCOverPermissionedUser.ps1
- site\assets\javascripts\bundle.79ae519e.min.js.map
- site\assets\javascripts\lunr\min\lunr.sv.min.js
- docs\assets\demo.gif
- Public\Scan\Get-SPCPreservationHoldWaste.ps1
- site\assets\stylesheets\main.484c7ddc.min.css
- site\assets\javascripts\lunr\min\lunr.ta.min.js
- docs\cmdlets\Clear-SPCRecycleBin.md
- Public\Scan\Get-SPCPrivilegedUser.ps1
- site\assets\stylesheets\main.484c7ddc.min.css.map
- site\assets\javascripts\lunr\min\lunr.te.min.js
- docs\cmdlets\connect-spctenant.md
- Public\Scan\Get-SPCStorageWaste.ps1
- site\assets\stylesheets\palette.ab4e12ef.min.css
- site\assets\javascripts\lunr\min\lunr.th.min.js
- docs\cmdlets\disconnect-spctenant.md
- Public\Scan\Get-SPCVersionWaste.ps1
- site\assets\stylesheets\palette.ab4e12ef.min.css.map
- site\assets\javascripts\lunr\min\lunr.tr.min.js
- docs\cmdlets\export-spcreport.md
- Public\Schedule\New-SPCScanSchedule.ps1
- site\cmdlets\connect-spctenant\index.html
- site\assets\javascripts\lunr\min\lunr.vi.min.js
- docs\cmdlets\Export-SPCStorageReport.md
- site\cmdlets\index.html
- site\cmdlets\disconnect-spctenant\index.html
- site\assets\javascripts\lunr\min\lunr.zh.min.js
- docs\cmdlets\get-spcguestaccess.md