Skip to content

Release v4.0.0 - src/ layout, SPSUpdate.Common module, psd1 config + DPAPI secrets#18

Merged
luigilink merged 13 commits into
mainfrom
release/4.0.0
Jun 29, 2026
Merged

Release v4.0.0 - src/ layout, SPSUpdate.Common module, psd1 config + DPAPI secrets#18
luigilink merged 13 commits into
mainfrom
release/4.0.0

Conversation

@luigilink

Copy link
Copy Markdown
Owner

Closes #17.

Major modernization release aligning SPSUpdate with SPSWeather / SPSUserSync. Breaking (package layout, config format, credential storage).

Highlights

  • src/ layout + new SPSUpdate.Common module (Public/ + Private/, one function per file, manifest version 4.0.0).
  • DPAPI secret store (Get-SPSSecret / Set-SPSSecret, Config\secrets.psd1) replacing the bundled credentialmanager module.
  • Config JSON -> psd1 with StoredCredential -> CredentialKey; runtime/output files stay JSON. Single documented CONTOSO-PROD.example.psd1 with safe defaults.
  • CredSSP hardening in Invoke-SPSCommand (fail fast, no silent local fallback, OpenTimeout).
  • ContentDB inventory primed only in Default mode; Add-SPSScheduledTask no longer dumps the RegisteredTask COM object.
  • CI: release.yml zips the contents of src/; pester.yml targets src/. Cross-platform Pester suite.
  • Slim README + rewritten/extended wiki (_Sidebar.md, Release-Process) + restored offline install guide.

Validation

  • PSScriptAnalyzer clean; 103 Pester tests passing (1 DPAPI test Windows-only, skipped on CI runners without it).
  • Verified on a real 3-server Subscription Edition farm: ProductUpdate (idempotent), full Default run with the four parallel sequences and per-server patch-status checks, and the DPAPI secret read end-to-end.

See RELEASE-NOTES.md for the full notes and the 3.x -> 4.0.0 migration steps.

luigilink and others added 11 commits June 29, 2026 14:30
…gs, gitignore)

