SPSUserSync: v1.2.0 — Subscription Edition support, readiness check, opt-in user removal - #9
Merged
Conversation
…odule Initialize-SPSScript resolved its default Logs folder with `Get-Variable -Name MyInvocation -Scope 1`. Because module functions run in the module session state, that lookup never reached the caller script and resolved to the module directory instead, so transcripts, rotation logs, deleted-user snapshots and HTML reports were written under `Modules\SPSUserSync.Common\Logs` — where they are wiped on every module redeploy. Initialize-SPSScript now takes an explicit `-ScriptRoot` parameter, and both entry-point scripts pass their `$PSScriptRoot`. When `-ScriptRoot`/`-LogFolder` are omitted, the function falls back to a `Get-PSCallStack` walk that skips frames inside the module directory, which correctly crosses the module boundary. - Initialize-SPSScript.ps1: add -ScriptRoot param, robust call-stack fallback, updated comment-based help. - SPSyncUserInfoList.ps1 / SPSyncUserProfile.ps1: pass -ScriptRoot $PSScriptRoot. Validated on macOS: PSScriptAnalyzer clean, Pester 43/43, module imports 13 functions, -ScriptRoot resolves via Get-Command. Fixes #3
…check Make the toolkit deployable and verifiable on a real SharePoint server, including Subscription Edition, without the SharePoint Management Shell. SharePoint command surface (#6) - Get-SPSInstalledProductVersion: reads the installed SharePoint version from Microsoft.SharePoint.dll, returning $null silently when SharePoint is absent. - Import-SPSSharePointCommand: edition-aware loader. Add-PSSnapin Microsoft.SharePoint.PowerShell on 2013/2016/2019; Import-Module SharePointServer on Subscription Edition (which no longer ships the snap-in). Idempotent. Both entry-point scripts call it at startup, so they run from a plain powershell.exe (a scheduled task) instead of requiring the Management Shell. Pre-flight readiness (#7) - Test-SPSADConnection: proves an AD domain is usable, not just that a searcher can be built. Binds (validating a Credential domain's stored secret actually works), reads one user (generic "any user" probe or a specific -SampleAccount), and reports which attributes SPSUserSync relies on (givenName, sn, mail, co, l, displayName) are populated. Returns a result object; never throws on a failed bind. - Test-SPSUserSyncReadiness.ps1: operator-facing, read-only, non-destructive pre-flight check (Host, Module, Config, Secrets, AD, SharePoint, Event Log, Share) with colored PASS/WARN/FAIL/SKIP output and exit code 0/1. It reuses Get-SPSInstalledProductVersion + Import-SPSSharePointCommand for the edition-aware SharePoint section and Test-SPSADConnection for the AD section, hence shipping together with them. Switches -SkipNetwork / -SkipSharePoint allow a config-only pass from a workstation. - SPSUserSync.Common.psd1: export the three new public functions (now 13). - tests/SPSUserSync.Common.Tests.ps1: expected-exports list + contracts for the three functions. - wiki/Getting-Started.md: a "Verify readiness" step before the first run. Validated on macOS: PSScriptAnalyzer clean, Pester 49/49, module imports 13 functions. Fixes #6 Fixes #7
…pals On a claims-based farm, classic-format principals never resolve via Set-SPUser -SyncFromAD, so the legacy cleanup removed them with Remove-SPUser on the live farm. A first real run pruned 18 entries, including 'NT AUTHORITY\authenticated users' and the classic 'DOMAIN\user' duplicate of a claims account. Two changes make this safe by default: - RemoveUnresolvableUsers setting (default $false) now gates the Remove-SPUser cleanup in SPSyncUserInfoList.ps1. When disabled, an unresolvable user is reported and left in place and only the benign Set-SPUser -SyncFromAD refresh runs. Set it to $true to restore the previous pruning behavior. - The classic system principals 'NT AUTHORITY\*', 'BUILTIN\*' and 'SHAREPOINT\*' are now always excluded from processing, in addition to the configured ExcludedUserLogins / ExcludedUserLoginPatterns. Previously only the claims forms (e.g. c:0!.s|windows) were excluded, so the classic forms slipped through and were pruned. Test-SPSUserSyncReadiness.ps1 surfaces RemoveUnresolvableUsers explicitly: WARN when enabled (destructive), PASS when disabled (safe default). - SPSyncUserInfoList.ps1: built-in exclusions, RemoveUnresolvableUsers gate. - config/sync-settings.example.psd1: documented RemoveUnresolvableUsers + the built-in system-principal exclusions. The readiness script's matching RemoveUnresolvableUsers safety check ships with Test-SPSUserSyncReadiness.ps1 in the readiness commit. Validated on macOS: PSScriptAnalyzer clean, Pester 43/43, example config parses with RemoveUnresolvableUsers = $false. Fixes #4
…udit) The report now makes it obvious which users did not resolve from AD - exactly the accounts that will not sync to the user profile and that Remove-SPUser would prune when RemoveUnresolvableUsers is enabled. UserInfoList report: - A row is "unresolved" when it has no display name, or its display name equals the de-claimed login (the signature of a failed Set-SPUser -SyncFromAD, e.g. both 'ZEBES\jcdrouhin' and 'i:0#.w|zebes\jcdrouhin' whose display name is just 'ZEBES\jcdrouhin'). The comparison strips the claim prefix and is case-insensitive, so it catches both the classic and claims forms. - Unresolved rows are highlighted in amber with a gold left accent. - A new "Unresolved" summary card (amber when > 0) shows the count. - A legend above the table explains the highlight and points to RemoveUnresolvableUsers. UserProfile report: - UNKNOWN_USER rows are highlighted the same way (amber card + rows + legend), reusing the shared per-row flag mechanism. Implementation: - Detection runs in PowerShell (testable); flagged rows carry a "_flag" field in the embedded JSON. The table JS adds that value as the row CSS class; the field is not a column, so it is never rendered as a cell, searched or sorted. - Get-SPSReportCardHtml gained an optional -Tone 'warn' for the warning palette. - All values stay HTML-encoded / textContent-rendered; the XSS-safety test is unchanged and still passes. Tests: Export-SPSUserReport.Tests.ps1 extended (resolved set emits no flag; unresolved set yields an amber card, a "_flag":"unresolved" payload, the count including a de-claimed match, and the legend; UserProfile flags UNKNOWN_USER). Full suite 49/49, PSScriptAnalyzer clean. - Export-SPSUserReport.ps1: unresolved detection, Unresolved card, legend, indexed row build with _flag; UNKNOWN_USER flagging for UserProfile. - Get-SPSReportAssets.ps1: -Tone param, .card.warn and tr.unresolved CSS, JS row class from _flag. Fixes #5
The release workflow zipped the src/ folder itself, so the archive contained a top-level src/ wrapper and deploying meant extracting and then manually moving config/, Modules/ and the scripts up one level into the deployment folder. Zip the contents of src/ instead (cd src && zip -r ../$zip_name .), so the archive extracts straight into the deployment folder with no manual move. - .github/workflows/release.yml: zip src contents; updated header comment. Fixes #8
Document under [Unreleased] the SharePoint SE command loader, the AD connection test, the pre-flight readiness check, the opt-in user removal with built-in system-principal exclusions, the log-folder fix, the unresolved-user report audit and the release-ZIP packaging change. Each entry references its issue. Refs #3, #4, #5, #6, #7, #8
Bump the module manifest to 1.2.0, date the CHANGELOG [1.2.0] section, and rewrite RELEASE-NOTES.md (the GitHub Release body) for this version. Highlights: SharePoint Subscription Edition support without the Management Shell, a pre-flight readiness check, an unresolved-user audit in the HTML report, and opt-in user removal (the sync no longer prunes accounts from a live farm by default). Refs #3, #4, #5, #6, #7, #8
Pester Test Results49 tests 49 ✅ 1s ⏱️ Results for commit 94022ef. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request (PR) description
Ships SPSUserSync v1.2.0. This release makes the toolkit deployable and
verifiable on a real SharePoint server — including Subscription Edition —
without the SharePoint Management Shell, adds a pre-flight readiness check, an
unresolved-user audit in the HTML report, and makes user removal opt-in so a
sync never prunes accounts from a live farm unless explicitly asked.
Added
Import-SPSSharePointCommandloads the snap-in on SharePoint 2013/2016/2019 and the
SharePointServermoduleon Subscription Edition (which no longer ships the snap-in), so both scripts run
from a plain
powershell.exe(a scheduled task).Get-SPSInstalledProductVersionbacks the detection.
Test-SPSUserSyncReadiness.ps1validatesHost, Module, Config, Secrets, AD, SharePoint, the Event Log and the master-VM
share, with colored PASS/WARN/FAIL/SKIP output and a 0/1 exit code. Read-only and
non-destructive.
Test-SPSADConnectionbacks the AD section.-SkipNetwork/-SkipSharePointallow a config-only pass from a workstation.resolve from AD (no display name, or a display name equal to the de-claimed
login) are highlighted in amber, counted in a new Unresolved card, and
explained by a legend pointing to
RemoveUnresolvableUsers. The UserProfilereport highlights
UNKNOWN_USERrows the same way.RemoveUnresolvableUserssetting (default$false) insync-settings.example.psd1([Bug]: Classic and system accounts are removed on claims-based farms during AD sync #4).Changed
system principals
NT AUTHORITY\*,BUILTIN\*andSHAREPOINT\*are now alwaysexcluded from processing (previously only their claims forms were).
Import-SPSSharePointCommandat startup ([Bug]: Scripts fail on SharePoint Subscription Edition (require the Management Shell snap-in) #6).src/at its root, so thearchive extracts straight into the deployment folder with no manual move ([Bug]: Release ZIP wraps a src/ folder, requiring a manual move on deploy #8).
Fixed
Logsfolder (transcript, rotation logs, deleted-user snapshots, HTMLreports) is written next to the calling script again, instead of inside the
module folder where it was wiped on every module redeploy ([Bug]: Logs and reports are written inside the module folder instead of next to the script #3).
Validation (local, macOS + PowerShell 7)
Invoke-Pester ./tests→ 49/49 passedInvoke-ScriptAnalyzer ./src -Recurse -Settings ./PSScriptAnalyzerSettings.psd1→ cleansrc/contents at theroot (no wrapper),
config/carries only*.example.psd1, packaged moduleimports as 1.2.0 with 13 exports, unresolved-user flag renders.
SPSyncUserInfoList.ps1on a SharePoint Subscription Editionfarm (SharePointServer module loaded, JSON + HTML report generated, copied to the
master VM).
This Pull Request (PR) fixes the following issues
Task list
file CHANGELOG.md. Entry should say what was changed and how that
affects users (if applicable), and reference the issue being resolved
(if applicable).