# Stack Research

**Domain:** C#/WPF desktop administration tool for SharePoint Online (multi-tenant MSP)
**Researched:** 2026-04-02
**Confidence:** HIGH (core framework choices), MEDIUM (charting library)

---

## Recommended Stack

### Core Technologies

| Technology | Version | Purpose | Why Recommended |
|------------|---------|---------|-----------------|
| .NET 10 LTS | 10.x | Target runtime | Released November 2025, LTS until November 2028 — the current LTS. Avoid .NET 8 (ends November 2026) and .NET 9 STS (ended May 2026). WPF support is first-class and actively improved in .NET 10. |
| WPF (.NET 10) | built-in | UI framework | Windows-only per project constraint. Modern MVVM data binding, richer styling than WinForms. The existing codebase uses WinForms; WPF is the correct upgrade path for richer UI. |
| C# 13 | built-in with .NET 10 | Language | Current language version shipping with .NET 10 SDK. |

### SharePoint / Microsoft 365 API

| Library | Version | Purpose | Why Recommended |
|---------|---------|---------|-----------------|
| PnP.Framework | 1.18.0 | SharePoint CSOM extensions, provisioning engine, site templates, permissions | Directly replaces PnP.PowerShell patterns the existing app uses. Contains PnP Provisioning Engine needed for site templates feature. Targets .NET Standard 2.0 so runs on .NET 10 via compatibility. This is the correct choice for a CSOM-heavy migration — use PnP.Core SDK only when starting greenfield with Graph-first design. |
| Microsoft.Graph | 5.103.0 | Microsoft Graph API access (Teams, Groups, users across tenants) | Required for Teams site management, user enumeration across tenants. Complements PnP.Framework which is CSOM-first. Use Graph SDK for Graph-native operations; use PnP.Framework for SharePoint-specific provisioning. |

**Note on PnP.Core SDK vs PnP.Framework:** PnP Core SDK is the modern Graph-first replacement for PnP Framework, but PnP Framework is the right choice here because: (1) this is a migration from PnP.PowerShell which is CSOM-based, (2) the PnP Provisioning Engine for site templates lives in PnP.Framework, not PnP Core SDK, (3) the existing feature set maps directly to PnP.Framework's extension methods.

### Authentication

| Library | Version | Purpose | Why Recommended |
|---------|---------|---------|-----------------|
| Microsoft.Identity.Client (MSAL.NET) | 4.83.1 | Azure AD interactive browser login, token acquisition | The underlying auth library used by both PnP.Framework and Microsoft.Graph SDK. Use directly for multi-tenant session management. |
| Microsoft.Identity.Client.Extensions.Msal | 4.83.3 | Token cache persistence to disk | Required for multi-tenant session caching — serializes the MSAL token cache to encrypted local storage so users don't re-authenticate on each app launch or tenant switch. PnP.Framework 1.18.0 already depends on this (>= 4.70.2). |
| Microsoft.Identity.Client.Desktop | 4.82.1 | Windows-native broker support (WAM) | Enables Windows Authentication Manager integration for WPF apps. Provides system-level SSO. Add `.WithWindowsBroker()` to the PublicClientApplicationBuilder. |

**Multi-tenant session caching pattern:** Create one `PublicClientApplication` per tenant, serialize each tenant's token cache separately using `MsalCacheHelper` from Extensions.Msal. Store serialized caches in `%AppData%\SharepointToolbox\tokens\{tenantId}.bin`. PnP.Framework's `AuthenticationManager.CreateWithInteractiveLogin()` accepts a custom MSAL app instance — wire the cached app here.

### MVVM Infrastructure

| Library | Version | Purpose | Why Recommended |
|---------|---------|---------|-----------------|
| CommunityToolkit.Mvvm | 8.4.2 | MVVM base classes, source-generated commands and properties, messaging | Microsoft-maintained, ships with .NET Community Toolkit. Source generators eliminate 90% of MVVM boilerplate. `[ObservableProperty]`, `[RelayCommand]`, `[INotifyPropertyChanged]` attributes generate all property change plumbing at compile time. The standard choice for WPF/MVVM in 2025-2026. |
| Microsoft.Extensions.Hosting | 10.x | Generic Host for DI, configuration, lifetime management | Provides `IServiceCollection` DI container, `IConfiguration`, and structured app startup/shutdown lifecycle in WPF. Avoids manual service locator patterns. Wire WPF `Application.Startup` into the host lifetime. |
| Microsoft.Extensions.DependencyInjection | 10.x | DI container | Included with Hosting. Register ViewModels, services, and repositories as scoped/singleton/transient services. |