Align SPSUpdate tooling with SPSWeather/SPSUserSync:
- .editorconfig: UTF-8 (BOM+CRLF for ps1/psm1/psd1), LF elsewhere
- .gitattributes: enforce encoding/line-ending policy for WinPS 5.1
- PSScriptAnalyzerSettings.psd1: Error/Warning severity baseline
- .gitignore: ignore secrets.psd1, real src/Config/*.psd1, logs and
  generated *-ContentDBs.json runtime output

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce src/Modules/SPSUpdate.Common with a manifest (v4.0.0) and a
dot-sourcing loader, splitting the former util.psm1 + sps.util.psm1 into
one file per function:

Public (17): Add-SPSScheduledTask, Add-SPSUpdateEvent,
Copy-SPSSideBySideFilesRemote, Get-SPSInstalledProductVersion,
Get-SPSSecret, Get-SPSServersPatchStatus, Initialize-SPSContentDbJsonFile,
Mount-SPSContentDatabase, Remove-SPSScheduledTask, Set-SPSSecret,
Set-SPSSideBySideToken, Start-SPSConfigExe, Start-SPSConfigExeRemote,
Start-SPSProductUpdate, Start-SPSScheduledTask, Test-SPSPendingReboot,
Update-SPSContentDatabase.

Private: Invoke-SPSCommand, Clear-ComObject, Get-SPSLocalVersionInfo,
Get-SPSConfigRoot.

Add-SPSUpdateEvent is now self-contained (version/user resolved from the
module instead of script-scope variables) and re-points a mismapped event
source to the SPSUpdate log. Get-SPSSecret/Set-SPSSecret add the DPAPI
secrets.psd1 store. PSScriptAnalyzer clean; module imports with 17
functions + 1 alias.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the per-farm JSON configs with a single documented psd1 template
(src/Config/CONTOSO-PROD.example.psd1) that lists every option with its
possible values inline. Add secrets.example.psd1 for the DPAPI credential
store. Rename the StoredCredential key to CredentialKey to match the new
Get-SPSSecret/Set-SPSSecret model.

Runtime/output files (ContentDB inventory, logs) stay JSON by design;
only the hand-edited environment config moves to psd1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move entry script to src/SPSUpdate.ps1 and import SPSUpdate.Common from
  src/Modules instead of the flat util.psm1/sps.util.psm1 helpers.
- Read the environment config with Import-PowerShellDataFile (*.psd1);
  validate required keys and apply safe defaults for optional behaviour
  keys (ProductUpdate/ShutdownServices/UpgradeContentDatabase default to
  $true, MountContentDatabase/SideBySide default to off).
- Replace Windows Credential Manager (Get/New/Remove-StoredCredential)
  with the DPAPI secret store: Install writes via Set-SPSSecret, Uninstall
  removes it, Default/sequence runs read via Get-SPSSecret. CredentialKey
  replaces StoredCredential.
- Derive the version banner from the module manifest.
- Delete the legacy scripts/ tree, including the bundled credentialmanager
  binaries and the old JSON configs.

Parse OK and PSScriptAnalyzer clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the legacy scripts/JSON-oriented tests with a cross-platform suite:
- SPSUpdate.Common.Tests.ps1: manifest validity, exact public export set,
  private helpers hidden, alias export, one-function-per-file, parse and
  UTF-8 BOM conventions, ShouldProcess/mandatory parameter contracts,
  Add-SPSUpdateEvent self-heal, and a DPAPI secret round-trip (Windows-only).
- Configuration.Tests.ps1: example psd1 config and secrets parse, required
  keys present, StoredCredential removed, PASTE placeholder preserved.

103 passed / 0 failed / 1 skipped (DPAPI test is Windows-only) on pwsh 7.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- release.yml: zip the *contents* of src/ (cd src && zip -r) so the
  archive extracts to Config/, Modules/, SPSUpdate.ps1 with no src/ wrapper,
  and add contents:write permission.
- pester.yml: trigger on src/**, tests/**, PSScriptAnalyzerSettings.psd1;
  run PSScriptAnalyzer against src/SPSUpdate.ps1 and the SPSUpdate.Common
  module; grant checks/pull-requests write for the results publisher.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- README.md: trim to a short overview + quick links to the wiki, matching
  the SPSWeather/SPSUserSync style.
- wiki/: rewrite Home, Getting-Started, Configuration and Usage for the
  src/ layout, psd1 config (CredentialKey), DPAPI secret store and the
  SPSUpdate.Common module; add a Release-Process page and a _Sidebar.md
  navigation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align with the SPSWeather CredSSP improvements: open the remote session
with -ErrorAction Stop inside try/catch and always attach it, so a failed
New-PSSession throws a clear error instead of letting Invoke-Command run
the SharePoint scriptblock locally on the master server with no session
(which silently targeted the wrong host for PSConfig and side-by-side
operations). Add an OpenTimeout (30s) and clearer remote-failure messages.

Preserves the version-aware base script (PSSnapin on 2016/2019, none on SE).
Documented under [4.0.0] Fixed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-add src/SPSUpdate_README.md so an offline server still has a self-
contained installation guide inside the release ZIP (next to SPSUpdate.ps1).
Rewritten for v4.0.0: psd1 config with CredentialKey, DPAPI secrets.psd1
(no Credential Manager), the Modules\ layout, and corrected behaviour notes
(no DSC dependency, no DSC cleanup, no reboot-status step). Documents the
read-only InitContentDB action and clarifies that ProductUpdate runs locally
without InstallAccount.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gate the step-2 inventory read/prime on $Action -eq 'Default' so that
-Action ProductUpdate (and Install/Uninstall) no longer generate or refresh
the ContentDatabase inventory JSON. The Default master run and the
-Sequence sub-runs still read/prime it; InitContentDB keeps regenerating it
through its own dedicated action block, avoiding a double snapshot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cast RegisterTaskDefinition to [void] so the returned RegisteredTask COM
object (Name/Path/State/Xml...) is no longer emitted to the output stream
and dumped into the transcript on every task creation, aligning with the
SPSWeather Add-SPSSheduledTask behaviour. Keeps the success message only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

Pester Test Results

  1 files   10 suites   1s ⏱️
 41 tests  41 ✅ 0 💤 0 ❌
104 runs  104 ✅ 0 💤 0 ❌

Results for commit 45c0451.

♻️ This comment has been updated with latest results.

luigilink and others added 2 commits June 29, 2026 15:49
Off a SharePoint server the function emits a non-terminating Write-Error
and returns nothing. On the windows-latest CI runner Pester treats that
stray error stream as a test failure (it passed on macOS only by luck of
error handling). Redirect the error stream (2>$null) and assert on the
captured null result instead, so the test is deterministic on every OS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add [CmdletBinding()] so the helper honours -ErrorAction. Off a SharePoint
server it surfaces a non-terminating error and returns nothing; without
CmdletBinding the previous test could not suppress that error, and the
windows-latest CI runner promoted it to a failure. The test now calls it
with -ErrorAction SilentlyContinue and asserts the null result, which is
deterministic across macOS and Windows (verified with ErrorActionPreference
set to Stop).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@luigilink luigilink merged commit b177588 into main Jun 29, 2026
3 checks passed
@luigilink luigilink deleted the release/4.0.0 branch June 29, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modernize SPSUpdate to the src/ + SPSUpdate.Common module layout (v4.0.0)

1 participant