docs(research): synthesize v2.2 research into SUMMARY.md
Adds v2.2 milestone section (Report Branding & User Directory) while preserving the original v1.0 summary. Covers stack additions (none), feature table stakes vs. differentiators, architecture integration points with dependency-aware build order, top 6 critical pitfalls with prevention strategies, suggested roadmap phase structure, open product questions, and confidence assessment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,349 @@
|
|||||||
|
|
||||||
**Project:** SharePoint Toolbox — C#/WPF SharePoint Online Administration Desktop Tool
|
**Project:** SharePoint Toolbox — C#/WPF SharePoint Online Administration Desktop Tool
|
||||||
**Domain:** SharePoint Online administration, auditing, and provisioning (MSP / IT admin)
|
**Domain:** SharePoint Online administration, auditing, and provisioning (MSP / IT admin)
|
||||||
|
**Researched:** 2026-04-02 (v1.0 original) | 2026-04-08 (v2.2 addendum)
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **Note:** This file contains two sections. The original v1.0 research summary is preserved below
|
||||||
|
> the v2.2 section. The roadmapper should consume **v2.2 first** for the current milestone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# v2.2 Research Summary — Report Branding & User Directory
|
||||||
|
|
||||||
|
**Milestone:** v2.2 — HTML Report Branding (MSP/client logos) + User Directory Browse Mode
|
||||||
|
**Synthesized:** 2026-04-08
|
||||||
|
**Sources:** STACK.md (v2.2 addendum), FEATURES.md (v2.2), ARCHITECTURE.md (v2.2), PITFALLS.md (v2.2 addendum)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
v2.2 adds two independent, self-contained features to a mature WPF MVVM codebase: logo branding
|
||||||
|
across all five HTML export services, and a full-directory browse mode as an alternative to the
|
||||||
|
existing people-picker in the User Access Audit tab. Both features are well within the capabilities
|
||||||
|
of the existing stack — no new NuGet packages are required. The implementation path is low risk
|
||||||
|
because neither feature touches the audit execution engine; they are purely additive layers on top
|
||||||
|
of proven infrastructure.
|
||||||
|
|
||||||
|
The branding feature follows a single clear pattern: store logos as base64 strings in existing JSON
|
||||||
|
settings and profile files, pass them at export time via a new optional `ReportBranding` record, and
|
||||||
|
inject `<img data-URI>` tags into a shared HTML header block. The architecture keeps the five export
|
||||||
|
services independent (each receives an optional parameter) while avoiding code duplication through a
|
||||||
|
shared header builder. The user directory browse feature adds a new `IGraphUserDirectoryService`
|
||||||
|
alongside the existing search service, wires it to new ViewModel state in
|
||||||
|
`UserAccessAuditViewModel`, and presents it as a toggle-panel in the View. The existing audit
|
||||||
|
pipeline is completely untouched.
|
||||||
|
|
||||||
|
The primary risks are not technical complexity but execution discipline: logo size must be enforced
|
||||||
|
at import time (512 KB limit) to prevent HTML report bloat, Graph pagination must use `PageIterator`
|
||||||
|
to handle tenants with more than 999 users, and logo data must be stored as base64 strings (not file
|
||||||
|
paths) to ensure portability across machines. All three of these are straightforward to implement
|
||||||
|
once the storage strategy is decided and locked in at the beginning of each feature's implementation
|
||||||
|
phase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Findings
|
||||||
|
|
||||||
|
### Stack Additions — None Required
|
||||||
|
|
||||||
|
The entire v2.2 scope is served by the existing stack:
|
||||||
|
|
||||||
|
| Capability | Provided By | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Logo encoding (file → base64) | BCL `Convert.ToBase64String` + `File.ReadAllBytesAsync` | Zero new packages |
|
||||||
|
| Logo preview in WPF settings UI | `BitmapImage` (WPF PresentationCore, already a transitive dep) | Standard WPF pattern |
|
||||||
|
| Logo file picker | `OpenFileDialog` (WPF Microsoft.Win32, already used in codebase) | Filter to PNG/JPG/GIF/BMP |
|
||||||
|
| User directory listing with pagination | `Microsoft.Graph` 5.74.0 `PageIterator<User, UserCollectionResponse>` | Already installed |
|
||||||
|
| Local directory filtering | `ICollectionView.Filter` (WPF System.Windows.Data) | Already used in PermissionsViewModel |
|
||||||
|
| Logo + profile JSON persistence | `System.Text.Json` + existing Repository pattern | Backward-compatible nullable fields |
|
||||||
|
|
||||||
|
Do NOT add: HTML template engines (Razor/Scriban), image processing libraries (ImageSharp,
|
||||||
|
Magick.NET), or PDF export libraries. All explicitly out of scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Feature Table Stakes vs. Differentiators
|
||||||
|
|
||||||
|
**Feature 1: HTML Report Branding**
|
||||||
|
|
||||||
|
Table stakes (must ship):
|
||||||
|
- MSP global logo in every HTML report header
|
||||||
|
- Client (per-tenant) logo in report header
|
||||||
|
- Logo renders without external URL (data-URI embedding for self-contained HTML portability)
|
||||||
|
- Graceful absence — no broken image icon when logo is not configured
|
||||||
|
- Consistent placement across all five HTML export types
|
||||||
|
|
||||||
|
Differentiators (build after table stakes):
|
||||||
|
- Auto-pull client logo from Microsoft Entra tenant branding (`GET /organization/{id}/branding/localizations/default/bannerLogo`) — zero-config path using the existing `User.Read` delegated scope
|
||||||
|
- Report timestamp and tenant display name in header
|
||||||
|
|
||||||
|
Anti-features — do not build:
|
||||||
|
- Per-tenant CSS color themes (design system complexity, disproportionate to MSP value)
|
||||||
|
- PDF export with embedded logo (requires third-party binary dependency)
|
||||||
|
- SVG logo support (XSS risk in data-URIs; PNG/JPG/GIF/BMP only)
|
||||||
|
- Hotlinked logo URL field (breaks offline/archived reports)
|
||||||
|
|
||||||
|
**Feature 2: User Directory Browse Mode**
|
||||||
|
|
||||||
|
Table stakes (must ship):
|
||||||
|
- Full directory listing (all enabled member users) with pagination
|
||||||
|
- In-memory text filter on DisplayName/UPN/Mail without server round-trips
|
||||||
|
- Sortable columns (Name, UPN)
|
||||||
|
- Select user from list to trigger existing audit pipeline
|
||||||
|
- Loading indicator with user count feedback ("Loaded X users...")
|
||||||
|
- Toggle between Browse mode and Search (people-picker) mode
|
||||||
|
|
||||||
|
Differentiators (add after core browse is stable):
|
||||||
|
- Filter by account type (member vs. guest toggle)
|
||||||
|
- Department / Job Title columns
|
||||||
|
- Session-scoped directory cache (invalidated on tenant switch)
|
||||||
|
|
||||||
|
Anti-features — do not build:
|
||||||
|
- Eager load on tab open (large tenants block UI and risk throttling)
|
||||||
|
- Delta query / incremental sync (wrong pattern for single-session audit)
|
||||||
|
- Multi-user bulk simultaneous audit (different results model, out of scope)
|
||||||
|
- Export user directory to CSV (identity reporting, not access audit)
|
||||||
|
|
||||||
|
**Recommended MVP build order:**
|
||||||
|
1. MSP logo in all HTML reports — highest visible impact, lowest complexity
|
||||||
|
2. Client logo in HTML reports (import from file) — completes co-branding
|
||||||
|
3. User directory browse core (load, select, filter, pipe into audit)
|
||||||
|
4. Auto-pull client logo from Entra branding — add after file import path is proven
|
||||||
|
5. Directory guest filter + department/jobTitle columns — low-effort polish
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Architecture Integration Points and Build Order
|
||||||
|
|
||||||
|
**New files to create (7):**
|
||||||
|
|
||||||
|
| Component | Layer | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `Core/Models/BrandingSettings.cs` | Core/Models | MSP logo base64 + MIME type; global, persisted in `branding.json` |
|
||||||
|
| `Core/Models/ReportBranding.cs` | Core/Models | Lightweight record assembled at export time; NOT persisted |
|
||||||
|
| `Core/Models/PagedUserResult.cs` | Core/Models | Page of `GraphUserResult` items + next-page cursor token |
|
||||||
|
| `Infrastructure/Persistence/BrandingRepository.cs` | Infrastructure | Atomic JSON write (mirrors SettingsRepository pattern exactly) |
|
||||||
|
| `Services/BrandingService.cs` | Services | Orchestrates file read → MIME detect → base64 → save |
|
||||||
|
| `Services/IGraphUserDirectoryService.cs` | Services | Contract for paginated tenant user enumeration |
|
||||||
|
| `Services/GraphUserDirectoryService.cs` | Services | Graph API user listing with `PageIterator` cursor pagination |
|
||||||
|
|
||||||
|
**Existing files to modify (17), by risk level:**
|
||||||
|
|
||||||
|
Medium risk (left-panel restructure or new async command):
|
||||||
|
- `ViewModels/Tabs/UserAccessAuditViewModel.cs` — add `IGraphUserDirectoryService` injection + browse mode state/commands
|
||||||
|
- `Views/Tabs/UserAccessAuditView.xaml` — add mode toggle + browse panel in left column
|
||||||
|
|
||||||
|
Low risk (optional param or uniform inject-and-call pattern, batchable):
|
||||||
|
- All 5 `Services/Export/*HtmlExportService.cs` — add `ReportBranding? branding = null` optional parameter
|
||||||
|
- `PermissionsViewModel`, `StorageViewModel`, `SearchViewModel`, `DuplicatesViewModel` — add `BrandingService` injection + use in `ExportHtmlAsync`
|
||||||
|
- `SettingsViewModel.cs` — add MSP logo browse/preview/clear commands
|
||||||
|
- `ProfileManagementViewModel.cs` — add client logo browse/preview/clear commands
|
||||||
|
- `SettingsView.xaml`, `ProfileManagementDialog.xaml` — add logo UI sections
|
||||||
|
- `App.xaml.cs` — register 3 new services
|
||||||
|
|
||||||
|
**Dependency-aware build phases:**
|
||||||
|
|
||||||
|
| Phase | Scope | Risk | Gate |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A — Models | BrandingSettings, ReportBranding, PagedUserResult, TenantProfile logo fields | None | POCOs; no dependencies |
|
||||||
|
| B — Services | BrandingRepository, BrandingService, IGraphUserDirectoryService, GraphUserDirectoryService | Low | Unit-testable with mocks; Phase A required |
|
||||||
|
| C — Export services | Add optional `ReportBranding?` to all 5 HTML export services | Low | Phase A required; regression tests: null branding produces identical HTML |
|
||||||
|
| D — Branding ViewModels | SettingsVM, ProfileManagementVM, 4 export VMs, App.xaml.cs registration | Low | Phase B+C required; steps are identical pattern, batch them |
|
||||||
|
| E — Directory ViewModel | UserAccessAuditViewModel browse mode state + commands | Medium | Phase B required; do after branding ViewModel pattern is proven |
|
||||||
|
| F — Branding Views | SettingsView.xaml, ProfileManagementDialog.xaml, base64→BitmapSource converter | Low | Phase D required; write converter once, reuse in both views |
|
||||||
|
| G — Directory View | UserAccessAuditView.xaml + code-behind SelectionChanged handler | Medium | Phase E required; do last, after ViewModel unit tests pass |
|
||||||
|
|
||||||
|
Key architectural constraints (must not violate):
|
||||||
|
- **Client logo on `TenantProfile`, NOT in `BrandingSettings`.** Client logos are per-tenant; mixing them with global MSP settings makes per-profile deletion and serialization awkward.
|
||||||
|
- **Logos stored as base64 strings in JSON, not as file paths.** File paths become stale when the tool is redistributed to another machine. Decided once at Phase A; all downstream phases depend on it.
|
||||||
|
- **Export services use optional `ReportBranding?` parameter, not required.** All existing call sites compile unchanged; branding is injected only where desired.
|
||||||
|
- **No `IHtmlExportService` interface for this change.** The existing 5-concrete-classes pattern needs no interface for an optional parameter addition.
|
||||||
|
- **`GraphUserDirectoryService` is a new service, separate from `GraphUserSearchService`.** Different call patterns (no `startsWith` filter, different pagination), different cancellation needs.
|
||||||
|
- **Do NOT load the directory automatically on tab open.** Require explicit "Load Directory" button click to avoid blocking UI on large tenants.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Top Pitfalls and Prevention Strategies
|
||||||
|
|
||||||
|
**v2.2-1 (Critical): Base64 logo bloat in every report**
|
||||||
|
Large source images (300-600 KB originals) become 400-800 KB of base64 inlined in every exported
|
||||||
|
HTML file, re-allocated on every export call.
|
||||||
|
Prevention: Enforce 512 KB max at import time in the settings UI. Store pre-encoded base64 in JSON
|
||||||
|
(computed once on import, never re-encoded). Inject the cached string directly into the `<img>` tag.
|
||||||
|
|
||||||
|
**v2.2-2 (Critical): Graph directory listing silently truncates at 999 users**
|
||||||
|
`GET /users` returns at most 999 per page. A 5,000-user tenant appears to have 999 users with no
|
||||||
|
error and no indication of truncation.
|
||||||
|
Prevention: Use `PageIterator<User, UserCollectionResponse>` for all full directory fetches. Never
|
||||||
|
call `.GetAsync()` on the users collection without following `@odata.nextLink` until null.
|
||||||
|
|
||||||
|
**v2.2-3 (Critical): Directory browse exposes guests, service accounts, and disabled accounts by default**
|
||||||
|
Raw `GET /users` returns all object types. An MSP tenant with 50+ guest collaborators and service
|
||||||
|
accounts produces a noisy, confusing directory.
|
||||||
|
Prevention: Default filter `accountEnabled eq true and userType eq 'Member'`. Expose an "Include
|
||||||
|
guest accounts" checkbox for explicit opt-in. Apply this filter at the service level, not the
|
||||||
|
ViewModel, so the ViewModel is not aware of Graph filter syntax.
|
||||||
|
|
||||||
|
**v2.2-4 (Critical): Directory load hangs UI without progress feedback**
|
||||||
|
3,000-user tenant takes 3-8 seconds. Without count feedback, the user assumes the feature is
|
||||||
|
broken and may double-click the button (triggering concurrent Graph requests).
|
||||||
|
Prevention: `DirectoryLoadStatus` observable property updated via `IProgress<int>` in the
|
||||||
|
PageIterator callback ("Loading... X users"). Guard `AsyncRelayCommand.CanExecute` during loading.
|
||||||
|
Add cancellation button wired to the same `CancellationToken` passed to `PageIterator.IterateAsync`.
|
||||||
|
|
||||||
|
**v2.2-5 (Critical): Logo file format validation skipped — broken images in reports**
|
||||||
|
OpenFileDialog filter is not sufficient. Renamed non-image files, corrupted JPEGs, and SVG files
|
||||||
|
pass the filter but produce broken `<img>` tags in generated reports.
|
||||||
|
Prevention: Validate by loading as `BitmapImage` in a try/catch before persisting. Check
|
||||||
|
`PixelWidth` and `PixelHeight` are non-zero. Use `BitmapCacheOption.OnLoad` + retry with
|
||||||
|
`IgnoreColorProfile` for EXIF-corrupt JPEGs. Reject SVG explicitly.
|
||||||
|
|
||||||
|
**v2.2-6 (Critical): Logo file path stored in JSON becomes stale across machines**
|
||||||
|
Storing `C:\Users\admin\logos\msp-logo.png` works on the import machine only. After redistribution
|
||||||
|
or reinstall, the path is missing and logos silently disappear from new reports.
|
||||||
|
Prevention: Store base64 string directly in `AppSettings` and `TenantProfile` JSON. The original
|
||||||
|
file path is discarded after import. The settings file becomes fully portable.
|
||||||
|
|
||||||
|
**Moderate pitfalls:**
|
||||||
|
- v2.2-7: Logo breaks HTML report print layout — apply `max-height: 60px; max-width: 200px` CSS and add `@media print` rules in the report `<style>` block.
|
||||||
|
- v2.2-8: Logo cleared on profile overwrite — verify `ClientLogoBase64` and `ClientLogoMimeType` survive the profile save/reload cycle before shipping.
|
||||||
|
- v2.2-9: `DirectoryPageTokenNotFoundException` on Graph page iteration retry — use `PageIterator` (which handles retry token correctly) rather than a manual `@odata.nextLink` loop.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implications for Roadmap
|
||||||
|
|
||||||
|
### Suggested Phase Structure
|
||||||
|
|
||||||
|
The two features are architecturally independent. A single developer should follow phases A-G in
|
||||||
|
order. Two developers can run branding (A→D→F) and directory browse (A→B→E→G) in parallel after
|
||||||
|
Phase A completes.
|
||||||
|
|
||||||
|
**Phase 1 — Data foundation (models + repositories + services)**
|
||||||
|
Rationale: Unblocks both features simultaneously. Establishes the logo storage strategy (base64-in-
|
||||||
|
JSON) before any export service or ViewModel is written. Establishing this here prevents a full
|
||||||
|
re-architecture later if file-path storage is chosen first.
|
||||||
|
Delivers: Phases A + B from the build order above.
|
||||||
|
Key decision to make before starting: confirm base64-in-JSON as the storage strategy (not file
|
||||||
|
paths). Document the decision explicitly.
|
||||||
|
Pitfalls to avoid: v2.2-6 (file path portability). The wrong storage decision here propagates to
|
||||||
|
all downstream phases.
|
||||||
|
|
||||||
|
**Phase 2 — HTML export service extensions + branding ViewModel integration**
|
||||||
|
Rationale: Modifying the 5 export services with an optional parameter is low-risk and unblocks all
|
||||||
|
ViewModel callers. The 4 export ViewModel changes are an identical inject-and-call pattern — batch
|
||||||
|
them. The SettingsViewModel and ProfileManagementViewModel changes complete the logo management UX.
|
||||||
|
Delivers: Phases C + D from the build order above. All HTML reports support optional logo headers.
|
||||||
|
MSP logo manageable from Settings. Client logo manageable from ProfileManagementDialog.
|
||||||
|
Pitfalls to avoid: v2.2-1 (size limit at import), v2.2-5 (file format validation), v2.2-7 (print
|
||||||
|
layout CSS). All three must be implemented in this phase, not deferred.
|
||||||
|
|
||||||
|
**Phase 3 — Branding UI views**
|
||||||
|
Rationale: Views built after ViewModel behavior is unit-tested. Requires the base64→BitmapSource
|
||||||
|
converter, written once and reused in both views.
|
||||||
|
Delivers: Phase F from the build order. Settings branding section + ProfileManagementDialog logo
|
||||||
|
fields, both with live preview.
|
||||||
|
|
||||||
|
**Phase 4 — User directory browse ViewModel**
|
||||||
|
Rationale: `UserAccessAuditViewModel` is the highest-risk change. New async command with progress,
|
||||||
|
cancellation, and tenant-switch reset. Implement and unit-test before touching the View.
|
||||||
|
Delivers: Phase E from the build order. Full browse mode behavior is testable via unit tests before
|
||||||
|
any XAML is written.
|
||||||
|
Pitfalls to avoid: v2.2-2 (pagination), v2.2-3 (default filter), v2.2-4 (progress feedback),
|
||||||
|
v2.2-9 (PageIterator vs. manual nextLink loop).
|
||||||
|
|
||||||
|
**Phase 5 — Directory browse UI view**
|
||||||
|
Rationale: Left panel restructure in UserAccessAuditView.xaml is the highest-risk XAML change.
|
||||||
|
Done last, after all ViewModel behavior is proven by tests.
|
||||||
|
Delivers: Phase G from the build order. Complete browse mode UX.
|
||||||
|
|
||||||
|
**Phase 6 — Differentiators (after core features proven)**
|
||||||
|
Rationale: Auto-pull Entra branding, directory guest filter toggle, department/jobTitle columns,
|
||||||
|
session-scoped directory cache. These are enhancements, not blockers for the milestone.
|
||||||
|
Delivers: Zero-config client logo path, richer directory filtering, faster repeat access.
|
||||||
|
Pitfalls to avoid: Auto-pull Entra logo must handle empty-body response gracefully (not all tenants
|
||||||
|
have branding configured). Fall back silently to no logo rather than showing an error.
|
||||||
|
|
||||||
|
### Research Flags
|
||||||
|
|
||||||
|
Phases 1-5 are standard patterns verified by direct codebase inspection. No additional research
|
||||||
|
needed. The architecture file provides exact file locations, class signatures, and data flows.
|
||||||
|
|
||||||
|
Phase 6 (auto-pull Entra branding): MEDIUM confidence. Test the `bannerLogo` stream endpoint
|
||||||
|
against a real tenant with and without branding configured before committing to the implementation.
|
||||||
|
The Graph API documentation states the response is an empty stream (not a 404) when no logo is set
|
||||||
|
— verify this behavior live before building the error handling path around it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open Questions for Product Decisions
|
||||||
|
|
||||||
|
These are not technical blockers but should be resolved before the phase that implements them:
|
||||||
|
|
||||||
|
1. **SVG logo support: anti-feature or bring-your-own-library feature?**
|
||||||
|
Current recommendation: reject SVG (XSS risk in data-URIs, requires SharpVectors for rasterization). If SVG support is needed, SharpVectors adds a dependency. Decide before Phase 2.
|
||||||
|
|
||||||
|
2. **Client logo source priority when both auto-pull and manual import are configured?**
|
||||||
|
Recommendation: manual import wins; auto-pull is the fallback when no manual logo is set.
|
||||||
|
Implement as `ClientLogoSource` enum: `None | Imported | AutoPulled`. Decide before Phase 6.
|
||||||
|
|
||||||
|
3. **Session-scoped directory cache: ViewModel lifetime or shared service?**
|
||||||
|
ViewModel-scoped = cache lost on tab navigation (ViewModel is transient). Service-scoped = cache
|
||||||
|
survives tab switches. Recommendation: start with no cache (Refresh button), add service-level
|
||||||
|
caching in Phase 6 only if user feedback indicates it is needed. Defers scope decision.
|
||||||
|
|
||||||
|
4. **Report header layout: logos side-by-side or MSP left + client right?**
|
||||||
|
Visual design decision only; does not affect services or ViewModels. Current spec uses
|
||||||
|
`display: flex; gap: 16px` (left-to-right). Can be changed at any time.
|
||||||
|
|
||||||
|
5. **"Load Directory" button placement: inside browse panel or tab-level toolbar?**
|
||||||
|
Recommendation: inside the browse panel, visible only in Browse mode. Avoids confusion when in
|
||||||
|
Search mode. Does not affect architecture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Confidence Assessment (v2.2)
|
||||||
|
|
||||||
|
| Area | Confidence | Basis |
|
||||||
|
|---|---|---|
|
||||||
|
| Stack (no new packages needed) | HIGH | Direct codebase inspection + BCL and Graph SDK documentation |
|
||||||
|
| Feature scope (table stakes vs. differentiators) | HIGH | Official Graph API docs + direct codebase inspection + MSP tool competitive research |
|
||||||
|
| Architecture (integration points, build order) | HIGH | Direct inspection of all affected files; exact class names and property signatures verified |
|
||||||
|
| Branding pitfalls (base64, file validation, portability) | HIGH | BCL behavior verified; file path portability pitfall is a well-known pattern |
|
||||||
|
| Graph pagination pitfalls | HIGH | Microsoft Learn PageIterator docs (updated 2025-08-06); DirectoryPageTokenNotFoundException documented |
|
||||||
|
| Directory filter behavior (accountEnabled, userType) | MEDIUM-HIGH | Graph docs confirm filter syntax; recommend verifying against a real tenant before shipping |
|
||||||
|
| Auto-pull Entra banner logo (Phase 6) | MEDIUM | API documented but empty-body behavior (no logo configured) needs live tenant verification |
|
||||||
|
| Print CSS behavior for logo header | MEDIUM | MDN/W3C verified; browser rendering varies; requires cross-browser manual test |
|
||||||
|
|
||||||
|
**Overall confidence:** HIGH for Phases 1-5. MEDIUM for Phase 6 (Entra auto-pull live behavior).
|
||||||
|
|
||||||
|
**Gaps to address during planning:**
|
||||||
|
- Confirm `$filter=accountEnabled eq true and userType eq 'Member'` works without `ConsistencyLevel: eventual` on the v1.0 `/users` endpoint. If eventual consistency is required, the `GraphUserDirectoryService` adds the `ConsistencyLevel` header and `$count=true` to this call path.
|
||||||
|
- Verify the Entra `bannerLogo` stream endpoint returns an empty response body (not HTTP 404) when tenant branding is not configured. This determines the error handling branch in the auto-pull code path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sources (v2.2)
|
||||||
|
|
||||||
|
| Source | Confidence | Used In |
|
||||||
|
|---|---|---|
|
||||||
|
| Microsoft Learn — List users (Graph v1.0), updated 2025-07-23 | HIGH | STACK, FEATURES, PITFALLS |
|
||||||
|
| Microsoft Learn — Page through a collection (Graph SDKs), updated 2025-08-06 | HIGH | STACK, PITFALLS |
|
||||||
|
| Microsoft Learn — Get organizationalBranding (Graph v1.0), updated 2025-11-08 | HIGH | STACK, FEATURES |
|
||||||
|
| .NET BCL docs — Convert.ToBase64String, File.ReadAllBytesAsync | HIGH | STACK |
|
||||||
|
| Microsoft Learn — Graph throttling guidance | HIGH | PITFALLS |
|
||||||
|
| Direct codebase inspection (GraphClientFactory, HtmlExportService, TenantProfile, AppSettings, UserAccessAuditViewModel, SettingsViewModel, UserAccessAuditView.xaml, App.xaml.cs) | HIGH | ARCHITECTURE, STACK |
|
||||||
|
| Existing codebase CONCERNS.md audit (2026-04-02) | HIGH | PITFALLS |
|
||||||
|
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
# v1.0 Research Summary (Original — Preserved for Reference)
|
||||||
|
|
||||||
**Researched:** 2026-04-02
|
**Researched:** 2026-04-02
|
||||||
**Confidence:** HIGH
|
**Confidence:** HIGH
|
||||||
|
|
||||||
@@ -50,7 +393,7 @@ The feature scope is well-researched. Competitive analysis against ShareGate, Ma
|
|||||||
**Should have (competitive differentiators — v1.x):**
|
**Should have (competitive differentiators — v1.x):**
|
||||||
- User access export across selected sites — "everything User X can access across 15 sites" — no native M365 equivalent
|
- User access export across selected sites — "everything User X can access across 15 sites" — no native M365 equivalent
|
||||||
- Simplified permissions view (plain language) — "can edit files" instead of "Contribute"
|
- Simplified permissions view (plain language) — "can edit files" instead of "Contribute"
|
||||||
- Storage graph by file type (pie + bar toggle) — file-type breakdown competitors don't provide
|
- Storage graph by file type (pie + bar toggle) via ScottPlot.WPF
|
||||||
|
|
||||||
**Defer (v2+):**
|
**Defer (v2+):**
|
||||||
- Scheduled scan runs via Windows Task Scheduler (requires stable CLI/headless mode first)
|
- Scheduled scan runs via Windows Task Scheduler (requires stable CLI/headless mode first)
|
||||||
@@ -102,81 +445,49 @@ Based on the combined research, the dependency graph from ARCHITECTURE.md and FE
|
|||||||
### Phase 1: Foundation and Infrastructure
|
### Phase 1: Foundation and Infrastructure
|
||||||
**Rationale:** All 10 critical pitfalls must be resolved before feature work begins. The dependency graph in FEATURES.md shows that every feature requires the tenant profile registry and session caching layer. Establishing async patterns, error handling, DI container, logging, and JSON persistence now prevents the most expensive retrofits.
|
**Rationale:** All 10 critical pitfalls must be resolved before feature work begins. The dependency graph in FEATURES.md shows that every feature requires the tenant profile registry and session caching layer. Establishing async patterns, error handling, DI container, logging, and JSON persistence now prevents the most expensive retrofits.
|
||||||
**Delivers:** Runnable WPF shell with tenant selector, multi-tenant session caching (MSAL + MsalCacheHelper), DI container wiring, Serilog logging, SettingsService with write-then-replace persistence, ResX localization scaffolding, shared pagination helper, shared `AsyncRelayCommand` pattern, global exception handlers.
|
**Delivers:** Runnable WPF shell with tenant selector, multi-tenant session caching (MSAL + MsalCacheHelper), DI container wiring, Serilog logging, SettingsService with write-then-replace persistence, ResX localization scaffolding, shared pagination helper, shared `AsyncRelayCommand` pattern, global exception handlers.
|
||||||
**Addresses:** Tenant profile registry (prerequisite for all features), EN/FR localization scaffolding, error reporting infrastructure.
|
**Research flag:** Standard patterns — no additional research needed.
|
||||||
**Avoids:** All 10 pitfalls — async deadlocks, silent errors, token cache races, JSON corruption, ObservableCollection threading, async void, throttling, disposal gaps, trimming.
|
|
||||||
**Research flag:** Standard patterns — `Microsoft.Extensions.Hosting` + `CommunityToolkit.Mvvm` + `MsalCacheHelper` are well-documented. No additional research needed.
|
|
||||||
|
|
||||||
### Phase 2: Permissions and Audit Core
|
### Phase 2: Permissions and Audit Core
|
||||||
**Rationale:** Permissions reporting is the highest-value daily-use feature and the canonical audit use case. Building it second validates that the auth layer and pagination helper work under real conditions before other features depend on them. It also forces the error reporting UX to be finalized early.
|
**Rationale:** Permissions reporting is the highest-value daily-use feature and validates the auth layer and pagination helper under real conditions.
|
||||||
**Delivers:** Site-level permissions report with recursive scan (configurable depth), CSV export, self-contained HTML export, plain progress feedback ("Scanning X of Y sites"), error surface for failed scans (no silent failures).
|
**Delivers:** Site-level permissions report with recursive scan, CSV export, self-contained HTML export, progress feedback, error surface for failed scans.
|
||||||
**Addresses:** Permissions report (table stakes P1), CSV + HTML export (table stakes P1), error reporting (table stakes P1).
|
**Research flag:** Standard PnP Framework patterns — HIGH confidence.
|
||||||
**Avoids:** 5,000-item threshold (pagination helper reuse), silent errors (error handling from Phase 1), sync/async deadlock (AsyncRelayCommand from Phase 1).
|
|
||||||
**Research flag:** Standard patterns — PnP Framework permission scanning is well-documented. PnP permissions API is HIGH confidence.
|
|
||||||
|
|
||||||
### Phase 3: Storage Metrics and File Operations
|
### Phase 3: Storage Metrics and File Operations
|
||||||
**Rationale:** Storage metrics and file search are the other two daily-use features in the existing tool. They reuse the auth session and export infrastructure from Phases 1–2. Duplicate detection depends on the file enumeration infrastructure built for file search, so these belong together.
|
**Rationale:** Storage metrics and file search reuse the auth session and export infrastructure from Phases 1–2. Duplicate detection depends on file enumeration built here.
|
||||||
**Delivers:** Storage metrics per site (total + breakdown), file search across sites (KQL-based), duplicate file detection (hash or name+size matching), storage data export (CSV + HTML).
|
**Delivers:** Storage metrics, file search (KQL), duplicate detection, storage data export.
|
||||||
**Addresses:** Storage metrics (P1), file search (P1), duplicate detection (P1).
|
**Research flag:** Duplicate detection at scale under Graph throttling may need targeted research.
|
||||||
**Avoids:** Large collection streaming (IProgress<T> pattern from Phase 1), Graph SDK pagination (`PageIterator`), API throttling (retry handler from Phase 1).
|
|
||||||
**Research flag:** Duplicate detection against large tenants under Graph throttling may need tactical research during planning — hash-based detection at scale has specific pagination constraints.
|
|
||||||
|
|
||||||
### Phase 4: Bulk Operations and Provisioning
|
### Phase 4: Bulk Operations and Provisioning
|
||||||
**Rationale:** Bulk operations (member add, site creation, transfer) and site/folder template management are the remaining P1 features. They are the highest-complexity features (HIGH implementation cost in FEATURES.md) and benefit from stable async/cancel/progress infrastructure from Phase 1. Folder provisioning depends on site template management — build together.
|
**Rationale:** Highest-complexity features (bulk writes to client tenants) benefit from stable async/cancel/progress infrastructure from Phase 1.
|
||||||
**Delivers:** Bulk member add/remove, bulk site creation, ownership transfer, site template capture and apply, folder structure provisioning from template.
|
**Delivers:** Bulk member add/remove, bulk site creation, ownership transfer, site template capture and apply, folder structure provisioning.
|
||||||
**Addresses:** Bulk operations with progress/cancel (P1), site template management (P1), folder structure provisioning (P1).
|
**Research flag:** PnP Provisioning Engine for Teams-connected sites — edge cases need validation.
|
||||||
**Avoids:** Operation cancellation (CancellationToken threading from Phase 1), partial-failure reporting (error surface from Phase 2), API throttling (retry handler from Phase 1).
|
|
||||||
**Research flag:** PnP Provisioning Engine for site templates may need specific research during planning — template schema and apply behavior are documented but edge cases (Teams-connected sites, modern vs. classic) need validation.
|
|
||||||
|
|
||||||
### Phase 5: New Differentiating Features (v1.x)
|
### Phase 5: New Differentiating Features (v1.x)
|
||||||
**Rationale:** These three features are new capabilities (not existing-tool parity) that depend on stable v1 infrastructure. User access export across sites requires multi-site permissions scan from Phase 2. Storage charts require storage metrics from Phase 3. Plain-language permissions view is a presentation layer on top of the permissions data model from Phase 2. Grouping them as v1.x avoids blocking the v1 release on new development.
|
**Rationale:** New capabilities (not existing-tool parity) that depend on stable v1 infrastructure. Group here to avoid blocking the v1 release.
|
||||||
**Delivers:** User access export across arbitrary site subsets (cross-site access report for a single user), simplified plain-language permissions view (jargon-free labels, color coding), storage graph by file type (pie/bar toggle via ScottPlot.WPF).
|
**Delivers:** User access export across sites, simplified plain-language permissions view, storage graph by file type.
|
||||||
**Addresses:** User access export (P2), simplified permissions view (P2), storage graph by file type (P2).
|
**Research flag:** User access export — Graph API approach for enumerating all permissions for user X across N sites needs targeted research.
|
||||||
**Uses:** ScottPlot.WPF 5.1.57, existing PermissionsService and StorageService from Phases 2–3.
|
|
||||||
**Research flag:** User access export across sites involves enumerating group memberships, direct assignments, and inherited access across N sites — the Graph API volume and correct enumeration approach may need targeted research.
|
|
||||||
|
|
||||||
### Phase 6: Distribution and Hardening
|
### Phase 6: Distribution and Hardening
|
||||||
**Rationale:** Packaging, end-to-end validation on clean machines, FR locale completeness check, and the "looks done but isn't" checklist from PITFALLS.md. Must be done before any release, not as an afterthought.
|
**Rationale:** Packaging, end-to-end validation on clean machines, FR locale completeness, "looks done but isn't" checklist.
|
||||||
**Delivers:** Single self-contained EXE (`PublishSingleFile=true`, `SelfContained=true`, `PublishTrimmed=false`, `win-x64`), validated on a machine with no .NET runtime, FR locale fully tested, throttling recovery verified, JSON corruption recovery verified, cancellation verified, 5,000+ item library tested.
|
**Delivers:** Single self-contained EXE, validated on a machine with no .NET runtime, all checklist items verified.
|
||||||
**Avoids:** WPF trimming crash (Pitfall 6), "works on dev machine" surprises.
|
**Research flag:** Standard `dotnet publish` configuration — no additional research needed.
|
||||||
**Research flag:** Standard patterns — `dotnet publish` single-file configuration is well-documented.
|
|
||||||
|
|
||||||
### Phase Ordering Rationale
|
|
||||||
|
|
||||||
- **Foundation first** is mandatory: all 10 pitfalls map to Phase 1. The auth layer and async patterns are prerequisites for every subsequent phase. Starting features before the foundation is solid replicates the original app's architectural problems.
|
|
||||||
- **Permissions before storage/search** because permissions validates the pagination helper, auth layer, and export pipeline under real conditions with the most complex data model.
|
|
||||||
- **Bulk ops and provisioning after core read operations** because they have higher risk (they write to client tenants) and should be tested against a validated auth layer and error surface.
|
|
||||||
- **New v1.x features after v1 parity** to avoid blocking the release on non-parity features. The three P2 features are all presentation or cross-cutting enhancements on top of stable Phase 2–3 data models.
|
|
||||||
- **Distribution last** because EXE packaging must be validated against the complete feature set.
|
|
||||||
|
|
||||||
### Research Flags
|
|
||||||
|
|
||||||
Phases likely needing `/gsd:research-phase` during planning:
|
|
||||||
- **Phase 3 (Duplicate detection):** Hash-based detection under Graph throttling constraints at large scale — specific pagination strategy and concurrency limits for file enumeration need validation.
|
|
||||||
- **Phase 4 (Site templates):** PnP Provisioning Engine behavior for Teams-connected sites, modern site template schema edge cases, and apply-template behavior on non-empty sites need verification.
|
|
||||||
- **Phase 5 (User access export):** Graph API approach for enumerating all permissions for a single user across N sites (group memberships + direct assignments + inherited) — the correct API sequence and volume implications need targeted research.
|
|
||||||
|
|
||||||
Phases with standard patterns (skip research-phase):
|
|
||||||
- **Phase 1 (Foundation):** `Microsoft.Extensions.Hosting` + `CommunityToolkit.Mvvm` + `MsalCacheHelper` patterns are extensively documented in official Microsoft sources.
|
|
||||||
- **Phase 2 (Permissions):** PnP Framework permission scanning APIs are HIGH confidence from official PnP documentation.
|
|
||||||
- **Phase 6 (Distribution):** `dotnet publish` single-file configuration is straightforward and well-documented.
|
|
||||||
|
|
||||||
## Confidence Assessment
|
## Confidence Assessment
|
||||||
|
|
||||||
| Area | Confidence | Notes |
|
| Area | Confidence | Notes |
|
||||||
|------|------------|-------|
|
|---|---|---|
|
||||||
| Stack | HIGH | All package versions verified on NuGet; .NET lifecycle dates confirmed on Microsoft support policy page; PnP.Framework vs PnP.Core SDK choice verified against authoritative GitHub issue |
|
| Stack | HIGH | All package versions verified on NuGet; .NET lifecycle dates confirmed; PnP.Framework vs PnP.Core SDK choice verified |
|
||||||
| Features | MEDIUM | Microsoft docs (permissions reports, storage reports, Graph API) are HIGH; competitor feature analysis from marketing pages is MEDIUM; no direct API testing performed |
|
| Features | MEDIUM | Microsoft docs HIGH; competitor feature analysis from marketing pages MEDIUM; no direct API testing |
|
||||||
| Architecture | HIGH | MVVM patterns from Microsoft Learn (official); PnP Framework auth patterns from official PnP docs; `MsalCacheHelper` from official MSAL.NET docs |
|
| Architecture | HIGH | MVVM patterns from Microsoft Learn; PnP Framework auth patterns from official PnP docs; MsalCacheHelper from official MSAL.NET docs |
|
||||||
| Pitfalls | HIGH | Critical pitfalls verified via official docs, PnP GitHub issues, and direct audit of the existing codebase (CONCERNS.md); async deadlock and WPF trimming pitfalls confirmed via dotnet/wpf GitHub issues |
|
| Pitfalls | HIGH | Critical pitfalls verified via official docs, PnP GitHub issues, and direct audit of existing codebase (CONCERNS.md) |
|
||||||
|
|
||||||
**Overall confidence:** HIGH
|
**Overall confidence:** HIGH
|
||||||
|
|
||||||
### Gaps to Address
|
**Gaps to address:**
|
||||||
|
- PnP Provisioning Engine for Teams-connected sites: behavior not fully documented; validate during Phase 4 planning.
|
||||||
- **PnP Provisioning Engine for Teams-connected sites:** The behavior of `PnP.Framework`'s provisioning engine when applied to Teams-connected modern team sites (vs. classic or communication sites) is not fully documented. Validate during Phase 4 planning with a dedicated research spike.
|
- User cross-site access enumeration via Graph API: multiple possible approaches with different throttling profiles; validate during Phase 5 planning.
|
||||||
- **User cross-site access enumeration via Graph API:** The correct Graph API sequence for "all permissions for user X across N sites" (covering group memberships, direct site assignments, and SharePoint group memberships) has multiple possible approaches with different throttling profiles. Validate the most efficient approach during Phase 5 planning.
|
- Graph API volume for duplicate detection at large scale: practical concurrency limits need validation.
|
||||||
- **Graph API volume for duplicate detection:** Enumerating file hashes across a large tenant (100k+ files) via `driveItem` Graph calls has unclear throttling limits at that scale. The practical concurrency limit and whether SHA256 computation must happen client-side needs validation.
|
- ScottPlot.WPF XAML integration: WpfPlot binding patterns less documented than WinForms equivalent; validate during Phase 5 planning.
|
||||||
- **ScottPlot.WPF XAML integration:** ScottPlot 5.x WPF XAML control integration patterns are less documented than the WinForms equivalent. Validate the `WpfPlot` control binding approach during Phase 5 planning.
|
|
||||||
|
|
||||||
## Sources
|
## Sources
|
||||||
|
|
||||||
@@ -187,26 +498,17 @@ Phases with standard patterns (skip research-phase):
|
|||||||
- Microsoft Learn: SharePoint Online list view threshold — https://learn.microsoft.com/en-us/troubleshoot/sharepoint/lists-and-libraries/items-exceeds-list-view-threshold
|
- Microsoft Learn: SharePoint Online list view threshold — https://learn.microsoft.com/en-us/troubleshoot/sharepoint/lists-and-libraries/items-exceeds-list-view-threshold
|
||||||
- Microsoft Learn: Graph SDK paging — https://learn.microsoft.com/en-us/graph/sdks/paging
|
- Microsoft Learn: Graph SDK paging — https://learn.microsoft.com/en-us/graph/sdks/paging
|
||||||
- Microsoft Learn: Graph throttling guidance — https://learn.microsoft.com/en-us/graph/throttling
|
- Microsoft Learn: Graph throttling guidance — https://learn.microsoft.com/en-us/graph/throttling
|
||||||
- PnP Framework GitHub: https://github.com/pnp/pnpframework — .NET targets, auth patterns
|
- PnP Framework GitHub: https://github.com/pnp/pnpframework
|
||||||
- PnP Framework vs Core authoritative comparison: https://github.com/pnp/pnpframework/issues/620
|
- PnP Framework vs Core authoritative comparison: https://github.com/pnp/pnpframework/issues/620
|
||||||
- PnP Framework auth issues: https://github.com/pnp/pnpframework/issues/961, /447
|
|
||||||
- dotnet/wpf trimming issues: https://github.com/dotnet/wpf/issues/4216, /6096
|
- dotnet/wpf trimming issues: https://github.com/dotnet/wpf/issues/4216, /6096
|
||||||
- .NET 10 announcement: https://devblogs.microsoft.com/dotnet/announcing-dotnet-10/
|
|
||||||
- .NET support policy: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core
|
|
||||||
- CommunityToolkit 8.4 announcement: https://devblogs.microsoft.com/dotnet/announcing-the-dotnet-community-toolkit-840/
|
|
||||||
- Existing codebase CONCERNS.md audit (2026-04-02)
|
- Existing codebase CONCERNS.md audit (2026-04-02)
|
||||||
|
|
||||||
### Secondary (MEDIUM confidence)
|
### Secondary (MEDIUM confidence)
|
||||||
- ShareGate SharePoint audit tool feature page — https://sharegate.com/sharepoint-audit-tool
|
- ShareGate SharePoint audit tool feature page — https://sharegate.com/sharepoint-audit-tool
|
||||||
- ManageEngine SharePoint Manager Plus — https://www.manageengine.com/sharepoint-management-reporting/sharepoint-permission-auditing-tool.html
|
- ManageEngine SharePoint Manager Plus — https://www.manageengine.com/sharepoint-management-reporting/sharepoint-permission-auditing-tool.html
|
||||||
- AdminDroid SharePoint Online auditing — https://admindroid.com/microsoft-365-sharepoint-online-auditing
|
- AdminDroid SharePoint Online auditing — https://admindroid.com/microsoft-365-sharepoint-online-auditing
|
||||||
- sprobot.io: 9 must-have features for SharePoint storage reporting — https://www.sprobot.io/blog/how-to-choose-the-right-sharepoint-storage-reporting-tool-9-must-have-features
|
|
||||||
- WPF Development Best Practices 2024 — https://medium.com/mesciusinc/wpf-development-best-practices-for-2024-9e5062c71350
|
|
||||||
- Rick Strahl: Async and Async Void Event Handling in WPF — https://weblog.west-wind.com/posts/2022/Apr/22/Async-and-Async-Void-Event-Handling-in-WPF
|
|
||||||
|
|
||||||
### Tertiary (LOW confidence)
|
|
||||||
- NuGet: ScottPlot.WPF XAML control documentation — sparse; WpfPlot binding patterns need hands-on validation
|
|
||||||
|
|
||||||
---
|
---
|
||||||
*Research completed: 2026-04-02*
|
*v1.0 research completed: 2026-04-02*
|
||||||
|
*v2.2 research synthesized: 2026-04-08*
|
||||||
*Ready for roadmap: yes*
|
*Ready for roadmap: yes*
|
||||||
|
|||||||
Reference in New Issue
Block a user