### Logging

| Library | Version | Purpose | Why Recommended |
|---------|---------|---------|-----------------|
| Serilog | 4.3.1 | Structured logging | Industry standard for .NET desktop apps. Structured log events (not just strings) make post-mortem debugging of the existing app's 38 silent catch blocks tractable. File sink for persistent logs, debug sink for development. |
| Serilog.Extensions.Logging | 10.0.0 | Bridge Serilog into ILogger<T> | Allows injecting `ILogger<T>` everywhere while Serilog handles the actual output. One configuration point. |
| Serilog.Sinks.File | latest | Write logs to rolling files | `%AppData%\SharepointToolbox\logs\log-.txt` with daily rolling. Essential for diagnosing auth and SharePoint API failures in the field. |

### Data Serialization

| Library | Version | Purpose | Why Recommended |
|---------|---------|---------|-----------------|
| System.Text.Json | built-in .NET 10 | JSON read/write for profiles, settings, templates | Built into .NET, no NuGet dependency, faster and less memory-hungry than Newtonsoft.Json. Sufficient for the simple config/profile/template structures this app needs. The existing PowerShell app uses JSON — `System.Text.Json` with source generators enables AOT-safe deserialization, important for self-contained EXE size. |

**Why not Newtonsoft.Json:** Slower, adds ~500KB to the EXE, no AOT support. Only justified when you need LINQ-to-JSON or highly polymorphic deserialization — neither of which applies here.

### Data Visualization (Charts)

| Library | Version | Purpose | Why Recommended |
|---------|---------|---------|-----------------|
| ScottPlot.WPF | 5.1.57 | Pie and bar charts for storage metrics | Stable, actively maintained (weekly releases), MIT licensed, no paid tier. Supports pie, bar, and all chart types needed. Renders via SkiaSharp — fast even for large datasets. LiveCharts2 is still RC for WPF (2.0.0-rc6.1 as of April 2026) and introduces unnecessary risk. OxyPlot is mature but lacks interactive features and has poor performance on large datasets. ScottPlot 5.x is the stable choice. |

### Report Generation

| Library | Version | Purpose | Why Recommended |
|---------|---------|---------|-----------------|
| CsvHelper | latest stable | CSV export | Industry standard for .NET CSV serialization. Handles encoding, quoting, header generation. Replaces manual string concatenation. |
| No HTML library needed | — | HTML reports | Generate HTML reports via `StringBuilder` or T4/Scriban text templates with embedded JS (Chart.js or DataTables). Self-contained HTML files require no server. Keep it simple — a `ReportBuilder` service class is sufficient. |

### Localization

| Library | Version | Purpose | Why Recommended |
|---------|---------|---------|-----------------|
| .NET Resource files (.resx) | built-in | EN/FR localization | ResX is the standard WPF localization approach for a two-language desktop app. Compile-time safety, strong tooling in Visual Studio, no runtime switching complexity. The existing app uses a key-based translation system — ResX maps directly. Use `Properties/Resources.en.resx` and `Properties/Resources.fr.resx`. Runtime language switching (if needed later) is achievable via `Thread.CurrentThread.CurrentUICulture`. |

### Distribution

| Tool | Version | Purpose | Why Recommended |
|------|---------|---------|-----------------|
| `dotnet publish` with PublishSingleFile + SelfContained | .NET 10 SDK | Single self-contained EXE | Built-in SDK feature. Set `<PublishSingleFile>true</PublishSingleFile>`, `<SelfContained>true</SelfContained>`, `<RuntimeIdentifier>win-x64</RuntimeIdentifier>`. No third-party tool needed. Expected output size: ~150-200MB (runtime + SkiaSharp from ScottPlot). |

