Skip to content

Consolidate GUI Installer into Dashboard "Add Server" flow #755

@erikdarlingdata

Description

@erikdarlingdata

Problem

Three separate entry points for server setup share zero code:

  • CLI Installer (Installer/Program.cs — 2,122 lines) — standalone console app
  • GUI Installer (InstallerGui/Services/InstallationService.cs — 1,552 lines) — completely duplicates CLI logic
  • Dashboard Add Server (AddServerDialog.xaml.cs) — read-only, no install capability

Users who want to monitor a new server must: run the installer separately, then open Dashboard, then add the server. Two apps for one task. Meanwhile, the GUI Installer duplicates all CLI logic instead of sharing it — any bug fix or new script requires changes in two places.

Proposal

Phase 1: Extract Installer.Core shared library

Primary extraction source: InstallerGui/Services/InstallationService.cs — already factored as a service class with IProgress<InstallationProgress>, CancellationToken on all async methods, and static methods. This is a much cleaner extraction base than Program.cs, which has ~600 lines of console-specific code mixed into the logic.

Core extraction:

  • Move connection testing, SQL batch splitting, script discovery/ordering, progress reporting, version detection, upgrade orchestration into a new Installer.Core class library
  • Move community dependency installation (sp_WhoIsActive, DarlingData, First Responder Kit — download with retry/backoff, batch split, execute)
  • Shared types: ServerInfo, InstallationProgress, InstallationResult
  • Expose CancellationToken support throughout async methods so all consumers (CLI, Dashboard) can cancel long-running installs
  • Define result types that map cleanly to the CLI's 9 exit codes (0=Success through 8=UpgradesFailed) — CLI wrapper translates result to exit code, Dashboard translates to user-facing messages
  • Log a warning when an upgrade folder exists but has no upgrade.txt (currently silently skipped in GetApplicableUpgrades())

SQL script packaging — embedded resources with filesystem override:

Dashboard ships via Velopack and has no access to the install/ folder at runtime. To solve this, Installer.Core embeds all SQL scripts as assembly resources at build time:

<!-- Installer.Core.csproj -->
<ItemGroup>
  <EmbeddedResource Include="..\install\**\*.sql" LinkBase="Scripts\install" />
  <EmbeddedResource Include="..\upgrades\**\*" LinkBase="Scripts\upgrades" />
</ItemGroup>

A ScriptProvider abstraction lets consumers choose the source:

public class ScriptProvider
{
    // Dashboard uses this — scripts baked into the assembly
    public static ScriptProvider FromEmbeddedResources();

    // CLI uses this — preserves current filesystem search behavior
    public static ScriptProvider FromDirectory(string path);
}

This means:

  • Dashboard references Installer.Core and scripts just work — no path management, no missing files
  • Scripts are versioned with the assembly — Dashboard 2.5.0 always carries the 2.5.0 scripts
  • CLI workflow unchanged (edit script locally, run installer, iterate)
  • Offline installs work for Dashboard
  • Total embedded size is ~200-300 KB of SQL text — negligible

Logging abstraction:

CLI writes to console, GUI writes to RichTextBox, Dashboard has its own Logger. Installer.Core needs a logging interface beyond IProgress<InstallationProgress>:

  • IProgress<InstallationProgress> for structured progress (status, percentage, step counts)
  • Installation report generation (text file) stays in Installer.Core as an opt-in utility — consumers decide whether to call it
  • Error logging left to consumers (each has their own mechanism)

CLI Installer refactor:

  • CLI references Installer.Core and becomes a thin console wrapper
  • Uses ScriptProvider.FromDirectory() to preserve filesystem discovery
  • Maps InstallationResult to exit codes
  • Interactive password input, console coloring, help text stay in CLI

Phase 2: Integrate installation into Dashboard's Add Server

  • When user clicks "Test Connection" and the PerformanceMonitor database doesn't exist, show an "Install Now" option
  • When the database exists at an older version, show an "Upgrade" option (not just detection — full execution of applicable upgrade scripts)
  • Dashboard already collects server name, auth type, credentials, encryption settings — everything the installer needs
  • Force ReadOnlyIntent=false on the installation connection regardless of the server's Dashboard config (installation requires write access)
  • Installation runs async with progress bar, log output, and cancel button in the dialog
  • Write to config.installation_history on completion (same as both current installers)
  • Generate installation report (text file) on completion — same format as current installers
  • On success: database is installed/upgraded AND server is added to Dashboard in one step
  • Options for clean install, reset schedule, validation exposed as checkboxes (collapsed by default, advanced section)

