Archive 5 phases (36 plans) to milestones/v1.0-phases/. Archive roadmap, requirements, and audit to milestones/. Evolve PROJECT.md with shipped state and validated requirements. Collapse ROADMAP.md to one-line milestone summary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
26 KiB
Phase 5: Distribution and Hardening - Research
Researched: 2026-04-03 Domain: .NET 10 WPF single-file publishing, localization completeness, reliability verification Confidence: HIGH
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| FOUND-11 | Self-contained single EXE distribution — no .NET runtime dependency for end users | dotnet publish -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true produces single EXE |
| </phase_requirements> |
Summary
Phase 5 delivers the final shipping artifact: a self-contained Windows EXE that runs without any pre-installed .NET runtime, a verified reliable behavior against SharePoint's 5,000-item threshold and throttling, and a complete French locale with proper diacritics.
The core publish mechanism is already proven to work: dotnet publish -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true was tested against the current codebase and produces exactly two output files — SharepointToolbox.exe (~201 MB, self-extracting) and SharepointToolbox.pdb. The EXE bundles all managed assemblies and the WPF native runtime DLLs (D3DCompiler, PresentationNative, wpfgfx, etc.) by extraction into %TEMP%/.net on first run. The msalruntime.dll WAM broker is also bundled. No additional flags beyond PublishSingleFile + IncludeNativeLibrariesForSelfExtract are needed.
The French locale has a documented quality gap: approximately 25 Phase 4 strings in Strings.fr.resx are missing required French diacritics (e.g., "Bibliotheque" instead of "Bibliothèque", "Creer" instead of "Créer", "echoue" instead of "échoué"). These strings display in-app when the user switches to French — they are not missing keys but incorrect values. All 177 keys exist in both EN and FR files (parity is 100%), so the task is correction not addition.
The retry helper (ExecuteQueryRetryHelper) and pagination helper (SharePointPaginationHelper) are implemented but have zero unit tests. The throttling behavior and 5,000-item pagination are covered only by live CSOM context tests marked Skip. Phase 5 must add tests that exercise these code paths without live SharePoint — using a fake that throws a 429-like exception for retry, and an in-memory list of items for pagination.
Primary recommendation: Implement as three parallel workstreams — (1) add PublishSingleFile + IncludeNativeLibrariesForSelfExtract to csproj and create a publish profile, (2) fix the 25 diacritic-missing FR strings and add a locale completeness test, (3) add unit tests for ExecuteQueryRetryHelper and SharePointPaginationHelper.
Standard Stack
Core
| Library | Version | Purpose | Why Standard |
|---|---|---|---|
| .NET SDK publish CLI | 10.0.200 | Self-contained single-file packaging | Built-in, no extra tool |
| xUnit | 2.9.3 | Unit tests for retry/pagination helpers | Already in test project |
| Moq | 4.20.72 | Mock ClientContext for retry test isolation |
Already in test project |
No New Dependencies
Phase 5 adds zero NuGet packages. All capability is already in the SDK and test infrastructure.
Publish command (verified working):
dotnet publish SharepointToolbox/SharepointToolbox.csproj \
-c Release \
-r win-x64 \
--self-contained true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-o ./publish
Output (verified):
SharepointToolbox.exe— ~201 MB self-contained EXESharepointToolbox.pdb— symbols (can be excluded in release by setting<DebugType>none</DebugType>)
Architecture Patterns
Recommended Project Structure Changes
SharepointToolbox/
├── SharepointToolbox.csproj # Add publish properties
└── Properties/
└── PublishProfiles/
└── win-x64.pubxml # Reusable publish profile (optional but clean)
SharepointToolbox.Tests/
└── Services/
├── ExecuteQueryRetryHelperTests.cs # NEW — retry/throttling tests
└── SharePointPaginationHelperTests.cs # NEW — pagination tests
Pattern 1: Self-Contained Single-File Publish Configuration
What: Set publish properties in .csproj so dotnet publish requires no extra flags
When to use: Preferred over CLI-only flags — reproducible builds, CI compatibility
<!-- Source: https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview -->
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<!-- Optional: embed PDB into EXE to keep publish dir clean -->
<!-- <DebugType>embedded</DebugType> -->
</PropertyGroup>
Important: PublishSingleFile must NOT be combined with PublishTrimmed=true. The project already has <PublishTrimmed>false</PublishTrimmed> — this is correct and must remain false (PnP.Framework and MSAL use reflection).
Pattern 2: Testing ExecuteQueryRetryHelper Without Live CSOM
What: Verify retry behavior by injecting a fake exception into ClientContext.ExecuteQueryAsync
When to use: ClientContext cannot be instantiated without a live SharePoint URL
The existing ExecuteQueryRetryHelper.ExecuteQueryRetryAsync takes a ClientContext parameter — it cannot be directly unit tested without either (a) a live context or (b) an abstraction layer. The cleanest approach without redesigning the helper is to extract an ISharePointExecutor interface that wraps ctx.ExecuteQueryAsync() and inject a fake that throws then succeeds.
// Proposed thin abstraction — no CSOM dependency in tests
public interface ISharePointExecutor
{
Task ExecuteAsync(CancellationToken ct = default);
}
// Production adapter
public class ClientContextExecutor : ISharePointExecutor
{
private readonly ClientContext _ctx;
public ClientContextExecutor(ClientContext ctx) => _ctx = ctx;
public Task ExecuteAsync(CancellationToken ct) => _ctx.ExecuteQueryAsync();
}
Then ExecuteQueryRetryHelper.ExecuteQueryRetryAsync gains an overload accepting ISharePointExecutor — the ClientContext overload becomes a convenience wrapper.
Alternatively (simpler, avoids interface): test IsThrottleException directly (it is private static — make it internal static + InternalsVisibleTo the test project), and test the retry loop indirectly via a stub subclass approach.
Simplest path requiring minimal refactoring: Change IsThrottleException to internal static and add InternalsVisibleTo (established project pattern from Phase 2 DeriveAdminUrl). Test the exception classification directly. For retry loop coverage, add an integration test that constructs a real exception with "429" in the message.
Pattern 3: Testing SharePointPaginationHelper Without Live CSOM
What: The GetAllItemsAsync method uses ctx.ExecuteQueryAsync() internally — cannot be unit tested without a live context
When to use: Pagination logic must be verified without a live tenant
The pagination helper's core correctness is in BuildPagedViewXml (private static). The approach:
- Make
BuildPagedViewXmlinternal static (parallel toDeriveAdminUrlprecedent) - Unit test the XML injection logic directly
- Mark the live pagination test as Skip with a clear comment explaining the 5,000-item guarantee comes from the
ListItemCollectionPositionloop
For the success criterion "scan against a library with more than 5,000 items returns complete, correct results", the verification is a manual smoke test against a real tenant — it cannot be automated without a live SharePoint environment.
Pattern 4: Locale Completeness Automated Test
What: Assert that every key in Strings.resx has a non-empty, non-bracketed value in Strings.fr.resx
When to use: Prevents future regressions where new EN keys are added without FR equivalents
// Source: .NET ResourceManager pattern — verified working in test suite
[Fact]
public void AllEnKeys_HaveNonEmptyFrTranslation()
{
var enManager = new ResourceManager("SharepointToolbox.Localization.Strings",
typeof(Strings).Assembly);
var frCulture = new CultureInfo("fr-FR");
// Get all EN keys by switching to invariant and enumerating
var resourceSet = enManager.GetResourceSet(CultureInfo.InvariantCulture, true, true);
foreach (DictionaryEntry entry in resourceSet!)
{
var key = (string)entry.Key;
var frValue = enManager.GetString(key, frCulture);
Assert.False(string.IsNullOrWhiteSpace(frValue),
$"Key '{key}' has no French translation.");
Assert.DoesNotContain("[", frValue!,
$"Key '{key}' returns bracketed fallback in French.");
}
}
Note: The existing TranslationSourceTests.Indexer_ReturnsFrOrFallback_AfterSwitchToFrFR only checks one key (app.title). This new test checks all 177 keys exhaustively.
Anti-Patterns to Avoid
- Adding
PublishTrimmed=true: PnP.Framework and MSAL both use reflection; trimming will silently break authentication and CSOM calls at runtime. The project already has<PublishTrimmed>false</PublishTrimmed>— keep it. - Framework-dependent publish:
--self-contained falserequires the target machine to have .NET 10 installed. FOUND-11 requires no runtime dependency. - Omitting
IncludeNativeLibrariesForSelfExtract=true: Without this flag, 6 WPF native DLLs (~8 MB total) land alongside the EXE, violating the "single file" contract. The publish is otherwise identical. - Using
win-x86RID: The app targets 64-bit Windows. Usingwin-x86would build but produce a 32-bit EXE that cannot use more than 4 GB RAM during large library scans. - Correcting FR strings via code string concatenation: Fix diacritics in the
.resxXML file directly. Do not work around them in C# code. The ResourceManager handles UTF-8 correctly.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| Self-contained EXE packaging | Custom bundler / NSIS installer | dotnet publish --self-contained -p:PublishSingleFile=true |
SDK-native, zero extra tooling |
| Native library bundling | Script to ZIP DLLs | IncludeNativeLibrariesForSelfExtract=true |
SDK handles extraction to %TEMP%/.net |
| Key enumeration for locale completeness | Parsing resx XML manually | ResourceManager.GetResourceSet(InvariantCulture, true, true) |
Returns all keys as DictionaryEntry |
| Retry/backoff logic | Custom retry loops per call site | ExecuteQueryRetryHelper.ExecuteQueryRetryAsync (already exists) |
Already implemented with exponential back-off |
| Pagination loop | Custom CAML page iteration per feature | SharePointPaginationHelper.GetAllItemsAsync (already exists) |
Already handles ListItemCollectionPosition |
Key insight: Every mechanism needed for Phase 5 already exists in the codebase. This phase is verification + correction + packaging, not feature construction.
Common Pitfalls
Pitfall 1: WPF Native DLLs Left Outside the Bundle
What goes wrong: Running dotnet publish -p:PublishSingleFile=true without IncludeNativeLibrariesForSelfExtract=true leaves D3DCompiler_47_cor3.dll, PenImc_cor3.dll, PresentationNative_cor3.dll, vcruntime140_cor3.dll, and wpfgfx_cor3.dll as loose files next to the EXE.
Why it happens: By design — the SDK bundles managed DLLs but not native runtime DLLs by default.
How to avoid: Always set <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract> in the csproj or publish command.
Warning signs: Publish output folder contains any .dll files besides msalruntime.dll (WAM broker — bundled with IncludeNativeLibraries).
Verified: In test publish without the flag: 6 loose DLLs. With the flag: zero loose DLLs (only EXE + PDB).
Pitfall 2: First-Run Extraction Delay and Antivirus False Positives
What goes wrong: On the first run on a clean machine, IncludeNativeLibrariesForSelfExtract causes the EXE to extract ~8 MB of native DLLs into %TEMP%\.net\<hash>\. This can trigger antivirus software and cause a 2-5 second startup delay.
Why it happens: The single-file extraction mechanism writes files to disk before the CLR can load them.
How to avoid: This is expected behavior. No mitigation needed for this tool. If needed: DOTNET_BUNDLE_EXTRACT_BASE_DIR environment variable can redirect extraction.
Warning signs: App appears to hang for 5+ seconds on first launch on a clean machine — this is the extraction, not a bug.
Pitfall 3: PublishTrimmed=true Silent Runtime Failures
What goes wrong: PnP.Framework and MSAL use reflection to resolve types at runtime. Trimming removes types the linker cannot statically trace, causing FileNotFoundException or MissingMethodException at runtime on a clean machine — not during publish.
Why it happens: Trimming is an optimization that removes dead code but cannot analyze reflection-based type loading.
How to avoid: The project already has <PublishTrimmed>false</PublishTrimmed>. Never change this.
Warning signs: App works in Debug but crashes on clean machine after trimmed publish.
Pitfall 4: French Strings Display as Unaccented Text
What goes wrong: Phase 4 introduced ~25 FR strings with missing diacritics. When the user switches to French, strings like "Bibliothèque source" display as "Bibliotheque source". This is not a fallback — the wrong French text IS returned from the ResourceManager.
Why it happens: The strings were written without diacritics during Phase 4 localization work.
How to avoid: Fix each affected string in Strings.fr.resx and add the exhaustive locale completeness test.
Warning signs: Any FR string containing e where é, è, ê is expected. Full list documented in Code Examples section.
Pitfall 5: Retry Helper Has No Unit Test Coverage
What goes wrong: If IsThrottleException has a bug (wrong string detection), throttled requests will fail without retry. This is invisible without a test.
Why it happens: The helper was implemented in Phase 1 but no retry-specific test was created (test scaffolds focused on services, not helpers).
How to avoid: Add ExecuteQueryRetryHelperTests.cs with exception classification tests and a live-stub retry loop test.
Pitfall 6: Assembly.GetExecutingAssembly().Location Returns Empty String in Single-File
What goes wrong: If any code uses Assembly.Location to build a file path, it returns "" in a single-file EXE.
Why it happens: Official .NET single-file limitation.
How to avoid: Code audit confirmed — the codebase uses GetManifestResourceStream (compatible) and AppContext.BaseDirectory (compatible). No Assembly.Location usage found.
Warning signs: Any path construction using Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location).
Code Examples
Single-File Publish csproj Configuration
<!-- Source: https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview -->
<!-- Add to SharepointToolbox.csproj PropertyGroup — verified working 2026-04-03 -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
Verified Publish Command
# Run from SharepointToolbox/ project directory
# Produces: ./publish/SharepointToolbox.exe (~201 MB) + SharepointToolbox.pdb
dotnet publish -c Release -r win-x64 --self-contained true \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-o ./publish
FR Strings Requiring Diacritic Correction
The following strings in Strings.fr.resx have incorrect values (missing accents). Each line shows: key → current wrong value → correct French value:
transfer.sourcelibrary "Bibliotheque source" → "Bibliothèque source"
transfer.destlibrary "Bibliotheque destination" → "Bibliothèque destination"
transfer.mode.move "Deplacer" → "Déplacer"
transfer.conflict.overwrite "Ecraser" → "Écraser"
transfer.start "Demarrer le transfert" → "Démarrer le transfert"
transfer.nofiles "Aucun fichier a transferer" → "Aucun fichier à transférer."
bulkmembers.preview "Apercu (...)" → "Aperçu (...)"
bulksites.execute "Creer les sites" → "Créer les sites"
bulksites.preview "Apercu (...)" → "Aperçu (...)"
bulksites.owners "Proprietaires" → "Propriétaires"
folderstruct.execute "Creer les dossiers" → "Créer les dossiers"
folderstruct.preview "Apercu ({0} dossiers a creer)" → "Aperçu ({0} dossiers à créer)"
folderstruct.library "Bibliotheque cible" → "Bibliothèque cible"
templates.list "Modeles enregistres" → "Modèles enregistrés"
templates.opt.libraries "Bibliotheques" → "Bibliothèques"
templates.opt.folders (ok: "Dossiers")
templates.opt.permissions (ok: "Groupes de permissions")
bulk.confirm.proceed "Continuer" → OK (no diacritic needed)
bulk.result.success "Termine : {0} reussis, {1} echoues" → "Terminé : {0} réussis, {1} échoués"
bulk.result.allfailed "Les {0} elements ont echoue." → "Les {0} éléments ont échoué."
bulk.result.allsuccess "Les {0} elements ont ete traites avec succes." → "Les {0} éléments ont été traités avec succès."
bulk.exportfailed "Exporter les elements echoues" → "Exporter les éléments échoués"
bulk.retryfailed "Reessayer les echecs" → "Réessayer les échecs"
bulk.validation.invalid "{0} lignes contiennent des erreurs. Corrigez et reimportez." → "...réimportez."
bulk.csvimport.title "Selectionner un fichier CSV" → "Sélectionner un fichier CSV"
folderbrowser.title "Selectionner un dossier" → "Sélectionner un dossier"
folderbrowser.select "Selectionner" → "Sélectionner"
Note on templates.* keys: Several templates keys also lack accents: templates.capture "Capturer un modele" → "Capturer un modèle", templates.apply "Appliquer le modele" → "Appliquer le modèle", templates.name "Nom du modele" → "Nom du modèle", etc.
ExecuteQueryRetryHelper — IsThrottleException Unit Test Pattern
// Source: established project test pattern (InternalsVisibleTo + internal static)
// Make IsThrottleException internal static in ExecuteQueryRetryHelper
[Theory]
[InlineData("The request has been throttled — 429")]
[InlineData("Service unavailable 503")]
[InlineData("SharePoint has throttled your request")]
public void IsThrottleException_ReturnsTrueForThrottleMessages(string message)
{
var ex = new Exception(message);
Assert.True(ExecuteQueryRetryHelper.IsThrottleException(ex));
}
[Fact]
public void IsThrottleException_ReturnsFalseForNonThrottleException()
{
var ex = new Exception("File not found");
Assert.False(ExecuteQueryRetryHelper.IsThrottleException(ex));
}
SharePointPaginationHelper — BuildPagedViewXml Unit Test Pattern
// Make BuildPagedViewXml internal static
[Fact]
public void BuildPagedViewXml_EmptyInput_ReturnsViewWithRowLimit()
{
var result = SharePointPaginationHelper.BuildPagedViewXml(null, rowLimit: 2000);
Assert.Equal("<View><RowLimit>2000</RowLimit></View>", result);
}
[Fact]
public void BuildPagedViewXml_ExistingRowLimit_Replaces()
{
var input = "<View><RowLimit>100</RowLimit></View>";
var result = SharePointPaginationHelper.BuildPagedViewXml(input, rowLimit: 2000);
Assert.Equal("<View><RowLimit>2000</RowLimit></View>", result);
}
State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|---|---|---|---|
| Separate installer (MSI/NSIS) | Single-file self-contained EXE | .NET 5+ | No installer needed — xcopy deployment |
| Framework-dependent deploy | Self-contained deploy | .NET Core 3.1+ | No runtime prereq on target machine |
| Native DLLs always loose | IncludeNativeLibrariesForSelfExtract=true |
.NET 5+ | True single-EXE for WPF |
| Manual publish profile | <PublishSingleFile> in csproj |
.NET 5+ | Reproducible via dotnet publish |
Deprecated/outdated:
- ClickOnce: Still exists but not used here — requires IIS/file share hosting, adds update mechanism this project doesn't need.
- MSIX packaging: Requires Developer Mode or certificate signing — overkill for an admin tool distributed by the developer directly.
Open Questions
-
PDB embedding vs. separate file
- What we know:
<DebugType>embedded</DebugType>merges PDB into the EXE;<DebugType>none</DebugType>strips symbols entirely. - What's unclear: User preference for crash debugging — does the admin want to be able to debug crashes post-deploy?
- Recommendation: Default to keeping the separate PDB (current behavior). If release packaging requires it,
<DebugType>none</DebugType>is the correct property.
- What we know:
-
WAM broker (
msalruntime.dll) on clean machines- What we know: With
IncludeNativeLibrariesForSelfExtract=true,msalruntime.dllis bundled into the EXE and extracted on first run. The interactive MSAL login (browser/WAM) uses this DLL. - What's unclear: Whether WAM authentication works correctly when the runtime is extracted vs. being a loose file. Not testable without a clean machine.
- Recommendation: Flag for human smoke test — launch on a clean Windows 10/11 machine, authenticate to a tenant, verify the browser/WAM login flow completes.
- What we know: With
-
EnableCompressionInSingleFiletradeoff- What we know: Setting this true reduces EXE size (201 MB → potentially ~130 MB) but adds ~1-3 second startup decompression delay.
- What's unclear: User tolerance for startup delay vs. distribution size.
- Recommendation: Do not set — the 201 MB EXE is acceptable for an admin tool. Compression adds startup complexity with minimal distribution benefit for this use case.
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | xUnit 2.9.3 |
| Config file | SharepointToolbox.Tests/SharepointToolbox.Tests.csproj |
| Quick run command | dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --no-build -v quiet |
| Full suite command | dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj -v quiet |
Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| FOUND-11-a | PublishSingleFile + IncludeNativeLibrariesForSelfExtract produces single EXE |
smoke (manual) | dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -o /tmp/pub && ls /tmp/pub/*.dll | wc -l (expect 0) |
❌ Wave 0 |
| FOUND-11-b | App launches on clean machine with no .NET runtime | manual smoke | N/A — requires clean VM | manual-only |
| SC-2-retry | ExecuteQueryRetryHelper.IsThrottleException classifies 429/503 correctly |
unit | dotnet test --filter "ExecuteQueryRetryHelper" |
❌ Wave 0 |
| SC-2-retry | Retry loop reports progress and eventually throws after MaxRetries | unit | dotnet test --filter "ExecuteQueryRetryHelper" |
❌ Wave 0 |
| SC-3-fr | All 177 EN keys have non-empty, non-bracketed FR values | unit | dotnet test --filter "LocaleCompleteness" |
❌ Wave 0 |
| SC-3-fr | No diacritic-missing strings appear when language=FR | manual smoke | N/A — visual inspection | manual-only |
| SC-4-paginat | BuildPagedViewXml correctly injects and replaces RowLimit |
unit | dotnet test --filter "SharePointPagination" |
❌ Wave 0 |
| SC-4-paginat | Scan against 5,000+ item library returns complete results | manual smoke | N/A — requires live tenant | manual-only |
Sampling Rate
- Per task commit:
dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --no-build -v quiet - Per wave merge:
dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj -v quiet - Phase gate: Full suite green before
/gsd:verify-work+ manual smoke checklist
Wave 0 Gaps
SharepointToolbox.Tests/Services/ExecuteQueryRetryHelperTests.cs— covers SC-2-retrySharepointToolbox.Tests/Services/SharePointPaginationHelperTests.cs— covers SC-4-paginatSharepointToolbox.Tests/Localization/LocaleCompletenessTests.cs— covers SC-3-frExecuteQueryRetryHelper.IsThrottleExceptionmust be changed fromprivate statictointernal staticSharePointPaginationHelper.BuildPagedViewXmlmust be changed fromprivate statictointernal staticInternalsVisibleTo("SharepointToolbox.Tests")already inAssemblyInfo.cs— no change needed
Sources
Primary (HIGH confidence)
- Official .NET docs: https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview — single-file publish behavior, API incompatibilities,
IncludeNativeLibrariesForSelfExtract - Live publish test (2026-04-03): ran
dotnet publishon actual codebase — confirmed single EXE output with and withoutIncludeNativeLibrariesForSelfExtract - Direct code inspection:
ExecuteQueryRetryHelper.cs,SharePointPaginationHelper.cs,Strings.resx,Strings.fr.resx,SharepointToolbox.csproj
Secondary (MEDIUM confidence)
- Microsoft Q&A: https://learn.microsoft.com/en-us/answers/questions/990342/wpf-publishing-application-into-single-exe-file — confirmed WPF native DLL bundling behavior
- MSAL WAM docs: https://learn.microsoft.com/en-us/entra/msal/dotnet/acquiring-tokens/desktop-mobile/wam — msalruntime.dll behavior with broker
Tertiary (LOW confidence)
- None
Metadata
Confidence breakdown:
- Standard stack: HIGH — publish tested live against actual codebase
- Architecture: HIGH — based on direct code inspection + official docs
- Pitfalls: HIGH — pitfall 1 verified experimentally; others from official docs + established project patterns
- FR string gaps: HIGH — direct inspection of Strings.fr.resx, enumerated 25+ affected keys
Research date: 2026-04-03 Valid until: 2026-07-03 (stable .NET tooling; 90 days reasonable)