---

## Project File Configuration

```xml
<PropertyGroup>
  <TargetFramework>net10.0-windows</TargetFramework>
  <UseWPF>true</UseWPF>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <!-- Distribution -->
  <PublishSingleFile>true</PublishSingleFile>
  <SelfContained>true</SelfContained>
  <RuntimeIdentifier>win-x64</RuntimeIdentifier>
  <!-- Trim carefully — MSAL and PnP use reflection -->
  <PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
```

**Note on trimming:** Do NOT enable `PublishTrimmed` with PnP.Framework or MSAL.NET. Both libraries use reflection internally and are not trim-safe. The EXE will be larger (~150-200MB) but reliable. Trimming would require extensive `[DynamicDependency]` annotations and is not worth the effort.

---

## Installation (NuGet Package References)

```xml
<!-- SharePoint / Graph API -->
<PackageReference Include="PnP.Framework" Version="1.18.0" />
<PackageReference Include="Microsoft.Graph" Version="5.103.0" />

<!-- Authentication -->
<PackageReference Include="Microsoft.Identity.Client" Version="4.83.1" />
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.83.3" />
<PackageReference Include="Microsoft.Identity.Client.Desktop" Version="4.82.1" />

<!-- MVVM + DI -->
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />

<!-- Logging -->
<PackageReference Include="Serilog" Version="4.3.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

<!-- Charts -->
<PackageReference Include="ScottPlot.WPF" Version="5.1.57" />

<!-- CSV Export -->
<PackageReference Include="CsvHelper" Version="33.0.1" />
```

---

## Alternatives Considered

| Category | Recommended | Alternative | Why Not |
|----------|-------------|-------------|---------|
| .NET version | .NET 10 LTS | .NET 8 LTS | .NET 8 support ends November 2026 — too soon for a new project to start on |
| .NET version | .NET 10 LTS | .NET 9 STS | .NET 9 ended May 2026 — already past EOL at time of writing |
| SharePoint API | PnP.Framework | PnP Core SDK | PnP Core SDK is Graph-first and not yet feature-complete for CSOM-heavy provisioning operations. Wrong choice for a migration from PnP.PowerShell patterns. |
| MVVM toolkit | CommunityToolkit.Mvvm | Prism | Prism adds module/region/navigation complexity appropriate for large enterprise apps. This is a focused admin tool — CommunityToolkit.Mvvm is leaner and Microsoft-maintained. |
| Charts | ScottPlot.WPF | LiveCharts2 | LiveCharts2 WPF package is still RC (2.0.0-rc6.1). Unstable API surface is inappropriate for production. |
| Charts | ScottPlot.WPF | OxyPlot | OxyPlot has poor performance on large datasets and limited interactivity. Low activity/maintenance compared to ScottPlot 5. |
| JSON | System.Text.Json | Newtonsoft.Json | Newtonsoft.Json adds ~500KB to EXE, is slower, and has no AOT support. Not needed for simple config structures. |
| Localization | ResX (.resx files) | WPF ResourceDictionary XAML | ResourceDictionary localization is more complex, harder to maintain with tooling, and overkill for a two-language app. ResX provides compile-time safety. |
| HTML reports | T4/StringBuilder | Razor / Blazor Hybrid | A dedicated template engine adds a dependency for what is a one-time file generation task. StringBuilder or Scriban (lightweight) is sufficient. |
| Logging | Serilog | Microsoft.Extensions.Logging (built-in) | Built-in logging lacks file sinks and structured event support without additional providers. Serilog is de facto standard for desktop .NET apps. |

---

## What NOT to Use

