Skip to content

Architecture

Rod Christiansen edited this page Sep 3, 2026 · 1 revision

Architecture

Projects

Project Output Role
csharpDialog.Core library Configuration model, argument parser, command parser, command-file watcher, the IDialogService contract, the console front end, and the Cimian/first-run/icon services
csharpDialog.WPF csharpDialog.WPF.exe (WinExe) The WPF windows and WpfDialogService, the front end the CLI actually drives. Also runnable on its own
csharpDialog.CLI dialog.exe (Exe, AssemblyName = dialog) The shipped entry point

All three target net10.0-windows with UseWPF. The CLI references the WPF project directly for packaging, but loads the WPF service by reflection at runtime (Assembly.LoadFrom on csharpDialog.WPF.dll, then Activator.CreateInstance on csharpDialog.WPF.Services.WpfDialogService), falling back to the console front end if that fails for any reason.

What happens on a run

flowchart TD
    A["dialog.exe Main"] --> B{"args == --autolaunch only?"}
    B -- yes --> C["First-run detection<br/>exit 0 if not first run"]
    C --> D["Built-in kiosk preset"]
    B -- no --> E["CommandLineParser.ParseArguments"]
    E --> F{"--autolaunch present<br/>with other flags?"}
    F -- yes --> G["First-run gate,<br/>then first-run preset"]
    F -- no --> H
    G --> H{"window, fullscreen, kiosk,<br/>firstrun or commandfile?"}
    D --> I
    H -- yes --> I["WpfDialogService<br/>(ProgressDialogWindow)"]
    H -- no --> J["ConsoleDialogService<br/>(stdout + Console.ReadLine)"]
    I --> K{"--cimian?"}
    J --> K
    K -- yes --> L["CimianMonitor starts:<br/>tail run.log, seed from manifests"]
    K -- no --> M["ShowDialog blocks"]
    L --> M
    M --> N["Stop monitor, mark first-run done,<br/>print Result/Timestamp, exit 0 or 1"]
Loading

The STA requirement is handled in Main: on Windows the whole run is marshalled onto a dedicated STA thread before anything WPF-related is touched.

Front ends

There are two IDialogService implementations and, confusingly, two different WPF windows.

WpfDialogService → ProgressDialogWindow

This is what dialog.exe shows. The window is a fixed five-row layout defined in XAML — header, message, scrolling list, progress caption, progress bar, one button — and the service configures it by looking elements up by name (TitleText, MessageText, ListItemsPanel, ProgressText, ProgressBarControl, OkButton) after the window's Loaded event.

That name-lookup approach is why several flags are inert: the service only assigns the handful of properties it explicitly reaches for. Width, height, title, message, button-1 text, button-1 disabled state, initial list items, timeout and the close/keyboard locks are applied. Icon, image, video, colours, fonts, topmost, initial progress value and initial progress text are not.

Theme is not configurable at all here: the window reads the system dark-mode setting and picks one of two hardcoded palettes, and asks DWM for a dark title bar to match.

ConsoleDialogService

The fallback. It prints the title, message, progress line, list and a numbered button menu to stdout and blocks on Console.ReadLine(). It implements far more of IDialogService than the WPF service does — including progressincrement, progressreset, config, style, theme and the execute* verbs — but all of it is Console.WriteLine narration, not a rendering.

Because it blocks on stdin, reaching this front end from a non-interactive context is a hang, not an error. See Troubleshooting.

MainWindow (the csharpDialog.WPF.exe host)

The WPF project also builds its own executable whose App.OnStartup parses the same arguments and shows MainWindow — a different, simpler window that does honour --icon, --image, --backgroundcolor, --textcolor, --fontfamily, --fontsize, --centeronscreen, --topmost, auto-sizing, and multiple buttons.

MainWindow has no list, no progress bar and no command-file support, so it is not useful for the progress-reporting job csharpDialog exists to do. It is best understood as an earlier design that the CLI no longer uses. Scripts should target dialog.exe.

Fullscreen and kiosk

--fullscreen and --kiosk do not maximise the dialog. They create a FullscreenOverlayWindow sized to the whole virtual desktop (all monitors), transparent, topmost, not in the taskbar, with a 57 %-opacity black grid and a DWM acrylic backdrop requested via DWMWA_SYSTEMBACKDROP_TYPE. The dialog window is then loaded, hidden, and its content tree is reparented into a centred Border inside the overlay, capped at 850 × 1200.

Kiosk adds a Closing handler that cancels every close attempt, so the only exits are a quit command or a timeout.

There is no admin escape hotkey. Some older internal notes mention one; it does not exist in the code.

The command-file loop

sequenceDiagram
    participant S as Script
    participant F as Command file
    participant W as FileSystemWatcher
    participant P as CommandParser
    participant U as UI thread
    S->>F: Add-Content "progress: 60"
    F->>W: Changed (LastWrite / Size)
    W->>W: debounce 100 ms
    W->>F: read from stored byte offset
    W->>P: ParseCommand per line
    P->>U: Dispatcher.Invoke(ProcessCommandSync)
    U->>U: set ProgressBarControl.Value
Loading

State that matters: the monitor keeps a byte offset and only ever reads forward. Truncating or rewriting the file while a dialog is open desynchronises it — a shorter file simply produces no reads until it grows past the old offset again.

Commands are applied on the UI thread via Dispatcher.Invoke, so ordering within the file is preserved.

Cimian monitoring

When --cimian is present a CimianMonitor runs alongside the dialog:

  1. Refuses to start unless first-run detection says this is a first-run scenario.
  2. Resolves a log to tail — reports\run.log, else the newest logs\<date>\<time>\run.log.
  3. Ensures managedsoftwareupdate is running, starting it if not.
  4. Scans every cached manifest for managed_installs: entries with a simple line scan (no YAML dependency) and adds one pending list row per unique name. With no manifests it falls back to a three-item placeholder list.
  5. Tails the log and matches four regular expressions — installation started, download or installation progress with a percentage, installation complete, and installation error — translating each into a listitem update.

The regexes are exact-phrase matchers. If the client's log wording differs, the monitor tails happily and updates nothing.

Where state lives

Everything durable is listed in Configuration. In summary: a rolling diagnostic log under %ProgramData%\ManagedNotifications\logs\, an optional icon cache under %ProgramData%\csharpDialog\IconCache\, the caller's command file wherever the caller put it, and first-run completion markers in the registry. No dialog state survives the process.

Unfinished layers

csharpDialog.Core also contains DialogManager, ProgressManager, ThemeService, StylingService, JsonConfigurationService and ShellCommandService. They compile and have real implementations, but the WPF service stubs out every corresponding IDialogService member (returning false or an empty result), and no CLI flag or command verb routes to them. Do not plan around them.

Clone this wiki locally