Repository navigation
Development
csharpdialog/
├── .github/workflows/
│ ├── ci.yml Build on PR and push to main
│ └── release.yml Build, package and publish on a v* tag
├── src/
│ ├── csharpDialog.Core/ Library: config model, parsers, services
│ │ ├── CommandLineParser.cs Every CLI flag is parsed here
│ │ ├── DialogConfiguration.cs The configuration model and its defaults
│ │ ├── IDialogService.cs The front-end contract, the factory, and the console front end
│ │ ├── Models/ Command, ListItemConfiguration, ListItemStatus, JSON schema, themes
│ │ └── Services/ Command parser/watcher, Cimian monitor, first-run detection,
│ │ icon cache, file log, and the unwired manager/theme/style layers
│ ├── csharpDialog.CLI/ dialog.exe — the shipped entry point
│ └── csharpDialog.WPF/ The windows and WpfDialogService; also builds its own host exe
├── docs/ Long-form notes (predate this wiki; verify against source)
├── examples/ PowerShell examples
├── build.ps1 Local build, sign, MSI and .pkg packaging
├── build-info.yaml Cimian package metadata template
└── CsharpDialog.sln
Note the disk casing: directories are CsharpDialog.*, the namespaces and project files
are csharpDialog.*. Both spellings appear in paths inside the repository and in
build.ps1; this is harmless on Windows but will bite on a case-sensitive filesystem.
| Tool | Version | Needed for |
|---|---|---|
| .NET SDK | 10.0 (preview quality is what CI uses) | Everything. All three projects target net10.0-windows
|
| WiX Toolset CLI | 6.x, installed as a global dotnet tool | MSI output only |
Windows SDK (signtool.exe) |
any recent | Signing only |
dotnet tool install --global wix --version "6.*"dotnet build CsharpDialog.sln -c ReleaseThe CLI's AssemblyName is dialog, so the binary is
src\csharpDialog.CLI\bin\Release\net10.0-windows\dialog.exe.
Run it in place:
dotnet run --project src/csharpDialog.CLI -- --window --title "Dev" --message "Hello".vscode/tasks.json defines build and run-cli tasks for the same two commands.
There is no test project. dotnet test finds nothing and reports success — do not read
that as a passing suite. Unit tests are on the repository's roadmap as unimplemented.
Verification today is manual. The examples/ directory is the closest thing to a test
matrix, covering basic messages, progress, list items, buttons, sizing, the command file,
fullscreen, kiosk, an install tracker and an onboarding flow. Note that several of those
scripts pass --progressbar, which is not a real flag — it is silently ignored, so they
still work, but do not copy that token into new code.
A quick smoke test of the paths that matter:
dialog --window --title "Smoke" --message "Window front end" --timeout 5$f = "$env:TEMP\smoke.txt"; Remove-Item $f -ErrorAction SilentlyContinue
Start-Process dialog -ArgumentList "--window","--progress","--commandfile",$f
Start-Sleep 3
Add-Content $f "listitem: add, title: Item, status: wait" -Encoding UTF8
Add-Content $f "listitem: update, title: Item, status: success" -Encoding UTF8
Add-Content $f "progress: 100" -Encoding UTF8
Add-Content $f "quit" -Encoding UTF8| Switch | Effect |
|---|---|
-Build |
dotnet build the solution and queue the built executables for signing |
-Sign |
Sign the queued files with signtool
|
-Msi |
Publish self-contained per runtime, harvest the output into a generated WiX v4-schema source, and build an MSI per architecture |
-Pkg |
Publish per runtime and assemble a Cimian .pkg (a zip of build-info.yaml, payload/ and scripts/postinstall.ps1) |
-All |
All four |
-SkipMsi, -SkipPkg
|
Turn the corresponding step back off |
-Configuration |
Default Release
|
-Runtime |
Default win-x64, win-arm64
|
With no switches at all it behaves as -Build -Sign -Msi -Pkg. Artifacts land in
dist\.
Things worth knowing before changing it:
-
Version is a build timestamp,
yyyy.MM.dd.HHmm, generated at run time. The MSI version is that with the century stripped and leading zeros removed (2026.09.03.1030→26.9.3.1030) so it fits the installer's four-field limit. -
The upgrade code is persisted in a
.wix-upgrade-codefile at the repo root, created on first use. Do not delete it — a new upgrade code makes every future MSI a side-by-side install rather than an upgrade. - Component GUIDs are regenerated on every build. Combined with the harvest-everything approach this is fine for a fresh install and an upgrade that replaces the whole payload, but it rules out patching.
- The MSI adds
INSTALLFOLDERto the system PATH and writesHKLM\Software\csharpDialogwithInstallPathandVersion. - Published output is self-contained, so releases do not require a .NET runtime on the target.
- Publishing signs the published executables before they are packaged, so the MSI and
.pkgpayloads carry signatures rather than being signed only on the outside. - Signing reads the certificate subject from
SIGNING_CERT_CN, resolvessigntool.exefrom PATH,SIGNTOOL_PATH/SIGNTOOL, or the newest Windows Kits bin directory, and retries up to four times across three timestamp authorities. Ifsigntoolcannot be found it warns once and skips — a build can therefore "succeed" while signing nothing.
ci.yml runs on pull requests to main and pushes to main, on windows-latest, with a
30-minute timeout and per-ref concurrency cancellation. It sets up .NET 10 (preview
quality), caches NuGet by the hash of the project files, restores, and builds the solution
in Release. It does not run tests, package, or sign.
release.yml runs only on a v* tag push, on windows-latest, with contents: write.
In order it:
- Derives the version by stripping the leading
vfrom the tag name. - Installs the WiX 6 CLI.
- Runs
.\build.ps1 -Build -Msi -Pkg— note-Signis deliberately absent, so everything the workflow produces is unsigned. - Collects
*.msi,*.pkg,*.nupkg,*.exeand*.zipfromdist\intorelease\. - Zips the
win-x64andwin-arm64publish directories ascsharpdialog-x64.zipandcsharpdialog-arm64.zip, failing the run if either publish directory is missing. - Generates GitHub release notes via the API, strips the contributor attributions and the "New Contributors" section, prepends a build-provenance block (SDK version, runner OS, a link to the run) and appends signing instructions, then creates the release with every artifact attached.
Merging to main ships nothing. A release only happens when a v* tag is pushed, and
what it publishes is unsigned. Any environment that requires signed binaries has to sign
the release artifacts itself, or build from source with build.ps1 -Sign and a certificate
available.
To cut a release:
git tag v1.2.0
git push origin v1.2.0- C# with nullable reference types and implicit usings enabled across all three projects.
- Namespaces are
csharpDialog.*(lower-casec). Two files useCSharpDialog.*for the theme and styling models; both spellings are imported where needed. - Front ends are added by implementing
IDialogService. The interface is large because it carries the unfinished JSON, theming, styling and shell-execution surface; returningTask.FromResult(false)from members you do not support is the established pattern. - New CLI flags go in the single
switchinCommandLineParser.ParseArguments, and must be added toShowHelp()in the same file. Match the existing shape: lowercase the flag, consume the value token withi++only when a value was actually taken. - New command-file verbs must be added to
CommandParser.ValidCommandsand given a case inWpfDialogService.ProcessCommandSync— a verb in the list without a handler is the root cause of most of the accepted-but-ignored commands documented in this wiki. - Logging goes through
FileLog(Debug/Info/Warn/Error). It never throws: a log that cannot be written must not take a dialog down. Keep script-parseable output on stdout and diagnostics in the log. - The
[DEBUG]Console.WriteLinecalls throughout the WPF path are development scaffolding that reached the shipped build. New code should preferFileLogso stdout stays useful to callers.
See CONTRIBUTING.md in the repository. Issues and pull requests go to
windowsadmins/csharpdialog.
The repository is public. Keep hostnames, machine names, user names, certificate subject names, tenant identifiers and internal URLs out of code, commits, tests and issues — use neutral placeholders.
csharpDialog — MIT licensed — windowsadmins/csharpdialog
Reference
Guides
Internals
Help