Phase 3: Retire InstallerGui

  • Remove InstallerGui/ project from solution
  • Remove from build pipeline and release artifacts
  • Update SignPath config (remove InstallerGui from signing, Installers artifact slug now covers CLI only)
  • Update docs to point users at Dashboard for GUI installs, CLI for automation

Credential separation

Installer credentials and monitoring credentials should be treated as distinct:

  • Installer credentials need elevated permissions (sa / sysadmin) to create the database, tables, stored procedures, Agent jobs, and Extended Events sessions. These are used once during setup and should not be persisted.
  • Monitoring credentials only need read access to the PerformanceMonitor database for ongoing Dashboard use. These are what get stored in Windows Credential Manager.

The Add Server flow should handle this cleanly: prompt for installer credentials during the install step (used transiently, not saved), then prompt for or default to lower-privilege monitoring credentials that get stored for Dashboard connections. This avoids leaving sa credentials in Credential Manager and follows least-privilege.

What stays

  • CLI Installer — essential for automation, headless servers, scripting across many servers. Refactored to use Installer.Core.
  • All SQL scripts (install/) — unchanged.

Task checklist

Phase 1: Installer.Core

  • Create Installer.Core class library project, add to solution
  • Implement ScriptProvider with embedded resources and filesystem override
  • Embed all install/**/*.sql and upgrades/**/* as assembly resources
  • Extract shared logic from InstallationService.cs: connection testing, batch splitting, script discovery, progress reporting, version detection, upgrade orchestration
  • Extract community dependency installation (download, retry/backoff, execute)
  • Extract shared models: ServerInfo, InstallationProgress, InstallationResult
  • Define result types mapping to CLI's 9 exit codes (Success, ConnectionFailed, CriticalScriptFailed, etc.)
  • Expose CancellationToken on all async install/upgrade methods
  • Add warning log when upgrade folder exists but upgrade.txt is missing
  • Extract installation report generation as opt-in utility
  • Refactor CLI Installer to reference Installer.Core (thin console wrapper using ScriptProvider.FromDirectory())
  • Retarget Installer.Tests against Installer.Core public API (VersionDetection, UpgradeOrdering, FileFiltering, Idempotency, Adversarial tests)
  • Verify CLI Installer works identically after refactor (all flags, auth modes, exit codes)

Phase 2: Dashboard integration

  • Add Installer.Core reference to Dashboard
  • Modify AddServerDialog to detect missing PerformanceMonitor database on Test Connection
  • Detect existing database at older version and offer Upgrade
  • Add inline installation UI (progress bar, log output, cancel button, advanced options)
  • Force ReadOnlyIntent=false on installation connection
  • Separate installer credentials (transient, elevated) from monitoring credentials (stored, least-privilege)
  • Wire up CredentialService for monitoring credentials only
  • Write to config.installation_history after install/upgrade from Dashboard
  • Generate installation report on completion
  • Test full flow: new server -> detect no DB -> install with sa -> add with monitoring creds
  • Test upgrade flow: existing server with older DB -> upgrade -> add/update in Dashboard
  • Test cancellation mid-install

Phase 3: Retire InstallerGui

  • Remove InstallerGui project from solution and build pipeline
  • Update release artifact list (remove InstallerGui executable)
  • Update SignPath signing config (remove InstallerGui artifact)
  • Update README / docs

Files involved

  • Installer.Core/ — new class library (ScriptProvider, InstallationService, models, result types)
  • Installer.Core/Installer.Core.csproj — embedded SQL resources, Microsoft.Data.SqlClient dependency
  • Installer/Program.cs — refactor to thin wrapper over Installer.Core
  • InstallerGui/Services/InstallationService.cs — primary extraction source, then delete project
  • Installer.Tests/ — retarget tests against Installer.Core
  • Dashboard/AddServerDialog.xaml.cs — add install/upgrade detection + UI
  • Dashboard/Services/CredentialService.cs — reuse for monitoring credentials
  • install/ — SQL scripts (unchanged, embedded into Installer.Core)
  • upgrades/ — upgrade scripts (unchanged, embedded into Installer.Core)

Not in scope

  • Lite (collects its own data, no server-side install needed)
  • Changes to SQL install scripts themselves
  • CLI Installer UX changes beyond refactoring to shared library
  • Dashboard uninstall capability (CLI-only for now — can be added later if needed)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions