site/search/search_index.json

{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"SPClean","text":"<p>PowerShell toolkit for SharePoint Online permission hygiene. Find orphaned users, score risk, generate reports, and remove safely \u2014 without enterprise-software pricing.</p> <p> </p>"},{"location":"#why-spclean","title":"Why SPClean","text":"<p>Every SharePoint tenant accumulates orphaned users \u2014 accounts that remain in site permission lists long after the employee left, the contractor finished, or the guest expired. Microsoft has no built-in tool to find and clean them at scale.</p> <p>The result: deleted accounts still holding active permissions, compliance reports that flag ghost identities, and hours of manual cleanup per tenant.</p> <p>SPClean fixes this in minutes:</p> <pre><code>Connect-SPCTenant -TenantName contoso -ClientId '&lt;app-id&gt;'\n\n# Scan all sites, export a colour-coded HTML report\nGet-SPCOrphanedUser -AllSites | Export-SPCReport -Format HTML -IncludeSummary\n\n# Preview what would be removed \u2014 no changes made\nGet-SPCOrphanedUser -AllSites | Remove-SPCOrphanedUser -WhatIf\n\n# Remove HIGH-risk orphans with a snapshot backup\nGet-SPCOrphanedUser -AllSites |\n Where-Object RiskLevel -eq 'HIGH' |\n Remove-SPCOrphanedUser -CreateSnapshot -SnapshotPath C:\\Snapshots\n</code></pre>"},{"location":"#what-spclean-does","title":"What SPClean does","text":"Capability Detail Detect Scans site User Information Lists and cross-checks with Entra ID to identify deleted, soft-deleted, disabled, and guest-orphaned accounts Score risk Classifies each orphan as HIGH / MEDIUM / LOW based on account state and active permission assignments Report Exports CSV, JSON, or self-contained HTML reports with sortable columns and colour-coded risk badges Remove safely Removes users from UILs and revokes direct role assignments \u2014 with <code>-WhatIf</code> preview and JSON snapshot backup Restore Re-applies permissions from a snapshot after accidental removal Schedule Registers a Windows Scheduled Task that runs unattended scans using AppOnly auth <p>Get Started \u2192 Cmdlet Reference \u2192 Licensing \u2192</p>"},{"location":"licensing/","title":"Licensing","text":"<p>SPClean uses a key-based license verified entirely offline \u2014 no internet check, no phone-home.</p>"},{"location":"licensing/#tiers","title":"Tiers","text":"Feature Free Pro Consultant Price $0 forever $79 / tenant / year $149 / year Orphan detection (<code>Get-SPCOrphanedUser</code>) \u2705 \u2705 \u2705 CSV and JSON reports \u2705 \u2705 \u2705 Unlimited sites per scan \u2705 \u2705 \u2705 HTML report with risk badges and sorting \u2014 \u2705 \u2705 Snapshot backup before removal (<code>-CreateSnapshot</code>) \u2014 \u2705 \u2705 Restore permissions from snapshot \u2014 \u2705 \u2705 Scheduled automated scans \u2014 \u2705 \u2705 Unlimited tenants \u2014 \u2014 \u2705 White-label HTML report (<code>-BrandingName</code>) \u2014 \u2014 \ud83d\udd1c v1.1 Priority support \u2014 \u2014 \u2705 Intended use Personal / evaluation Single-org admin MSP / multi-tenant consultant <p>Free tier</p> <p>Free lets you scan every site and export CSV/JSON reports without a key \u2014 enough to identify and audit orphans. Pro and Consultant unlock the full remediation and automation workflow.</p> <p>\u2192 Purchase on Gumroad</p>"},{"location":"licensing/#check-your-current-license-status","title":"Check your current license status","text":"<pre><code>Get-SPCLicenseInfo\n</code></pre> <p>Example output (unlicensed):</p> <pre><code>Status : Unlicensed\nTier : FREE\nEmail :\nExpiresAt :\n</code></pre>"},{"location":"licensing/#activate-a-license","title":"Activate a license","text":"<p>After purchasing from Gumroad you will receive a key in the format <code>SPCLEAN-PRO-\u2026</code> by email.</p> <pre><code>Register-SPCLicense -LicenseKey 'SPCLEAN-PRO-&lt;payload&gt;-&lt;sig&gt;'\n</code></pre> <p>The key is validated offline (HMAC-SHA256), written to <code>%APPDATA%\\SPClean\\license.lic</code>, and takes effect immediately \u2014 no restart required.</p> <p>Verify activation:</p> <pre><code>Get-SPCLicenseInfo\n</code></pre> <pre><code>Status : Active\nTier : PRO\nEmail : you@contoso.com\nExpiresAt : 2027-06-25 00:00:00\n</code></pre>"},{"location":"licensing/#what-happens-when-a-feature-requires-a-license","title":"What happens when a feature requires a license","text":"<pre><code>Export-SPCReport: ERR-LIC-003: 'HTMLReport' requires a Pro or Consultant license.\nCurrent status: Unlicensed.\n\u2192 Purchase at: https://hungpham2802.gumroad.com\n\u2192 Register with: Register-SPCLicense -LicenseKey 'SPCLEAN-PRO-...'\n</code></pre> <p>-WhatIf is never gated</p> <p><code>-WhatIf</code> on all write cmdlets always works without a license \u2014 preview is never restricted.</p>"},{"location":"licensing/#error-codes","title":"Error codes","text":"Code Meaning <code>ERR-LIC-001</code> Key format is invalid or signature does not match <code>ERR-LIC-002</code> Key has expired <code>ERR-LIC-003</code> Feature requires a Pro or Consultant license <code>ERR-LIC-004</code> Feature requires a Consultant license"},{"location":"licensing/#see-also","title":"See also","text":"<ul> <li><code>Register-SPCLicense</code> \u2014 activate a key</li> <li><code>Get-SPCLicenseInfo</code> \u2014 inspect current status</li> </ul>"},{"location":"cmdlets/","title":"Cmdlet Reference","text":"<p>SPClean exports 9 cmdlets grouped by function.</p> Cmdlet Type Description Connect-SPCTenant Auth Establish a SharePoint Online + Graph session Disconnect-SPCTenant Auth Clear the module connection state Get-SPCOrphanedUser Read Scan sites and return orphaned user objects Export-SPCReport Report Generate CSV, HTML, or JSON reports from scan results Remove-SPCOrphanedUser Write Remove orphaned users from UILs and revoke direct permissions Restore-SPCOrphanedUser Write Re-apply permissions from a JSON snapshot New-SPCScanSchedule Util Register a Windows Scheduled Task for automated scans Register-SPCLicense Util Validate and activate a license key Get-SPCLicenseInfo Util Return the current license status"},{"location":"cmdlets/#typical-workflow","title":"Typical workflow","text":"<pre><code>Connect-SPCTenant\n \u2193\nGet-SPCOrphanedUser \u2192 Export-SPCReport\n \u2193\nRemove-SPCOrphanedUser (with -CreateSnapshot)\n \u2193\nRestore-SPCOrphanedUser (if rollback needed)\n \u2193\nDisconnect-SPCTenant\n</code></pre>"},{"location":"cmdlets/#license-requirements","title":"License requirements","text":"Feature Free Pro Consultant <code>Get-SPCOrphanedUser</code> \u2705 \u2705 \u2705 <code>Export-SPCReport -Format CSV\\|JSON</code> \u2705 \u2705 \u2705 <code>Export-SPCReport -Format HTML</code> \u2014 \u2705 \u2705 <code>Remove-SPCOrphanedUser -CreateSnapshot</code> \u2014 \u2705 \u2705 <code>Restore-SPCOrphanedUser</code> \u2014 \u2705 \u2705 <code>New-SPCScanSchedule</code> \u2014 \u2705 \u2705 <p>See Licensing for details and pricing.</p>"},{"location":"cmdlets/connect-spctenant/","title":"Connect-SPCTenant","text":"<p>Establishes a session to SharePoint Online and Microsoft Graph for all SPClean cmdlets.</p>"},{"location":"cmdlets/connect-spctenant/#synopsis","title":"Synopsis","text":"<pre><code>Connect-SPCTenant\n -TenantName &lt;string&gt;\n [-AuthMethod Interactive | AppOnly]\n [-ClientId &lt;string&gt;]\n [-CertificatePath &lt;string&gt;]\n [-CertificatePassword &lt;SecureString&gt;]\n [-ClientSecret &lt;SecureString&gt;]\n</code></pre>"},{"location":"cmdlets/connect-spctenant/#parameters","title":"Parameters","text":"Parameter Type Required Description <code>-TenantName</code> <code>string</code> \u2705 Short name (<code>contoso</code>), full domain (<code>contoso.onmicrosoft.com</code>), or SharePoint root URL <code>-AuthMethod</code> <code>Interactive \\| AppOnly</code> Authentication method. Default: <code>Interactive</code> <code>-ClientId</code> <code>string</code> Entra App Registration client ID. Required for Interactive and AppOnly <code>-CertificatePath</code> <code>string</code> Path to <code>.pfx</code> certificate file. AppOnly only <code>-CertificatePassword</code> <code>SecureString</code> Password for the <code>.pfx</code> file. AppOnly only <code>-ClientSecret</code> <code>SecureString</code> Client secret. AppOnly alternative to certificate"},{"location":"cmdlets/connect-spctenant/#returns","title":"Returns","text":"<p><code>SPC.ConnectionInfo</code></p>"},{"location":"cmdlets/connect-spctenant/#error-codes","title":"Error codes","text":"Code Condition <code>ERR-AUTH-001</code> Cannot resolve tenant URL from TenantName <code>ERR-AUTH-002</code> Authentication failed \u2014 invalid credentials or insufficient permissions <code>ERR-AUTH-003</code> AppOnly auth requires <code>-ClientId</code> and either <code>-CertificatePath</code> or <code>-ClientSecret</code> <code>ERR-AUTH-004</code> Interactive auth requires <code>-ClientId</code> in PnP.PowerShell 3.x"},{"location":"cmdlets/connect-spctenant/#examples","title":"Examples","text":"InteractiveAppOnly \u2014 CertificateAppOnly \u2014 Client Secret <pre><code>Connect-SPCTenant -TenantName contoso -ClientId '&lt;delegated-app-id&gt;'\n</code></pre> <pre><code>$certPwd = Read-Host -AsSecureString 'Certificate password'\nConnect-SPCTenant -TenantName contoso `\n -AuthMethod AppOnly `\n -ClientId '&lt;app-id&gt;' `\n -CertificatePath C:\\certs\\spclean.pfx `\n -CertificatePassword $certPwd\n</code></pre> <pre><code>$secret = Read-Host -AsSecureString 'Client secret'\nConnect-SPCTenant -TenantName contoso `\n -AuthMethod AppOnly `\n -ClientId '&lt;app-id&gt;' `\n -ClientSecret $secret\n</code></pre>"},{"location":"cmdlets/connect-spctenant/#see-also","title":"See also","text":"<ul> <li>Authentication setup</li> <li>Disconnect-SPCTenant</li> </ul>"},{"location":"cmdlets/disconnect-spctenant/","title":"Disconnect-SPCTenant","text":"<p>Clears the SPClean module connection state and disconnects both PnP and Microsoft Graph sessions.</p>"},{"location":"cmdlets/disconnect-spctenant/#synopsis","title":"Synopsis","text":"<pre><code>Disconnect-SPCTenant\n</code></pre>"},{"location":"cmdlets/disconnect-spctenant/#parameters","title":"Parameters","text":"<p>None.</p>"},{"location":"cmdlets/disconnect-spctenant/#returns","title":"Returns","text":"<p>Nothing. This cmdlet never throws \u2014 it is safe to call even when no connection is active.</p>"},{"location":"cmdlets/disconnect-spctenant/#behavior","title":"Behavior","text":"<ul> <li>Disconnects the active PnP.PowerShell session</li> <li>Disconnects the Microsoft Graph session</li> <li>Clears <code>$script:SPCContext</code> (the module-level connection object)</li> <li>Clears the license cache (<code>$script:SPCLicenseCache</code>)</li> </ul>"},{"location":"cmdlets/disconnect-spctenant/#examples","title":"Examples","text":"<pre><code># Standard disconnect after a scan session\nConnect-SPCTenant -TenantName contoso -ClientId '&lt;app-id&gt;'\nGet-SPCOrphanedUser -AllSites | Export-SPCReport -Format CSV\nDisconnect-SPCTenant\n</code></pre>"},{"location":"cmdlets/disconnect-spctenant/#see-also","title":"See also","text":"<ul> <li>Connect-SPCTenant</li> </ul>"},{"location":"cmdlets/export-spcreport/","title":"Export-SPCReport","text":"<p>Generates a report from orphaned user pipeline input in CSV, HTML, or JSON format.</p> <p>License requirement</p> <p>HTML format requires a Pro or Consultant license. CSV and JSON are available on the Free tier.</p>"},{"location":"cmdlets/export-spcreport/#synopsis","title":"Synopsis","text":"<pre><code>Export-SPCReport\n -InputObject &lt;SPC.OrphanedUser[]&gt;\n [-Format CSV | HTML | JSON]\n [-OutputPath &lt;string&gt;]\n [-GroupBy Site | RiskLevel | OrphanType]\n [-IncludeSummary]\n [-PassThru]\n</code></pre>"},{"location":"cmdlets/export-spcreport/#parameters","title":"Parameters","text":"Parameter Type Required Description <code>-InputObject</code> <code>SPC.OrphanedUser[]</code> \u2705 Accepts pipeline input <code>-Format</code> <code>CSV \\| HTML \\| JSON</code> Output format. Default: <code>CSV</code> <code>-OutputPath</code> <code>string</code> Full path for the output file. Default: auto-named file in the current directory <code>-GroupBy</code> <code>Site \\| RiskLevel \\| OrphanType</code> Sort/group rows by field. Default: <code>Site</code> <code>-IncludeSummary</code> switch Prepend a summary card with totals and breakdown by risk level <code>-PassThru</code> switch Pass input <code>SPC.OrphanedUser</code> objects through the pipeline after writing the report"},{"location":"cmdlets/export-spcreport/#returns","title":"Returns","text":"<p><code>SPC.ReportResult</code> with properties:</p> Property Description <code>FilePath</code> Absolute path to the generated file <code>Format</code> The format used (<code>CSV</code>, <code>HTML</code>, or <code>JSON</code>) <code>TotalOrphansReported</code> Count of records written <code>GeneratedAt</code> UTC timestamp"},{"location":"cmdlets/export-spcreport/#format-notes","title":"Format notes","text":""},{"location":"cmdlets/export-spcreport/#csv","title":"CSV","text":"<ul> <li>UTF-8 with BOM (Excel-compatible)</li> <li>11 columns covering all <code>SPC.OrphanedUser</code> properties</li> <li>Sorted by the <code>-GroupBy</code> field</li> </ul>"},{"location":"cmdlets/export-spcreport/#html","title":"HTML","text":"<ul> <li>Self-contained \u2014 all CSS and JavaScript are inlined; no external dependencies</li> <li>Colour-coded risk badges: HIGH (red), MEDIUM (amber), LOW (green)</li> <li>Sortable columns via click</li> <li>Optional summary card with totals and breakdown chart when <code>-IncludeSummary</code> is used</li> </ul>"},{"location":"cmdlets/export-spcreport/#json","title":"JSON","text":"<ul> <li>Root object grouped by the <code>-GroupBy</code> field</li> <li>Optional <code>summary</code> block when <code>-IncludeSummary</code> is used</li> </ul>"},{"location":"cmdlets/export-spcreport/#examples","title":"Examples","text":"HTML report with summaryCSV grouped by risk levelJSON with pass-through <pre><code>Get-SPCOrphanedUser -AllSites |\n Export-SPCReport -Format HTML -IncludeSummary -OutputPath C:\\reports\\orphans.html\n</code></pre> <pre><code>Get-SPCOrphanedUser -SiteUrl $url |\n Export-SPCReport -Format CSV -GroupBy RiskLevel -OutputPath C:\\reports\\orphans.csv\n</code></pre> <pre><code># Export to JSON and continue piping the objects\n$orphans = Get-SPCOrphanedUser -SiteUrl $url |\n Export-SPCReport -Format JSON -PassThru\n\n# $orphans now contains the SPC.OrphanedUser objects for further processing\n$orphans | Where-Object RiskLevel -eq 'HIGH'\n</code></pre>"},{"location":"cmdlets/export-spcreport/#see-also","title":"See also","text":"<ul> <li>Get-SPCOrphanedUser</li> <li>Remove-SPCOrphanedUser</li> <li>Licensing</li> </ul>"},{"location":"cmdlets/get-spclicenseinfo/","title":"Get-SPCLicenseInfo","text":"<p>Returns the current SPClean license status from the module cache or disk. Never throws \u2014 safe to call at any time, including before <code>Connect-SPCTenant</code>.</p>"},{"location":"cmdlets/get-spclicenseinfo/#synopsis","title":"Synopsis","text":"<pre><code>Get-SPCLicenseInfo\n</code></pre>"},{"location":"cmdlets/get-spclicenseinfo/#parameters","title":"Parameters","text":"<p>None.</p>"},{"location":"cmdlets/get-spclicenseinfo/#returns","title":"Returns","text":"<p><code>SPC.LicenseInfo</code> with the following properties:</p> Property Type Description <code>Status</code> <code>string</code> <code>Active</code>, <code>Expired</code>, <code>Invalid</code>, or <code>Unlicensed</code> <code>Tier</code> <code>string</code> <code>FREE</code>, <code>PRO</code>, or <code>CONSULTANT</code> <code>Email</code> <code>string</code> Email address the license was issued to <code>ExpiresAt</code> <code>DateTime?</code> License expiry date (UTC). Empty for unlicensed <code>RegisteredAt</code> <code>DateTime?</code> Date the license was registered on this machine <code>LicenseId</code> <code>string</code> Unique license identifier embedded in the key"},{"location":"cmdlets/get-spclicenseinfo/#examples","title":"Examples","text":"Check statusConditional guard <pre><code>Get-SPCLicenseInfo\n</code></pre> <p>Unlicensed output: <pre><code>Status : Unlicensed\nTier : FREE\nEmail :\nExpiresAt :\nRegisteredAt:\nLicenseId :\n</code></pre></p> <p>Active output: <pre><code>Status : Active\nTier : PRO\nEmail : you@contoso.com\nExpiresAt : 2027-06-25 00:00:00\nRegisteredAt: 2026-06-25 09:14:00\nLicenseId : abc123\n</code></pre></p> <pre><code>if ((Get-SPCLicenseInfo).Status -ne 'Active') {\n Write-Warning 'HTML reports and scheduled scans require a Pro license.'\n Write-Warning 'Purchase at: https://hungpham2802.gumroad.com'\n}\n</code></pre>"},{"location":"cmdlets/get-spclicenseinfo/#notes","title":"Notes","text":"<ul> <li>The cmdlet checks the module-level cache first. If the cache is empty it reads <code>%APPDATA%\\SPClean\\license.lic</code> from disk and re-verifies the HMAC signature.</li> <li>If no license file exists, <code>Status = Unlicensed</code> and <code>Tier = FREE</code> are returned (no error).</li> <li>After calling <code>Register-SPCLicense</code>, the cache is cleared and <code>Get-SPCLicenseInfo</code> will reflect the new key immediately.</li> </ul>"},{"location":"cmdlets/get-spclicenseinfo/#see-also","title":"See also","text":"<ul> <li>Register-SPCLicense</li> <li>Licensing</li> </ul>"},{"location":"cmdlets/get-spcorphaneduser/","title":"Get-SPCOrphanedUser","text":"<p>Scans one or more SharePoint site collections, cross-checks the User Information List against Entra ID, and returns orphaned user objects.</p>"},{"location":"cmdlets/get-spcorphaneduser/#synopsis","title":"Synopsis","text":"<pre><code>Get-SPCOrphanedUser\n [-SiteUrl &lt;string[]&gt;]\n [-AllSites]\n [-IncludeGuests]\n [-IncludeDisabled]\n [-ExcludeSiteUrl &lt;string[]&gt;]\n [-ThrottleLimit &lt;int&gt;]\n</code></pre>"},{"location":"cmdlets/get-spcorphaneduser/#parameters","title":"Parameters","text":"Parameter Type Required Description <code>-SiteUrl</code> <code>string[]</code> One or more site collection URLs. Accepts pipeline input <code>-AllSites</code> switch Scan all site collections in the tenant. Mutually exclusive with <code>-SiteUrl</code> <code>-IncludeGuests</code> switch Include orphaned external (<code>#EXT#</code>) guest accounts <code>-IncludeDisabled</code> switch Include Entra-disabled accounts (not yet deleted) <code>-ExcludeSiteUrl</code> <code>string[]</code> URL patterns to skip when using <code>-AllSites</code>. Supports wildcards <code>-ThrottleLimit</code> <code>int</code> Maximum concurrent site connections. Default: <code>3</code>, maximum: <code>10</code>"},{"location":"cmdlets/get-spcorphaneduser/#returns","title":"Returns","text":"<p><code>SPC.OrphanedUser</code> objects with the following properties:</p> Property Type Description <code>UPN</code> <code>string</code> User principal name <code>DisplayName</code> <code>string</code> Display name from the User Information List <code>LoginName</code> <code>string</code> SharePoint claim string (e.g. <code>i:0#.f\\|membership\\|user@contoso.com</code>) <code>SiteUrl</code> <code>string</code> Site collection URL <code>SiteTitle</code> <code>string</code> Site collection display name <code>OrphanType</code> <code>string</code> <code>Deleted</code>, <code>SoftDeleted</code>, <code>Disabled</code>, or <code>GuestOrphaned</code> <code>RiskLevel</code> <code>string</code> <code>HIGH</code>, <code>MEDIUM</code>, or <code>LOW</code> <code>HasDirectPermissions</code> <code>bool</code> Whether the user has direct role assignments (not just group membership) <code>GroupMemberships</code> <code>string[]</code> SharePoint groups the user belongs to <code>DetectedAt</code> <code>DateTime</code> UTC timestamp of detection"},{"location":"cmdlets/get-spcorphaneduser/#risk-scoring","title":"Risk scoring","text":"Level Condition HIGH <code>Deleted</code> or <code>GuestOrphaned</code> with active direct permission assignments MEDIUM <code>SoftDeleted</code> (still accessible until permanently purged) or <code>Disabled</code> with direct permissions LOW <code>Deleted</code> with no active permissions \u2014 UIL entry only"},{"location":"cmdlets/get-spcorphaneduser/#examples","title":"Examples","text":"Single siteMultiple sitesAll sites, exclude OneDriveAll sites, include guests and disabled <pre><code>Get-SPCOrphanedUser -SiteUrl 'https://contoso.sharepoint.com/sites/HR'\n</code></pre> <pre><code>Get-SPCOrphanedUser -SiteUrl 'https://contoso.sharepoint.com/sites/HR',\n 'https://contoso.sharepoint.com/sites/Finance'\n</code></pre> <pre><code>Get-SPCOrphanedUser -AllSites -ExcludeSiteUrl '*-my.sharepoint.com/*'\n</code></pre> <pre><code>Get-SPCOrphanedUser -AllSites -IncludeGuests -IncludeDisabled\n</code></pre>"},{"location":"cmdlets/get-spcorphaneduser/#pipeline-usage","title":"Pipeline usage","text":"<pre><code># Scan \u2192 filter \u2192 report\nGet-SPCOrphanedUser -AllSites |\n Where-Object RiskLevel -eq 'HIGH' |\n Export-SPCReport -Format HTML -IncludeSummary\n\n# Scan \u2192 filter \u2192 remove (with WhatIf preview first)\nGet-SPCOrphanedUser -SiteUrl $url |\n Where-Object OrphanType -eq 'Deleted' |\n Remove-SPCOrphanedUser -WhatIf\n</code></pre>"},{"location":"cmdlets/get-spcorphaneduser/#notes","title":"Notes","text":"<ul> <li>Requires an active connection established by <code>Connect-SPCTenant</code>.</li> <li>System accounts (<code>SHAREPOINT\\system</code>, <code>NT AUTHORITY\\*</code>, claim type <code>c:0(.s|true)</code>) are automatically filtered and never returned.</li> <li><code>AllSites</code> uses <code>Write-Progress</code> to display a progress bar across the tenant scan.</li> <li>Detection uses Microsoft Graph batch requests for efficient Entra ID lookups.</li> </ul>"},{"location":"cmdlets/get-spcorphaneduser/#see-also","title":"See also","text":"<ul> <li>Export-SPCReport</li> <li>Remove-SPCOrphanedUser</li> </ul>"},{"location":"cmdlets/new-spcscanschedule/","title":"New-SPCScanSchedule","text":"<p>Generates a self-contained scan script and registers a Windows Scheduled Task that runs unattended orphan scans using AppOnly authentication.</p> <p>License requirement</p> <p>Requires a Pro or Consultant license.</p> <p>Windows only</p> <p><code>New-SPCScanSchedule</code> uses <code>Register-ScheduledTask</code> which is only available on Windows. On non-Windows systems the cmdlet writes the scan script and outputs the equivalent cron expression but does not register a task.</p>"},{"location":"cmdlets/new-spcscanschedule/#synopsis","title":"Synopsis","text":"<pre><code>New-SPCScanSchedule\n -TenantName &lt;string&gt;\n -ClientId &lt;string&gt;\n -CertificatePath &lt;string&gt;\n -CertificatePassword &lt;SecureString&gt;\n [-Schedule Daily | Weekly | Monthly]\n [-ScheduleAt &lt;datetime&gt;]\n [-ReportFormat HTML | CSV | JSON]\n [-ReportOutputPath &lt;string&gt;]\n [-TaskName &lt;string&gt;]\n [-WhatIf]\n</code></pre>"},{"location":"cmdlets/new-spcscanschedule/#parameters","title":"Parameters","text":"Parameter Type Required Description <code>-TenantName</code> <code>string</code> \u2705 Short tenant name or full domain <code>-ClientId</code> <code>string</code> \u2705 Entra App Registration client ID (AppOnly) <code>-CertificatePath</code> <code>string</code> \u2705 Path to <code>.pfx</code> certificate <code>-CertificatePassword</code> <code>SecureString</code> \u2705 Password for the <code>.pfx</code> file <code>-Schedule</code> <code>Daily \\| Weekly \\| Monthly</code> Recurrence. Mutually exclusive with <code>-ScheduleAt</code> <code>-ScheduleAt</code> <code>datetime</code> One-time execution at this date/time <code>-ReportFormat</code> <code>HTML \\| CSV \\| JSON</code> Format for the generated report. Default: <code>HTML</code> <code>-ReportOutputPath</code> <code>string</code> Directory for generated reports <code>-TaskName</code> <code>string</code> Windows Task Scheduler task name. Default: <code>SPClean_OrphanedUserScan</code> <code>-WhatIf</code> switch Show what task would be registered without creating it"},{"location":"cmdlets/new-spcscanschedule/#returns","title":"Returns","text":"<p><code>SPC.ScheduleResult</code> with properties:</p> Property Description <code>TaskName</code> Registered task name <code>ScriptPath</code> Path to the generated scan script <code>Schedule</code> Recurrence type or one-time datetime <code>NextRun</code> Scheduled next execution time <code>Status</code> <code>Registered</code> or <code>ScriptOnly</code> (non-Windows)"},{"location":"cmdlets/new-spcscanschedule/#security-note","title":"Security note","text":"<p>The certificate password is encrypted using Windows DPAPI (<code>ConvertFrom-SecureString</code>) and stored in the generated script. It is only decryptable by the same Windows user account on the same machine that ran <code>New-SPCScanSchedule</code>. The plain-text password is never written to disk.</p>"},{"location":"cmdlets/new-spcscanschedule/#example","title":"Example","text":"<pre><code>$certPwd = Read-Host -AsSecureString 'Certificate password'\n\nNew-SPCScanSchedule -TenantName contoso `\n -ClientId '&lt;app-id&gt;' `\n -CertificatePath C:\\certs\\spclean.pfx `\n -CertificatePassword $certPwd `\n -Schedule Weekly `\n -ReportFormat HTML `\n -ReportOutputPath C:\\Reports\\SPClean\n</code></pre>"},{"location":"cmdlets/new-spcscanschedule/#full-workflow","title":"Full workflow","text":"<pre><code># 1. Verify credentials work\n$certPwd = Read-Host -AsSecureString 'Certificate password'\nConnect-SPCTenant -TenantName contoso -AuthMethod AppOnly `\n -ClientId '&lt;app-id&gt;' -CertificatePath C:\\certs\\spclean.pfx -CertificatePassword $certPwd\n\n# 2. Register the scheduled task\nNew-SPCScanSchedule -TenantName contoso `\n -ClientId '&lt;app-id&gt;' `\n -CertificatePath C:\\certs\\spclean.pfx `\n -CertificatePassword $certPwd `\n -Schedule Weekly `\n -ReportFormat HTML `\n -ReportOutputPath C:\\Reports\\SPClean\n\nDisconnect-SPCTenant\n</code></pre>"},{"location":"cmdlets/new-spcscanschedule/#see-also","title":"See also","text":"<ul> <li>Authentication \u2014 AppOnly / certificate</li> </ul>"},{"location":"cmdlets/register-spclicense/","title":"Register-SPCLicense","text":"<p>Validates and activates a SPClean license key. Writes the key to <code>%APPDATA%\\SPClean\\license.lic</code> and clears the in-memory cache so the new tier takes effect immediately in the current session.</p>"},{"location":"cmdlets/register-spclicense/#synopsis","title":"Synopsis","text":"<pre><code>Register-SPCLicense\n -LicenseKey &lt;string&gt;\n [-Force]\n [-WhatIf]\n [-Confirm]\n</code></pre>"},{"location":"cmdlets/register-spclicense/#parameters","title":"Parameters","text":"Parameter Type Required Description <code>-LicenseKey</code> <code>string</code> \u2705 The license key in format <code>SPCLEAN-PRO-&lt;payload&gt;-&lt;sig&gt;</code> or <code>SPCLEAN-CONSULTANT-&lt;payload&gt;-&lt;sig&gt;</code> <code>-Force</code> switch Overwrite an existing <code>license.lic</code> without prompting <code>-WhatIf</code> switch Validate the key and show what would change, without writing the file <code>-Confirm</code> switch Prompt for confirmation before writing"},{"location":"cmdlets/register-spclicense/#returns","title":"Returns","text":"<p><code>SPC.LicenseInfo</code> reflecting the newly activated license.</p>"},{"location":"cmdlets/register-spclicense/#key-validation","title":"Key validation","text":"<p>Keys are validated entirely offline using HMAC-SHA256 \u2014 no internet connection is required. A key encodes the license tier and email address in a URL-safe Base64 payload, and carries a fixed-length 43-character signature.</p>"},{"location":"cmdlets/register-spclicense/#examples","title":"Examples","text":"ActivateValidate without writing (WhatIf) <pre><code>Register-SPCLicense -LicenseKey 'SPCLEAN-PRO-&lt;payload&gt;-&lt;sig&gt;'\n</code></pre> <pre><code># Verify the key is valid before committing\nRegister-SPCLicense -LicenseKey 'SPCLEAN-PRO-&lt;payload&gt;-&lt;sig&gt;' -WhatIf\n</code></pre>"},{"location":"cmdlets/register-spclicense/#error-codes","title":"Error codes","text":"Code Meaning <code>ERR-LIC-001</code> Key format is invalid or HMAC signature does not match <code>ERR-LIC-002</code> Key has expired <code>ERR-LIC-003</code> Feature requires a Pro or Consultant license <code>ERR-LIC-004</code> Feature requires a Consultant license"},{"location":"cmdlets/register-spclicense/#see-also","title":"See also","text":"<ul> <li>Get-SPCLicenseInfo</li> <li>Licensing</li> </ul>"},{"location":"cmdlets/remove-spcorphaneduser/","title":"Remove-SPCOrphanedUser","text":"<p>Removes orphaned users from SharePoint User Information Lists (UILs) and revokes direct role assignments.</p> <p>What this cmdlet does NOT do</p> <ul> <li>Does not delete accounts from Entra ID</li> <li>Does not remove users from SharePoint groups</li> <li>Direct role assignments are revoked; group-inherited access is not affected</li> </ul> <p>License requirement</p> <p><code>-CreateSnapshot</code> requires a Pro or Consultant license. <code>-WhatIf</code> preview is always available on the Free tier.</p>"},{"location":"cmdlets/remove-spcorphaneduser/#synopsis","title":"Synopsis","text":"<pre><code>Remove-SPCOrphanedUser\n -InputObject &lt;SPC.OrphanedUser[]&gt;\n [-RiskLevel HIGH | MEDIUM | LOW]\n [-OrphanType Deleted | SoftDeleted | Disabled | GuestOrphaned]\n [-CreateSnapshot]\n [-SnapshotPath &lt;string&gt;]\n [-Force]\n [-WhatIf]\n [-Confirm]\n</code></pre>"},{"location":"cmdlets/remove-spcorphaneduser/#parameters","title":"Parameters","text":"Parameter Type Required Description <code>-InputObject</code> <code>SPC.OrphanedUser[]</code> \u2705 Accepts pipeline input <code>-RiskLevel</code> <code>HIGH \\| MEDIUM \\| LOW</code> Only process orphans at this risk level <code>-OrphanType</code> <code>Deleted \\| SoftDeleted \\| Disabled \\| GuestOrphaned</code> Filter by orphan type. Default: <code>Deleted</code> only <code>-CreateSnapshot</code> switch Save a JSON permission snapshot before each removal (Pro/Consultant) <code>-SnapshotPath</code> <code>string</code> Directory for snapshot files. Required when <code>-CreateSnapshot</code> is used <code>-Force</code> switch Suppress <code>-Confirm</code> prompts <code>-WhatIf</code> switch Print what would be removed without making any changes <code>-Confirm</code> switch Prompt before each removal"},{"location":"cmdlets/remove-spcorphaneduser/#returns","title":"Returns","text":"<p><code>SPC.RemovalResult</code> per processed record.</p>"},{"location":"cmdlets/remove-spcorphaneduser/#default-behaviour","title":"Default behaviour","text":"<p>By default, only orphans with <code>OrphanType = Deleted</code> are processed. To include soft-deleted or disabled accounts, explicitly pass <code>-OrphanType</code>:</p> <pre><code>Remove-SPCOrphanedUser -OrphanType Deleted,SoftDeleted,Disabled\n</code></pre>"},{"location":"cmdlets/remove-spcorphaneduser/#examples","title":"Examples","text":"WhatIf previewRemove HIGH-risk with snapshotRemove all orphan types <pre><code># See what would be removed \u2014 no changes made\nGet-SPCOrphanedUser -SiteUrl $url | Remove-SPCOrphanedUser -WhatIf\n</code></pre> <pre><code>Get-SPCOrphanedUser -SiteUrl $url |\n Remove-SPCOrphanedUser -RiskLevel HIGH -CreateSnapshot -SnapshotPath C:\\Snapshots\n</code></pre> <pre><code># Use with care \u2014 processes SoftDeleted and Disabled as well\nGet-SPCOrphanedUser -SiteUrl $url -IncludeDisabled |\n Remove-SPCOrphanedUser -OrphanType Deleted,SoftDeleted,Disabled -CreateSnapshot -SnapshotPath C:\\Snapshots\n</code></pre>"},{"location":"cmdlets/remove-spcorphaneduser/#snapshot-files","title":"Snapshot files","text":"<p>When <code>-CreateSnapshot</code> is used, one JSON file is written per user before removal:</p> <pre><code>C:\\Snapshots\\\n user@contoso.com_20260622T120000Z.json\n anotheruser@contoso.com_20260622T120001Z.json\n</code></pre> <p>Use <code>Restore-SPCOrphanedUser</code> to re-apply permissions from a snapshot.</p>"},{"location":"cmdlets/remove-spcorphaneduser/#see-also","title":"See also","text":"<ul> <li>Get-SPCOrphanedUser</li> <li>Restore-SPCOrphanedUser</li> <li>Snapshot and recovery</li> </ul>"},{"location":"cmdlets/restore-spcorphaneduser/","title":"Restore-SPCOrphanedUser","text":"<p>Re-applies direct permission assignments from a JSON snapshot file created by <code>Remove-SPCOrphanedUser -CreateSnapshot</code>.</p> <p>License requirement</p> <p>Requires a Pro or Consultant license.</p>"},{"location":"cmdlets/restore-spcorphaneduser/#synopsis","title":"Synopsis","text":"<pre><code>Restore-SPCOrphanedUser\n -SnapshotPath &lt;string&gt;\n [-WhatIf]\n [-Confirm]\n</code></pre>"},{"location":"cmdlets/restore-spcorphaneduser/#parameters","title":"Parameters","text":"Parameter Type Required Description <code>-SnapshotPath</code> <code>string</code> \u2705 Full path to the <code>.json</code> snapshot file created by <code>Remove-SPCOrphanedUser -CreateSnapshot</code> <code>-WhatIf</code> switch Print what would be restored without making any changes <code>-Confirm</code> switch Prompt before applying each permission"},{"location":"cmdlets/restore-spcorphaneduser/#returns","title":"Returns","text":"<p><code>SPC.RestoreResult</code> with properties:</p> Property Description <code>UserPrincipalName</code> UPN of the user being restored <code>SiteUrl</code> Site where permissions are being re-applied <code>PermissionsRestored</code> Count of direct role assignments successfully re-applied <code>PermissionsFailed</code> Count of assignments that could not be re-applied <code>Status</code> <code>Success</code> or <code>Failed</code>"},{"location":"cmdlets/restore-spcorphaneduser/#limitations","title":"Limitations","text":"<p>What restore does NOT do</p> <ul> <li>Group memberships are not re-applied. SharePoint group memberships are recorded in the snapshot for reference but are not automatically restored. They must be re-added manually.</li> <li>Permanently deleted accounts cannot be restored. If the user's Entra account was permanently deleted (after the 30-day soft-delete window), SharePoint cannot grant permissions to a non-existent identity. The restore will fail with an identity resolution error.</li> <li>Only direct role assignments are restored. Permissions that came from group membership are not affected.</li> </ul>"},{"location":"cmdlets/restore-spcorphaneduser/#examples","title":"Examples","text":"Preview restoreRestore <pre><code>Restore-SPCOrphanedUser `\n -SnapshotPath C:\\Snapshots\\user@contoso.com_20260622T120000Z.json `\n -WhatIf\n</code></pre> <pre><code>Connect-SPCTenant -TenantName contoso -ClientId '&lt;app-id&gt;'\nRestore-SPCOrphanedUser `\n -SnapshotPath C:\\Snapshots\\user@contoso.com_20260622T120000Z.json\nDisconnect-SPCTenant\n</code></pre>"},{"location":"cmdlets/restore-spcorphaneduser/#see-also","title":"See also","text":"<ul> <li>Remove-SPCOrphanedUser</li> </ul>"},{"location":"getting-started/authentication/","title":"Authentication","text":"<p>SPClean supports two authentication methods. You must connect before using any other cmdlet.</p>"},{"location":"getting-started/authentication/#method-a-interactive-delegated-for-manual-use","title":"Method A \u2014 Interactive (delegated, for manual use)","text":"<p>Requires an Entra app registration configured for delegated auth.</p>"},{"location":"getting-started/authentication/#one-time-app-registration-setup","title":"One-time app registration setup","text":"<ol> <li>Go to Entra Admin Center \u2192 App registrations \u2192 New registration</li> <li>Authentication blade:<ul> <li>Add platform \u2192 Mobile and desktop applications</li> <li>Redirect URI: <code>https://login.microsoftonline.com/common/oauth2/nativeclient</code></li> <li>Enable Allow public client flows = Yes</li> </ul> </li> <li>API permissions \u2192 Add delegated permissions:<ul> <li>Microsoft Graph: <code>User.Read.All</code>, <code>Directory.Read.All</code></li> <li>SharePoint: <code>AllSites.FullControl</code></li> </ul> </li> <li>Grant admin consent</li> </ol>"},{"location":"getting-started/authentication/#connect","title":"Connect","text":"<pre><code>Connect-SPCTenant -TenantName contoso -ClientId '&lt;your-app-client-id&gt;'\n</code></pre> <p>A browser window opens for sign-in. Use an account with SharePoint Admin or Site Collection Admin rights.</p>"},{"location":"getting-started/authentication/#method-b-apponly-certificate-automation-and-scheduled-tasks","title":"Method B \u2014 AppOnly / certificate (automation and scheduled tasks)","text":"<p>Requires an Entra app registration with a certificate credential.</p>"},{"location":"getting-started/authentication/#one-time-app-registration-setup_1","title":"One-time app registration setup","text":"<ol> <li>Go to Entra Admin Center \u2192 App registrations \u2192 New registration</li> <li>Certificates &amp; secrets \u2192 upload a <code>.pfx</code> or <code>.cer</code> certificate</li> <li>API permissions \u2192 Add application permissions:<ul> <li>Microsoft Graph: <code>User.Read.All</code>, <code>Directory.Read.All</code>, <code>Sites.FullControl.All</code></li> <li>SharePoint: <code>Sites.FullControl.All</code></li> </ul> </li> <li>Grant admin consent</li> </ol>"},{"location":"getting-started/authentication/#connect_1","title":"Connect","text":"<pre><code>$certPwd = Read-Host -AsSecureString 'Certificate password'\nConnect-SPCTenant -TenantName contoso `\n -AuthMethod AppOnly `\n -ClientId '&lt;your-app-client-id&gt;' `\n -CertificatePath C:\\certs\\spclean.pfx `\n -CertificatePassword $certPwd\n</code></pre>"},{"location":"getting-started/authentication/#method-c-apponly-client-secret","title":"Method C \u2014 AppOnly / client secret","text":"<pre><code>$secret = Read-Host -AsSecureString 'Client secret'\nConnect-SPCTenant -TenantName contoso `\n -AuthMethod AppOnly `\n -ClientId '&lt;your-app-client-id&gt;' `\n -ClientSecret $secret\n</code></pre> <p>Certificate preferred for automation</p> <p>Client secrets expire and must be rotated manually. Use certificate auth for scheduled tasks.</p>"},{"location":"getting-started/authentication/#disconnect","title":"Disconnect","text":"<pre><code>Disconnect-SPCTenant\n</code></pre> <p>Clears the module connection state and disconnects both PnP and Microsoft Graph sessions.</p>"},{"location":"getting-started/authentication/#permission-requirements","title":"Permission requirements","text":""},{"location":"getting-started/authentication/#apponly-automation","title":"AppOnly (automation)","text":"Permission Type API Purpose <code>Sites.FullControl.All</code> Application Microsoft Graph Read UIL, remove users <code>User.Read.All</code> Application Microsoft Graph Verify Entra account status <code>Directory.Read.All</code> Application Microsoft Graph Detect soft-deleted accounts <code>Sites.FullControl.All</code> Application SharePoint Per-site connections"},{"location":"getting-started/authentication/#interactive-manual","title":"Interactive (manual)","text":"Permission Type API Purpose <code>AllSites.FullControl</code> Delegated SharePoint PnP site connections <code>User.Read.All</code> Delegated Microsoft Graph Verify Entra account status <code>Directory.Read.All</code> Delegated Microsoft Graph Detect soft-deleted accounts <p>All permissions require admin consent.</p>"},{"location":"getting-started/authentication/#next-step","title":"Next step","text":"<p>Quick Start \u2192</p>"},{"location":"getting-started/installation/","title":"Installation","text":""},{"location":"getting-started/installation/#requirements","title":"Requirements","text":"Requirement Minimum version PowerShell 5.1 or 7+ PnP.PowerShell 2.0.0 (tested on 3.2.0) Microsoft.Graph.Authentication 2.0.0 (tested on 2.38.0) SharePoint Online role Site Collection Administrator (single site) or SharePoint Administrator (all sites) Entra ID role At least User Administrator (read-only scan)"},{"location":"getting-started/installation/#install-dependencies","title":"Install dependencies","text":"<pre><code>Install-Module PnP.PowerShell -Scope CurrentUser -Force\nInstall-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force\n</code></pre>"},{"location":"getting-started/installation/#install-spclean","title":"Install SPClean","text":"PSResourceGet (recommended)PowerShellGet (legacy) <pre><code>Install-PSResource -Name SPClean\n</code></pre> <pre><code>Install-Module -Name SPClean\n</code></pre>"},{"location":"getting-started/installation/#install-from-source","title":"Install from source","text":"<pre><code># Import directly from the cloned repo\nImport-Module .\\SPClean\\SPClean.psd1 -Force\n\n# Or copy to your module path for auto-load\nCopy-Item .\\SPClean -Destination \"$HOME\\Documents\\PowerShell\\Modules\\SPClean\" -Recurse\nImport-Module SPClean\n</code></pre>"},{"location":"getting-started/installation/#verify-the-import","title":"Verify the import","text":"<pre><code>Get-Command -Module SPClean\n</code></pre> <p>Expected cmdlets:</p> <pre><code>Connect-SPCTenant\nDisconnect-SPCTenant\nGet-SPCOrphanedUser\nExport-SPCReport\nRemove-SPCOrphanedUser\nRestore-SPCOrphanedUser\nNew-SPCScanSchedule\nRegister-SPCLicense\nGet-SPCLicenseInfo\n</code></pre>"},{"location":"getting-started/installation/#next-step","title":"Next step","text":"<p>Set up authentication \u2192</p>"},{"location":"getting-started/quickstart/","title":"Quick Start","text":"<p>A complete scan-report-remediate workflow in six steps.</p> <pre><code># 1. Connect\nConnect-SPCTenant -TenantName contoso -ClientId '&lt;app-id&gt;'\n\n# 2. Scan one site and view results\nGet-SPCOrphanedUser -SiteUrl 'https://contoso.sharepoint.com/sites/HR'\n\n# 3. Export to HTML report\nGet-SPCOrphanedUser -SiteUrl 'https://contoso.sharepoint.com/sites/HR' |\n Export-SPCReport -Format HTML -IncludeSummary\n\n# 4. Preview what would be removed (WhatIf \u2014 no changes made)\nGet-SPCOrphanedUser -SiteUrl 'https://contoso.sharepoint.com/sites/HR' |\n Remove-SPCOrphanedUser -WhatIf\n\n# 5. Remove HIGH-risk orphans with a snapshot backup\nGet-SPCOrphanedUser -SiteUrl 'https://contoso.sharepoint.com/sites/HR' |\n Where-Object RiskLevel -eq 'HIGH' |\n Remove-SPCOrphanedUser -CreateSnapshot -SnapshotPath C:\\Snapshots -Confirm\n\n# 6. Disconnect\nDisconnect-SPCTenant\n</code></pre> <p>WhatIf first</p> <p>Always run step 4 (<code>-WhatIf</code>) before step 5. It prints exactly what would be removed without making any changes.</p> <p>Pro license required for HTML reports</p> <p><code>Export-SPCReport -Format HTML</code> and <code>-CreateSnapshot</code> require a Pro or Consultant license. The Free tier supports CSV and JSON output. See Licensing.</p>"},{"location":"getting-started/quickstart/#scan-all-sites","title":"Scan all sites","text":"<pre><code># Scan every site collection in the tenant\nGet-SPCOrphanedUser -AllSites |\n Export-SPCReport -Format HTML -IncludeSummary -OutputPath C:\\reports\\orphans.html\n</code></pre>"},{"location":"getting-started/quickstart/#restore-after-accidental-removal","title":"Restore after accidental removal","text":"<p>If you removed a user and need to restore their permissions from the snapshot:</p> <pre><code>Connect-SPCTenant -TenantName contoso -ClientId '&lt;app-id&gt;'\nRestore-SPCOrphanedUser -SnapshotPath C:\\Snapshots\\user@contoso.com_20260622T120000Z.json\nDisconnect-SPCTenant\n</code></pre>"},{"location":"getting-started/quickstart/#next-step","title":"Next step","text":"<p>Explore the Cmdlet Reference \u2192</p>"}]}