Tutorial G: SharePoint On-Premises Retirement Archive¶
Overview¶
This tutorial walks you through using Cesivi as a long-term compliance archive for a retiring SharePoint on-premises farm. You will import all lists, libraries, items, versions, attachments, content types, fields, permissions, users, and groups into Cesivi, lock everything down with archive mode, set a retention policy, apply a legal hold, verify content integrity, and tour the ControlCenter archive dashboard.
Use case: A SharePoint 2013/2016/2019 farm is reaching end-of-life. Legal or compliance obligations require retaining the content for 7–30 years. Keeping the farm running costs money; migrating to SharePoint Online is expensive and still requires a retention archive. Cesivi becomes the archive — auditors see exactly what they would have seen on the live farm.
Time required: ~60 minutes on a fresh install
When to use Tutorial G vs Tutorial F: - Tutorial F — Deploy a production live SP-replacement that users actively work in. - Tutorial G — Deploy a read-only retirement archive of a farm that is being shut down.
Reference guides:
ARCHIVE_MODE.md | ARCHIVE_IDENTITY.md | ARCHIVE_IMPORTER.md | ARCHIVE_AUDIT.md | ARCHIVE_INTEGRITY.md | ARCHIVE_RETENTION.md | ARCHIVE_LEGAL_HOLD.md
Prerequisites¶
| Item | Notes |
|---|---|
| .NET 10.0 runtime | dotnet --version must show 10.x |
| Windows host (or Linux — see Deployment Guide) | Windows NTFS vol recommended for WORM (filesystem immutability) |
| 8 GB RAM | 16 GB recommended for large farms |
| 50 GB disk | For WORM audit segments and archive data |
| Source SP farm URL + Site Collection Administrator credentials | Cesivi reads from the source farm |
| Cesivi.Setup installer (CesiviSetup.exe) | Available in the Cesivi release package |
Architecture¶
Source SP Farm Cesivi Archive Instance
┌──────────────┐ ┌──────────────────────────────────────────┐
│ Lists │ │ Archive Site (archive_mode=true) │
│ Libraries │ MigrationTool│ ┌──────────────────────────────────────┐ │
│ Items │ ─────────────▶│ │ Lists / Libraries / Items / Versions │ │
│ Versions │ archive-import│ │ Content Types / Fields │ │
│ Users │ │ └──────────────────────────────────────┘ │
│ Permissions │ │ │
└──────────────┘ │ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │WORM audit│ │Integrity │ │Retent. │ │
│ │log (JSONL│ │SHA-256 │ │gate │ │
│ │hash chain│ │walker │ │(hard) │ │
│ └──────────┘ └──────────┘ └────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Legal Hold gate (hold > retention) │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
│
ControlCenter StorageBrowser
Table of Contents¶
- Phase 1: Install and Configure
- Step 1: Run the Setup Wizard
- Step 2: Choose Archive Mode branch
- Step 3: Configure WORM backend
- Step 4: Set retention defaults
- Step 5: Configure federation
- Step 6: First-boot verification
- Phase 2: Run the Import
- Step 7: Dry-run the import
- Step 8: Full import
- Step 9: Verify post-import state
- Phase 3: Lock It Down
- Step 10: Set retention policy
- Step 11: Apply a legal hold
- Step 12: Verify hold beats retention
- Step 13: Run an integrity walk
- Phase 4: Acceptance and Tour
- Step 14: Tour the ControlCenter Archive bundle
- Step 15: Open StorageBrowser archive view
- Step 16: Export audit log to CSV
- Step 17: Acceptance checklist
- Step 18: What is next
Phase 1: Install and Configure¶
Step 1: Run the Setup Wizard¶
Launch the installer:
- Windows: Double-click
CesiviSetup.exeor run:CesiviSetup.exe - Linux: Run via dotnet:
dotnet CesiviSetup.dll
Walk through the first three pages:
- Welcome — review the version and license.
- Install Path — choose install directory and data directory.
Choose a data directory on a volume with at least 50 GB free. For WORM filesystem immutability the volume should be NTFS (Windows) or ext4 (Linux).
Step 2: Choose Archive Mode branch¶
On the Components page (step 3 of the standard wizard):
- Select the components to install: Server, WebUI, optionally CLI.
- Tick Enable Archive Mode.
- Click Next.
Ticking Enable Archive Mode inserts three additional configuration pages into the wizard (pages 4–6 of 12 total). These pages configure the archive subsystems.
Step 3: Configure WORM Backend¶
WORM Backend (page 4 of 12):
| Option | Notes |
|---|---|
| File System (recommended) | Enter a WORM base path, e.g. C:\CesiviArchive\worm. The installer validates the path. |
| Amazon S3 (Object Lock) | v1.3 planned — configure manually in appsettings.json after install. |
| Azure Blob (Immutability) | v1.3 planned — configure manually after install. |
The File System backend uses OS-level immutability: ReadOnly on Windows and chmod 0444 + chattr +i on
Linux. Once a WORM segment is sealed it cannot be modified without breaking the hash chain.
v1.3 note: When cloud backends arrive they plug in via the
IWormAuditLogStoreSPI already shipped in v1.2. See ARCHIVE_AUDIT.md for the SPI contract.
Step 4: Set Retention Defaults¶
Retention Defaults (page 5 of 12):
| Field | Default | Notes |
|---|---|---|
| Default retention (days) | 2555 (7 years) | HIPAA and SOX both require 7-year retention. Set 0 for no expiry. |
| Retention anchor | Item Created date | ItemCreated, ItemModified, or ImportTime |
Retention windows can only be extended after assignment, never shortened. Once a record is stamped with a retention anchor the longer of (existing, new policy) always wins.
Step 5: Configure Federation¶
Federation (page 6 of 12):
| Mode | Behaviour |
|---|---|
| None (snapshot only) | Identities served from import-time snapshot. Default for air-gapped deployments. |
| Active Directory (LDAP) | Live LDAP lookup; falls back to snapshot if user not found. |
| Microsoft Entra ID | Live Entra/Azure AD lookup; falls back to snapshot. |
For a lab or initial pilot, None is correct — Cesivi resolves all identities from the import-time snapshot. For a production deployment where the source AD still runs, choose AD or Entra to get live display names for users who remain in the directory.
See ARCHIVE_IDENTITY.md for the production federation configuration (AD endpoint, bind credentials, cache TTL).
Complete the remaining wizard pages (IIS, Database, Authentication, Summary, Install, Complete) as for any standard Cesivi deployment.
Step 6: First-Boot Verification¶
After the wizard finishes:
- Start Cesivi.Server (the wizard shows the command or opens the browser automatically).
- Open your browser:
http://localhost:15020(ControlCenter — port from your IIS / branch-config). - Click Archive in the top navigation.
- The Archive KPI Dashboard (
/Archive) should open showing 7 KPI cards — all showing0. This confirms the archive subsystem started correctly on a fresh installation.
Screenshot moment: /Archive KPI dashboard — all zeros, 7 cards rendered
Phase 2: Run the Import¶
Step 7: Dry-Run the Import¶
Before writing anything, validate that Cesivi can reach your source farm:
cd Cesivi.MigrationTool
dotnet run -- archive-import \
--source "https://intranet.contoso.com" \
--username "CONTOSO\administrator" \
--password "Passw0rd" \
--dry-run
A dry-run connects to the source, walks all site collections, lists, and items, and prints summary counts — without writing anything to the Cesivi target.
For this tutorial (reproducible without a real SP farm): use a Cesivi-on-Cesivi import.
Start a second Cesivi instance with a small data set (R:/MockData or a YAML-seeded dataset),
and use it as the --source:
dotnet run -- archive-import \
--source "http://localhost:5010" \
--username "admin" \
--password "admin" \
--dry-run
Expect output like:
[DRY RUN] Site collections found: 1
[DRY RUN] Lists/libraries: 5
[DRY RUN] Items: 47
[DRY RUN] Files: 12
No data was written.
Step 8: Full Import¶
Run the full import with a target:
cd Cesivi.MigrationTool
dotnet run -- archive-import \
--source "http://localhost:5010" \
--username "admin" \
--password "admin" \
--target "http://localhost:15020" \
--target-user "admin" \
--target-password "admin"
--target is the running Cesivi archive instance URL. --target-user / --target-password
default to the source credentials when omitted (safe for same-host test).
During the import, the tool prints a live progress table:
┌───────────────────────────────────────────────────┐
│ Farm: http://localhost:5010 │
│ Phase: Items (3/5 lists complete) │
│ Items: 42/47 Files: 10/12 Errors: 0 │
│ Identity snapshots: 3 ACLs frozen: 5 │
└───────────────────────────────────────────────────┘
The import automatically:
- Sets archive_mode = true on all imported webs and lists
- Captures identity snapshots for every user encountered
- Freezes role assignments into the archived ACL store
- Writes ItemImported, IdentitySnapshotCaptured, and AclFrozen events to the WORM log
Resuming an interrupted import¶
If the import is killed (power loss, Ctrl+C), restart with --resume:
dotnet run -- archive-import \
--source "http://localhost:5010" \
--username "admin" \
--password "admin" \
--target "http://localhost:15020" \
--target-user "admin" \
--target-password "admin" \
--resume
The tool reads a checkpoint file (default: alongside the tool binary) and skips already-imported items.
Screenshot moment: /Archive/ImportProgress — live feed during import
Step 9: Verify Post-Import State¶
Once the import completes, verify the key post-import invariants:
9a. ARCHIVED banner¶
Navigate to the imported site in the WebUI: http://localhost:15020/sites/RootSite/SitePages/Home.aspx
A navy ARCHIVED banner should appear at the top of the page:
⚠ ARCHIVED — This site is a read-only retirement archive. Write operations are not permitted.
9b. Identity dashboard¶
Open ControlCenter → Archive → Identity (/Archive/Identity). You should see:
- Total principals: matches the number of users in the source farm
- At least some with Snapshot tier (users who exist in the snapshot but may not be in the live AD)
Screenshot moment: /Archive/Identity dashboard after import
9c. Audit log events¶
Open ControlCenter → Archive → Audit Log (/Archive/AuditLog). Filter to event_type = ItemImported.
You should see one event per imported item. Also verify IdentitySnapshotCaptured and AclFrozen events.
Screenshot moment: /Archive/AuditLog showing ItemImported events
9d. Write-rejection¶
Attempt to delete an item via REST:
DELETE http://localhost:15020/_api/web/lists/getbytitle('Documents')/items(1)
Authorization: Basic YWRtaW46YWRtaW4=
Expected response: HTTP 403 Forbidden
{
"error": {
"code": "-2147024891, System.UnauthorizedAccessException",
"message": {
"lang": "en-US",
"value": "Web is archived (read-only). Use X-Cesivi-Archive-Override header with admin credentials to bypass."
}
}
}
Troubleshooting¶
| Symptom | Likely cause | Resolution |
|---|---|---|
| Import stalls at "Phase: Users" | Source SP throttling | Add --log-text and check logs; reduce concurrency |
| Identity snapshot count = 0 | Source has no users with SIDs | Check source farm — anonymous-only site has no snapshots |
| ARCHIVED banner missing | WebUI cache | Force-refresh browser; check ControlCenter /Archive/Mode for the web |
| Import fails with 401 on target | Wrong --target-user / --target-password |
Use admin credentials with site collection admin rights on the target |
Cross-reference: ARCHIVE_IMPORTER.md operator troubleshooting section.
Phase 3: Lock It Down¶
Step 10: Set Retention Policy¶
Set a 7-year retention policy on the archived site. Replace {siteId} with the site's archive site ID
(visible in ControlCenter → Archive → Retention → Policy tab, or from the import log).
PATCH http://localhost:15020/_api/archive/retention/policy?siteId={siteId}
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
{
"DefaultWindowDays": 2555,
"Anchor": "ItemCreated",
"AllowExtension": true,
"PolicyVersion": 1
}
Expected: HTTP 204 No Content
Immediately after, check the audit log for a RetentionAnchorAssigned event per item, confirming the
policy was applied to all imported items.
Attempt a destructive operation (verify gate)¶
Try to delete the same item again:
DELETE http://localhost:15020/_api/web/lists/getbytitle('Documents')/items(1)
Authorization: Basic YWRtaW46YWRtaW4=
Expected: HTTP 423 Locked
{
"error": {
"code": "RetentionBlocked",
"message": {
"value": "Item is within retention window (expires 2033-05-27). Deletion blocked by ArchiveRetentionGate."
}
}
}
This DeleteBlocked audit event appears in the Retention dashboard:
Screenshot moment: /Archive/Retention dashboard with blocked-attempt counter
No admin override: The retention gate has no bypass. Even admin credentials cannot delete a retained item. This is by design — it satisfies HIPAA and SOX tamper-proof requirements. See ARCHIVE_RETENTION.md.
Step 11: Apply a Legal Hold¶
Apply an item-scoped legal hold. You will need:
- ArchiveSiteId — the farm GUID shown in ControlCenter → Archive → ImportProgress (or returned by GET /_api/archive/integrity/sites/ status endpoint)
- ListId — the list's GUID (from GET /_api/web/lists/getbytitle('Documents')?$select=Id)
- ItemId — the integer item ID
POST http://localhost:15020/_api/archive/legalhold/apply
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
{
"ArchiveSiteId": "00000000-0000-0000-0000-000000000000",
"Scope": "Item",
"ListId": "00000000-0000-0000-0000-000000000001",
"ItemId": 1,
"CaseNumber": "CASE-2026-001",
"Reason": "Pending litigation — do not destroy"
}
Replace the placeholder GUIDs with the actual values for your archive site and list.
Expected: HTTP 201 Created with a hold ID in the response body.
A HoldApplied event is written to the WORM audit log. Open /Archive/LegalHold to confirm the
hold is listed as Active.
Screenshot moment: /Archive/LegalHold dashboard with active hold
Step 12: Verify Hold Beats Retention¶
The legal hold gate runs before the retention gate. Even if the retention window expired, an active hold blocks all mutation:
To demonstrate: apply the hold to an item whose retention window you have already expired (set
DefaultWindowDays: 1 temporarily, wait for the reaper pass, then restore to 2555). Then attempt:
DELETE http://localhost:15020/_api/web/lists/getbytitle('Documents')/items(1)
Authorization: Basic YWRtaW46YWRtaW4=
Expected: HTTP 423 Locked — the response body references LegalHoldGate, not RetentionGate,
confirming the hold evaluated first:
{
"error": {
"code": "LegalHoldBlocked",
"message": {
"value": "Item is subject to legal hold CASE-2026-001. Deletion blocked by ArchiveLegalHoldGate."
}
}
}
Cross-reference: ARCHIVE_LEGAL_HOLD.md — hold-beats-retention section.
Step 13: Run an Integrity Walk¶
Trigger a full SHA-256 integrity walk across all imported farms:
POST http://localhost:15020/_api/archive/integrity/walks/run
Authorization: Basic YWRtaW46YWRtaW4=
Expected: HTTP 202 Accepted — the walk starts in the background for every registered archive farm.
Per-farm variant: To walk a single farm, use
POST /_api/archive/integrity/sites/{archiveSiteId}/walks/runwhere{archiveSiteId}is the farm GUID shown in ControlCenter → Archive → ImportProgress.
Open ControlCenter → Archive → Integrity (/Archive/Integrity) and watch the SignalR
live-tail. The IntegrityWalkStarted event fires immediately, followed by per-item progress,
then IntegrityWalkCompleted with a summary.
Screenshot moment: /Archive/Integrity dashboard during walk
Simulate a corruption to test the on-access gate¶
- Locate a BLOB file in the archive data directory (under
{DataRootPath}/lists/...). - Open it in a text editor and change one byte.
- Request the file via REST:
GET http://localhost:15020/_api/web/lists/getbytitle('Documents')/items(1)/File/$value Authorization: Basic YWRtaW46YWRtaW4= - Expected:
HTTP 451 Unavailable For Legal Reasons
{
"error": {
"code": "IntegrityMismatch",
"message": {
"value": "Content integrity check failed. Item is quarantined pending remediation."
}
}
}
- Restore the BLOB to its original bytes to clear quarantine. Once the file matches its stored SHA-256 hash, the next on-access read will succeed.
Cross-reference: ARCHIVE_INTEGRITY.md — on-access gate and quarantine sections.
Phase 4: Acceptance and Tour¶
Step 14: Tour the ControlCenter Archive Bundle¶
Open http://localhost:15020 (ControlCenter) → Archive.
The _ArchiveLayout sticky sidebar nav appears on every archive page:
| Page | URL | What to check |
|---|---|---|
| Dashboard | /Archive |
7 KPI cards populated (non-zero after import) |
| Archive Mode | /Archive/Mode |
Toggle + audit trail; confirm imported sites show ARCHIVED |
| Identity | /Archive/Identity |
Principal count, tier distribution (Live/Snapshot/Unknown) |
| Import | /Archive/ImportProgress |
Last import status per farm |
| Audit Log | /Archive/AuditLog |
Event rate, live-tail, filter + CSV export |
| Integrity | /Archive/Integrity |
Walk status, mismatch count (0 = green) |
| Retention | /Archive/Retention |
Items under retention, blocked-attempt heat map |
| Legal Hold | /Archive/LegalHold |
Active holds, items under hold |
Screenshot moment: /Archive KPI dashboard populated after import
Step 15: Open StorageBrowser Archive View¶
Start StorageBrowser if not already running:
cd Cesivi.StorageBrowser
export CESIVI_SERVER_URL=http://localhost:15020
export CESIVI_STORAGE_BROWSER_PORT=5610
dotnet run
Navigate to http://localhost:5610. In the tree:
- Archived sites show a ARCHIVED badge (navy).
- Sites with active legal holds show a 🔒 lock icon.
- Integrity status shows as a green pill ("OK") or red pill ("Mismatches").
- Retention countdown shows "720d remaining" or EXPIRED if lapsed.
Click Details next to an archived site to open the ArchiveDetail 4-card panel:
| Card | Content |
|---|---|
| Archive Mode | true, archived-at timestamp, source farm |
| Retention | 7-year window, anchor: ItemCreated, last blocked attempt |
| Legal Holds | Active hold list with case numbers |
| Integrity | Last walk timestamp, mismatch count |
Screenshot moment: StorageBrowser ARCHIVED badge + ArchiveDetail panel
Step 16: Export Audit Log to CSV¶
In ControlCenter → Archive → Audit Log (/Archive/AuditLog):
- Set filter: Event type =
DeleteBlocked; Date range = last 30 days. - Click Export CSV.
- Verify the downloaded file has columns:
eventType,importedAt,itemKey,principalIds,detail.
The CSV export is the primary artifact for compliance auditors. It captures all blocked deletion attempts for the retention period in a tamper-evident format.
Step 17: Acceptance Checklist¶
Before declaring the archive site live, tick off each of the following:
- [ ]
archive_mode = trueconfirmed on all imported webs and lists (ControlCenter/Archive/Mode) - [ ] Identity snapshot count = expected source user count (ControlCenter
/Archive/Identity) - [ ] All role assignments frozen —
AclFrozenevents in audit log - [ ] Retention policy set —
RetentionAnchorAssignedevents in audit log - [ ] WORM segments sealed and hash-chain verified (ControlCenter
/Archive/AuditLog→ chain status indicator) - [ ] Integrity walker reports 0 mismatches (ControlCenter
/Archive/Integrity) - [ ] Audit log live-tailing in real time (SignalR indicator green)
- [ ] Legal hold mechanism tested —
HoldAppliedevent in audit log - [ ] No destructive operations possible (attempted DELETE returns 403/423)
- [ ] No admin override possible for retained items (verified with admin credentials)
- [ ] All 7 ControlCenter sub-pages render correctly (
/Archivethrough/Archive/LegalHold) - [ ] StorageBrowser archive badges correct (ARCHIVED, lock, integrity pill, retention countdown)
- [ ] Audit log CSV export produces valid file
Step 18: What is Next¶
v1.2 is your production-ready on-premises retirement archive. The archive is now:
- Tamper-resistant (WORM audit log with hash chain)
- Content-verified (SHA-256 integrity walker)
- Retention-enforced (hard gate, no bypass)
- Legal-hold capable (hold beats retention)
- Fully observable (ControlCenter + StorageBrowser)
v1.3 roadmap:
| Feature | Notes |
|---|---|
| Variant B — SPO local backup | Archive SharePoint Online tenant exports into Cesivi |
| S3 Object Lock backend | Plug in via IWormAuditLogStore SPI (already in v1.2) |
| Azure Blob Immutability backend | Plug in via IWormAuditLogStore SPI |
| Identity federation at scale | AD/Entra adapters for large user bases |
| Multi-tenant archive collections | Host multiple independent farm archives on one Cesivi instance |
For buying-decision and compliance-officer context, see: - Cesivi Archive Variant A whitepaper - Compliance Cookbook — HIPAA/GDPR/SOX/FRCP
See also¶
- ARCHIVE_MODE.md — archive mode operator guide
- ARCHIVE_IDENTITY.md — identity resolution operator guide
- ARCHIVE_IMPORTER.md — MigrationTool archive-import operator guide
- ARCHIVE_AUDIT.md — WORM audit log operator guide
- ARCHIVE_INTEGRITY.md — integrity verification operator guide
- ARCHIVE_RETENTION.md — retention enforcement operator guide
- ARCHIVE_LEGAL_HOLD.md — legal hold operator guide
- ARCHIVE_CONTROLCENTER.md — ControlCenter Archive bundle quick tour
- ARCHIVE_TOOLS_OPERATOR.md — StorageBrowser/StorageConverter/Setup operator guide
- CESIVI_ARCHIVE_VARIANT_A.md — customer whitepaper
- COMPLIANCE_COOKBOOK.md — HIPAA/GDPR/SOX/FRCP mapping