| Avoid | Why | Use Instead |
|-------|-----|-------------|
| LiveCharts2 WPF | Still in RC (2.0.0-rc6.1 as of April 2026) — unstable API, potential breaking changes before 2.0 GA | ScottPlot.WPF 5.1.57 (stable, weekly releases) |
| PnP Core SDK (as primary SharePoint lib) | Graph-first design doesn't match the CSOM-heavy provisioning/permissions operations being migrated. The PnP Provisioning Engine is only in PnP.Framework | PnP.Framework 1.18.0 |
| Prism Framework | Overengineered for this use case. Adds module system, region navigation complexity that doesn't match a single-window admin tool | CommunityToolkit.Mvvm 8.4.2 |
| PublishTrimmed=true | PnP.Framework and MSAL.NET use reflection and are not trim-safe. Trimming causes runtime crashes | Keep trimming disabled; accept larger EXE |
| .NET 8 as target | EOL November 2026 — a new project started now should not immediately be on a near-EOL runtime | .NET 10 LTS (supported until November 2028) |
| SQLite / LiteDB | Out of scope per project constraints. JSON is sufficient for profiles, settings, templates. | System.Text.Json with file-based storage |
| DeviceLogin / client secrets for auth | Per project memory note: MSP workflow requires interactive login, never DeviceLogin for PnP registration | MSAL interactive browser login via `WithInteractiveBrowser()` |
| WinForms | The existing app is WinForms. The rewrite targets WPF explicitly for MVVM data binding and richer styling | WPF |

---

## Version Compatibility Notes

| Concern | Detail |
|---------|--------|
| PnP.Framework on .NET 10 | PnP.Framework targets .NET Standard 2.0, .NET 8.0, .NET 9.0. It runs on .NET 10 via .NET Standard 2.0 compatibility. No explicit .NET 10 TFM yet (as of April 2026), but the .NET Standard 2.0 path is stable. |
| MSAL version pinning | PnP.Framework 1.18.0 requires `Microsoft.Identity.Client.Extensions.Msal >= 4.70.2`. Installing 4.83.3 satisfies this constraint. Pin to 4.83.x to avoid drift. |
| Microsoft.Graph SDK major version | Use 5.x only. The 4.x to 5.x upgrade introduced Kiota-generated code with significant breaking changes. Do not mix 4.x and 5.x packages. |
| CommunityToolkit.Mvvm source generators | 8.4.2 introduces partial properties support requiring C# 13 / .NET 9+ SDK. On .NET 10 this is fully supported. |
| ScottPlot.WPF + SkiaSharp | ScottPlot 5.x bundles SkiaSharp. Ensure no version conflict if SkiaSharp is pulled in by another dependency. ScottPlot.WPF 5.1.57 bundles SkiaSharp 2.88.x. |

---

## Sources

- NuGet: https://www.nuget.org/packages/PnP.Framework/ — version 1.18.0 confirmed, .NET targets confirmed
- NuGet: https://www.nuget.org/packages/Microsoft.Graph/ — version 5.103.0 confirmed
- NuGet: https://www.nuget.org/packages/microsoft.identity.client — version 4.83.1 confirmed
- NuGet: https://www.nuget.org/packages/Microsoft.Identity.Client.Extensions.Msal/ — version 4.83.3 confirmed
- NuGet: https://www.nuget.org/packages/CommunityToolkit.Mvvm/ — version 8.4.2 confirmed
- NuGet: https://www.nuget.org/packages/ScottPlot.WPF — version 5.1.57 (stable), 5.1.58 (latest as of March 2026)
- NuGet: https://www.nuget.org/packages/serilog/ — version 4.3.1 confirmed
- Microsoft Learn: https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview — PublishSingleFile guidance, .NET 8+ SelfContained behavior change
- .NET Blog: https://devblogs.microsoft.com/dotnet/announcing-dotnet-10/ — .NET 10 LTS November 2025 GA
- .NET Support Policy: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core — LTS lifecycle dates
- PnP Framework GitHub: https://github.com/pnp/pnpframework — .NET targets, auth patterns
- PnP Framework vs Core comparison: https://github.com/pnp/pnpframework/issues/620 — authoritative guidance on which library to use
- MSAL token cache: https://learn.microsoft.com/en-us/entra/msal/dotnet/how-to/token-cache-serialization — cache serialization patterns
- CommunityToolkit 8.4 announcement: https://devblogs.microsoft.com/dotnet/announcing-the-dotnet-community-toolkit-840/ — partial properties, .NET 10 support

---

*Stack research for: SharePoint Online administration desktop tool (C#/WPF)*
*Researched: 2026-04-02*
