Skip to content

Releases: luigilink/SPSUserSync

v1.3.4

Choose a tag to compare

@github-actions github-actions released this 10 Jul 08:30
f091a8e

SPSUserSync - Release Notes

[1.3.4] - 2026-07-10

This is a performance release for SPSyncUserProfile.ps1, with no change in
behaviour: the same profiles are created and updated, and the same JSON files and
HTML report are produced.

On a large (~100k-user) farm, a full profile reconciliation took over an hour. A
transcript analysis showed the time was spent almost entirely in the per-user loop,
and that the loop rebuilt the User Profile service context and UserProfileManager
on every single user — the dominant cost, and the cause of a steady throughput
decay over the run (object churn / GC pressure).

Changed

  • The UserProfileManager is now built once and reused for the whole run
    (#24), instead of being reconstructed for every user inside Add-SPSUserProfile.
    This removes the dominant per-user cost and the associated memory churn.
  • The per-user transcript output is condensed to a single status line
    [UPDATE] DOMAIN\user (changed: WorkEmail, Country), [CREATE], [INFO] or
    [UNKNOWN_USER] — replacing the previous ~14-line before/after dump. On a
    ~100k-user run this cuts a multi-million-line, tens-of-MB transcript down to one
    line per user and removes the corresponding synchronous I/O. (#24)

Added

  • End-of-run timing summary in SPSyncUserProfile.ps1: users processed, wall
    time, average milliseconds per user, and the CREATE / UPDATE / INFO /
    UNKNOWN_USER breakdown — so each run's performance is visible directly in the
    log. (#24)

Upgrade note

No configuration change is required and the output files are unchanged. Simply
deploy the new version; the next SPSyncUserProfile.ps1 run is faster and its log
is far smaller, ending with the new timing summary.

v1.3.3

Choose a tag to compare

@github-actions github-actions released this 09 Jul 18:41
dfcfb2e

SPSUserSync - Release Notes

[1.3.3] - 2026-07-09

This release adds AD account status detection so departed employees are handled
correctly, and it works for every customer because it relies only on universal
Active Directory signals — never on a customer-specific leaver process.

Two kinds of departed accounts are now distinguished and handled:

  • Disabled accounts (kept in AD, userAccountControl bit 0x2): detected and,
    with the new opt-in SkipDisabledUsers, reported instead of being given a profile.
  • Deleted accounts (no longer in AD): already not provisioned, now explicitly
    tagged so they can be told apart from an actionable configuration gap.

Added

  • Account status on every recordConvertTo-SPSUserRecord reads
    userAccountControl and exposes AccountStatus (Active / Disabled /
    NotFound) and Enabled; SPSyncUserInfoList.ps1 writes AccountStatus into the
    JSON snapshot. Universal by design: no dedicated OU, naming convention, HR feed or
    retention assumption. A resolved account with no userAccountControl (some non-AD
    LDAP directories) stays Active, exactly as before. (#21)
  • SkipDisabledUsers (sync-settings.psd1, default $false) — when $true,
    SPSyncUserProfile.ps1 reports Disabled accounts as Not Added instead of creating
    or updating their profile. Default preserves the previous behaviour. (#21)
  • NotAddedReason on each Not-Added entry — AD_NOT_FOUND, MISSING_ATTRIBUTES
    or DISABLED — with a per-reason breakdown in the run summary, so an expected miss
    (a departed account) is easy to tell apart from an actionable one (e.g. a forest
    missing from ad-domains.psd1). (#21)
  • The SPSyncUserInfoList HTML report gains an AD Status column and a
    Disabled in AD card (with a note pointing at SkipDisabledUsers), so disabled —
    departed-but-retained — accounts are visible before the profile sync runs. (#21)

Changed

  • CI: bump the GitHub Actions that ran on the deprecated Node.js 20 runtime —
    actions/checkout v4v7, actions/upload-artifact v4v7,
    softprops/action-gh-release v2v3. CI-only; the packaged module and scripts
    are unchanged. (#22)

Upgrade note

SkipDisabledUsers acts on the AccountStatus written by SPSyncUserInfoList.ps1
1.3.3+. Regenerate the JSON snapshot after upgrading for the flag to take effect;
a pre-1.3.3 snapshot carries no status, so no user is skipped as disabled until then.
There is no behaviour change for a correctly-deployed farm when SkipDisabledUsers
is left at its default $false.

v1.3.2

Choose a tag to compare

@github-actions github-actions released this 09 Jul 13:17
397feac

SPSUserSync - Release Notes

[1.3.2] - 2026-07-08

This is a hardening release that makes a broken or mis-deployed secrets.psd1
fail fast and loud instead of silently degrading the result. It follows a
field case where a secrets.psd1 that could not be decoded on the User Profile
master server (DPAPI SecureStrings are bound to the machine and account that
created them) caused thousands of real users to be skipped as UNKNOWN_USER,
with the only trace being one Event-Log line per user.

There is no behaviour change for a correctly-deployed farm; the generated JSON
and the profile updates are unchanged.

Fixed

  • AD configuration/secret errors are no longer mistaken for "user absent"
    (#18). Get-SPSADConnection and Get-SPSADUser now distinguish a build-time
    misconfiguration (missing LdapPath, missing CredentialKey, or an
    undecodable/missing secret) — which throws a terminating SPSADConfigError
    from a genuine lookup miss, which still returns $null. Previously every
    failure, including an undecodable DPAPI secret, was logged only to the Event
    Log and returned as $null, so a broken forest silently produced empty-name
    records and, on the profile side, UNKNOWN_USER downgrades.
  • SPSyncUserProfile.ps1 pre-flights the AD configuration (#18) once per
    credential-mode forest present in the input JSON, before the user loop, and
    stops with Exit 1 and an actionable message when a forest's secret cannot be
    decoded on this server — instead of silently skipping every affected user.
  • SPSyncUserInfoList.ps1 fails the run loudly (#18) when a forest cannot be
    resolved because of a configuration/secret error, naming the affected
    forest(s), rather than writing a JSON with a whole forest blanked out.

Added

  • AuthenticationType per domain in ad-domains.psd1 (#20) — maps to
    System.DirectoryServices.AuthenticationTypes and is passed as the LDAP bind
    type, so a non-Active-Directory directory can be configured entirely from the
    file: 'None' for a plain simple bind, 'SecureSocketsLayer' for LDAPS on
    port 636, and so on. It defaults to 'Secure' (integrated Kerberos/NTLM),
    unchanged for existing AD forests, and is now honoured on Default-mode
    domains too (previously only Credential-mode). The value is case-insensitive
    and may combine flags (e.g. 'SecureSocketsLayer, ServerBind'); an unknown
    value fails the run with the list of valid names instead of silently falling
    back. ad-domains.example.psd1 gains a documented non-AD directory example.
  • Get-SPSADConnectionError (#18) — a fast, query-free pre-flight that decodes
    each referenced forest's secret (without issuing an LDAP search) and returns
    the forests that fail. It backs the new SPSyncUserProfile.ps1 pre-flight and
    is reusable in your own checks.

Changed

  • Connectivity errors stay non-fatal (#18). An LDAP server that is not
    operational
    or returns a referral (for example an external directory
    reachable from an application farm but not from the UPA master) is deliberately
    not treated as a configuration error: the affected login is logged and left
    unresolved so the rest of the run continues, and Test-SPSUserSyncReadiness.ps1
    flags the forest so you can fix the LDAP path/routing. Only deterministic,
    fixable configuration/secret errors abort the run.

Upgrade notes

Drop-in replacement for 1.3.1 — no configuration change required. Before enabling
the scheduled tasks, run Test-SPSUserSyncReadiness.ps1 on each server
(application farms and the UPA master), signed in as the service account, to
confirm every forest's secret decodes and binds. With 1.3.2 a forest whose secret
cannot be decoded now stops the run explicitly (exit code 1) instead of silently
producing empty profiles.

v1.3.1

Choose a tag to compare

@github-actions github-actions released this 08 Jul 12:39
2096eeb

SPSUserSync - Release Notes

[1.3.1] - 2026-07-08

This is a hardening release for SPSyncUserInfoList.ps1. It does not change the
generated JSON for a correctly-permissioned account, but it makes a wrong
service account
(or a missing Shell Admin) fail loudly and early instead of
silently. It follows a field case where the script was launched with an
under-privileged account and produced no output and no visible error at all.

Fixed

  • No more silent ACCESS_DENIED runs (#16). Get-SPSite -Limit All no longer
    uses -ErrorAction SilentlyContinue. When the running account cannot enumerate
    the farm site collections, the ACCESS_DENIED (E_ACCESSDENIED 0x80070005) is
    now surfaced to the console/transcript as well as the Windows Event Log,
    with an actionable message pointing at the Shell Admin / correct service
    account. Previously the error reached only the Event Log, so the operator saw an
    almost-empty screen while the script kept running and then emitted two confusing
    secondary errors on a JSON file that was never written.
  • No more overwriting/copying an empty snapshot (#16). When zero users are
    collected — almost always a rights problem rather than a genuinely empty farm —
    the previous good SPSyncUserInfoListUserList.json is left untouched, an
    explicit error is raised, and the HTML report and the remote copy are skipped
    (the script exits 1) instead of pushing empty or stale data to the User
    Profile farm.

Added

  • Readiness site-collection enumeration check (#16). Test-SPSUserSyncReadiness.ps1
    now walks Get-SPSite -Limit All and FAILs on ACCESS_DENIED, pointing at
    the Shell Admin / service account prerequisite. This is the exact permission
    SPSyncUserInfoList.ps1 depends on, and it plugs the gap left by the previous
    Get-SPFarm-only check (which only proves config-database access, while the
    real run reads every content database). Run the readiness check as the service
    account before enabling the scheduled task to catch a wrong account up front.
  • Regression tests (#16) for Get-SPSUniqueUsers: healthy farm writes the JSON
    and reports success; zero users writes no JSON and raises an Error event;
    ACCESS_DENIED surfaces an actionable Error and writes no JSON.

Upgrade notes

Drop-in replacement for 1.3.0 — no configuration change required. If you rely on a
scheduled task, make sure the account it runs under is a Shell Admin on every
content database (Add-SPShellAdmin); with 1.3.1 a wrong account now fails the run
explicitly (exit code 1) instead of silently producing nothing.

v1.3.0

Choose a tag to compare

@github-actions github-actions released this 28 Jun 16:18
748fd78

SPSUserSync - Release Notes

[1.3.0] - 2026-06-28

This release adds optional parallel Active Directory resolution to
SPSyncUserInfoList.ps1, for large multi-forest farms where the per-user LDAP
round-trip dominates the runtime. It is opt-in and off by default, and the
generated JSON is byte-for-byte identical whether parallel resolution is on or
off (verified on a SharePoint Subscription Edition farm).

Added

  • Parallel AD resolution (#14). With ParallelADResolution = $true (new
    setting, default $false), the unique user logins are resolved against AD
    concurrently through a RunspacePool — Windows PowerShell 5.1 compatible, no
    ForEach-Object -Parallel required. MaxParallelADQueries sets the degree of
    parallelism (0 = auto from the CPU count). Off by default because on small
    farms the per-runspace module-import overhead is not amortized. Two new public
    helpers back it: Resolve-SPSADUserBatch (the RunspacePool resolver) and
    Get-SPSThrottleLimit (CPU-based default). A measured ~8x speedup on a 40-user
    mock with 100 ms LDAP latency.
  • ConvertTo-SPSUserRecord (#14) — the single Get-SPSADUser -> record
    projection shared by both the sequential path and the parallel worker, which is
    what guarantees the identical JSON.

Changed

  • SPSyncUserInfoList.ps1 now resolves each unique login against AD exactly
    once (previously once per web the user appeared in), by separating the
    user-collection, AD-resolution and JSON-building passes. This also speeds up
    the default sequential mode on farms where users span many sites. The
    user-removal walk (Set-SPUser / Remove-SPUser) is unchanged and still runs
    per web. (#14)

Upgrade notes

  • No action required: ParallelADResolution and MaxParallelADQueries are
    optional and default to the previous (sequential) behavior. Add them to
    sync-settings.psd1 only when you want to enable parallel resolution.

A full list of changes in each version can be found in the change log

v1.2.1

Choose a tag to compare

@github-actions github-actions released this 28 Jun 15:23
0c4ca29

SPSUserSync - Release Notes

[1.2.1] - 2026-06-28

A small follow-up to 1.2.0, with one important fix to the User Profile
reconciliation script plus a documentation/readiness improvement around UPA
permissions.

Fixed

  • SPSyncUserProfile.ps1 processed no eligible users at all — the run
    reported "N users do not meet Prerequisites", wrote no
    SPSyncUserAddedInUSPList file and logged no error, so it looked like a silent
    no-op. Add-SPSUserProfile's mandatory -ResultCollection parameter rejected
    the empty ArrayList on the first loop iteration, and a script-scoped
    Trap { Continue } swallowed the terminating error and abandoned the whole
    batch. Added [AllowEmptyCollection()], wrapped the per-user call in
    try/catch (one failing user is logged and the batch continues), and removed
    the misleading Trap. (#13)

Added

  • The readiness check (Test-SPSUserSyncReadiness.ps1) now verifies the current
    account can read the User Profile Service Application — a non-destructive
    profile-count read via UserProfileManager, on the UPA master only. This
    catches the missing Manage Profiles permission that otherwise makes
    SPSyncUserProfile.ps1 fail at runtime with "ProfileDBCacheServiceClient.GetUserData
    threw exception: Access is denied."
    PASS reports the profile count; WARN points
    to the permission / farm-account prerequisite when the read is denied. (#11)

Changed

  • The prerequisites now document that the account running SPSyncUserProfile.ps1
    must be able to manage profiles on the UPA — either the farm account or an
    account granted Administrator of the UPA with the Manage Profiles
    permission. (#10)

A full list of changes in each version can be found in the change log

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 28 Jun 14:09
925970d

SPSUserSync - Release Notes

[1.2.0] - 2026-06-28

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, and makes user removal opt-in so a sync
never prunes accounts from a live farm unless you ask it to.

Behavior change — user removal is now opt-in. On a claims-based farm,
earlier versions removed classic-format and system principals (e.g.
NT AUTHORITY\authenticated users) when Set-SPUser -SyncFromAD could not
resolve them. SPSUserSync now reports those users and leaves them in place by
default. Set RemoveUnresolvableUsers = $true in sync-settings.psd1 to
restore the previous pruning behavior.

Added

  • SharePoint command surface, edition-aware (#6) — Import-SPSSharePointCommand
    loads the snap-in on SharePoint 2013/2016/2019 and the SharePointServer module
    on Subscription Edition (which no longer ships the snap-in), so both scripts run
    from a plain powershell.exe (a scheduled task) instead of requiring the
    SharePoint Management Shell. Get-SPSInstalledProductVersion backs the detection.
  • Pre-flight readiness check (#7) — Test-SPSUserSyncReadiness.ps1 validates
    Host, 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-SPSADConnection backs the AD section (binds, reads one
    user, reports which key attributes are populated); -SkipNetwork /
    -SkipSharePoint allow a config-only pass from a workstation.
  • Unresolved-user audit in the HTML report (#5) — rows whose identity did not
    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 UserProfile
    report highlights UNKNOWN_USER rows the same way.
  • RemoveUnresolvableUsers setting (default $false) in
    sync-settings.example.psd1 (#4).

Changed

  • User removal is opt-in (#4) — see the behavior change above. The classic
    system principals NT AUTHORITY\*, BUILTIN\* and SHAREPOINT\* are now always
    excluded from processing, so they are never written to the JSON nor removed
    (previously only their claims forms were excluded).
  • Both SPSyncUserInfoList.ps1 and SPSyncUserProfile.ps1 load the SharePoint
    command surface via Import-SPSSharePointCommand at startup (#6).
  • The release ZIP now contains the contents of src/ at its root, so the
    archive extracts straight into the deployment folder with no manual move (#8).

Fixed

  • The Logs folder (transcript, rotation logs, deleted-user snapshots, HTML
    reports) is written next to the calling script again, instead of inside the
    module folder where it was wiped on every module redeploy (#3).

A full list of changes in each version can be found in the change log

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 26 Jun 13:25
073dba8

SPSUserSync - Release Notes

[1.1.0] - 2026-06-26

Added

  • JSON snapshot history and anomaly detection (SPSyncUserInfoList.ps1):
    • Backup-SPSJsonFile — archives the previous SPSyncUserInfoListUserList.json to Logs\history\ with a timestamp before each regeneration.
    • Compare-SPSJsonSnapshots — pure function returning CurrentCount, PreviousCount, Delta, DropPercent, ThresholdPercent, IsAnomalous.
    • The script now archives the previous snapshot, regenerates, then raises a Warning in the SPSUserSync Event Log when the user count drops by at least JsonDropThresholdPercent (helps catch an unreachable AD forest or a bad exclusion before the UPA reconciliation runs).
    • History snapshots are rotated using the existing Clear-SPSLogFolder with -Extension '*.json'.
  • New settings in sync-settings.example.psd1 (backward-compatible defaults applied when absent):
    • JsonHistoryRetentionDays (default 90)
    • JsonDropThresholdPercent (default 20)
    • GenerateHtmlReport (default $true)
  • Self-contained HTML reporting:
    • Export-SPSUserReport — generates a single dependency-free HTML report (no CDN, works offline) for either dataset via -ReportType UserInfoList|UserProfile. Summary cards plus an interactive table (live search, column sort, pagination) rendered by embedded vanilla JavaScript. All AD-sourced values are HTML-encoded and rendered via textContent, so names/emails cannot inject markup.
    • SPSyncUserInfoList.ps1 writes SPSyncUserInfoListReport-*.html (total users, email coverage, top countries, top AD domains).
    • SPSyncUserProfile.ps1 writes SPSyncUserProfileReport-*.html (counts by Status: CREATE / UPDATE / INFO / UNKNOWN_USER).
    • Reports are rotated with the existing Clear-SPSLogFolder using -Extension '*.html', and can be disabled by setting GenerateHtmlReport = $false.
  • Pester test suite under tests/ (39 tests, cross-platform):
    • SPSUserSync.Common.Tests.ps1 — module import, manifest validity, public/private surface, parameter contracts
    • Compare-SPSJsonSnapshots.Tests.ps1 — drop detection, threshold edges, growth, empty-previous
    • Backup-SPSJsonFile.Tests.ps1 — timestamped copy, copy-not-move, folder creation, missing source, content preservation
    • Export-SPSUserReport.Tests.ps1 — both report types, empty dataset, and an HTML-injection safety test
    • Private.Tests.ps1ConvertFrom-SPSUserLogin, ConvertTo-SPSHtmlEncoded, Get-SPSJsonRecordCount via InModuleScope
  • .github/workflows/pester.yml — runs Pester and PSScriptAnalyzer on pull requests touching src/, tests/ or the analyzer settings.
  • PSScriptAnalyzerSettings.psd1 — analyzer configuration (Error+Warning, with PSUseSingularNouns disabled for the deliberately plural Compare-SPSJsonSnapshots / Get-SPSUniqueUsers).
  • .gitattributes and .editorconfig — encode the project's text conventions.

Changed

  • All PowerShell files (*.ps1, *.psm1, *.psd1) are now stored as UTF-8 with BOM and checked out with CRLF, so Windows PowerShell 5.1 reads any non-ASCII content correctly instead of falling back to the ANSI code page. YAML, Markdown and JSON keep LF and no BOM.
  • Renamed two private report helpers to non-state-changing verbs (New-SPSReportCard -> Get-SPSReportCardHtml, New-SPSReportTopList -> Get-SPSReportTopListHtml) to satisfy PSScriptAnalyzer.

A full list of changes in each version can be found in the change log

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 26 Jun 11:00

SPSUserSync - Release Notes

[1.0.0] - 2026-06-26

Added

  • README.md
    • Add code_of_conduct.md badge
  • Add CODE_OF_CONDUCT.md file
  • Add Issue Templates files:
    • 1_bug_report.yml
    • 2_feature_request.yml
    • 3_documentation_request.yml
    • 4_improvement_request.yml
    • config.yml
  • Add RELEASE-NOTES.md file
  • Add CHANGELOG.md file
  • Add CONTRIBUTING.md file
  • Add SECURITY.md file
  • Wiki documentation (wiki/Home.md, wiki/Getting-Started.md, wiki/Configuration.md, wiki/Usage.md, wiki/Release-Process.md, wiki/_Sidebar.md) auto-synced to the GitHub Wiki by the existing wiki.yml workflow. The _Sidebar.md renders the navigation on every wiki page; Release-Process.md documents the maintainer checklist for shipping a new version.

Changed

  • .gitignore
    • Add patterns for runtime logs and local configuration files
    • Prevent accidental commit of credentials and secrets
  • .github/ISSUE_TEMPLATE/1_bug_report.yml
    • Align version and PowerShell dropdown options with the project
  • README.md
    • Add Requirements section (SharePoint Server 2016/2019/SE, PowerShell 5.1, Farm Admin, AD reachability, farm property bags)
  • CODE_OF_CONDUCT.md
    • Set the enforcement contact to the project maintainer's GitHub profile (@luigilink)
  • README.md
    • Remove obsolete reference to farm property bags (now in config/sync-settings.psd1)

Added

  • Configuration scaffolding under src/config/ (ad-domains, secrets, sync-settings — .example.psd1 only is versioned)
  • PowerShell module src/Modules/SPSUserSync.Common/ with manifest, loader, and 7 public functions: Add-SPSUserSyncEvent (dedicated SPSUserSync Windows Event Log), Clear-SPSLogFolder (log rotation), Get-SPSADConnection (DirectorySearcher per domain), Get-SPSADUser / Test-SPSADUser (SP-to-AD lookup), Get-SPSSyncSetting (settings loader), Initialize-SPSScript (admin check, transcript, banner). Private helpers cover login parsing, config loading and SecureString DPAPI secret decryption.

Changed

  • src/SPSyncUserInfoList.ps1 refactored to consume the new module:
    • All 19 AD domains, bind credentials, master VM names, MySite URLs, user-exclusion patterns and claim prefix moved out of code into src/config/*.psd1
    • JSON output is now UTF-8 (fixes accents corruption)
    • Errors funnel into the SPSUserSync Event Log instead of *_errlog.xml dumps
  • src/SPSyncUserProfile.ps1 refactored to consume the new module:
    • Same module-driven cleanup as SPSyncUserInfoList.ps1
    • Reads the input JSON explicitly as UTF-8
    • UPA log retention configurable via UpaLogRetentionDays
  • Both scripts no longer carry their own version string. The single source of truth is the ModuleVersion field of SPSUserSync.Common.psd1; Initialize-SPSScript auto-detects it.
  • The SPSUserSync Event Log header records the SPSUserSync version and the calling script name on dedicated lines so operators can filter events by version or script directly from Event Viewer or SCOM.

Added

  • .github/workflows/release.yml — automated release: packages src/ and publishes a GitHub Release with RELEASE-NOTES.md as body, triggered by pushing a v* tag.

Changed (post-commit polish)

  • README.md slimmed down to badges + short description + Quick links. Detailed Requirements / Usage / Configuration content moved to the Wiki.
  • README.md: fix Code of Conduct badge link casing (CODE_OF_CONDUCT.md).
  • SECURITY.md: replace the obsolete APP_CODE / ENV_NAME farm property bag reference with a broader description of the values that must never be committed.

A full list of changes in each version can be found in the change log