Skip to content

Migrate the setup tools to .NET 8 and replace the VB6 migration tools - #15

Merged
chrisholloway5 merged 4 commits into
masterfrom
net8-tools
Aug 10, 2026
Merged

Migrate the setup tools to .NET 8 and replace the VB6 migration tools#15
chrisholloway5 merged 4 commits into
masterfrom
net8-tools

Conversation

@chrisholloway5

@chrisholloway5 chrisholloway5 commented Aug 10, 2026

Copy link
Copy Markdown
Member

Summary

Completes the .NET modernization of the C# tooling: the five setup-tool projects (DBSetup, DBSetupQuick, DBUpdater, DataDirectorySynchronizer, Shared) move from .NET Framework 4.8.1 to SDK-style net8.0-windows, and the unbuildable VB6-era source/Migration tools are replaced by one supported .NET 8 ImportTool (accounts from text files, messages from mbox files).

Key design points

  • Checked-in COM interop: the tools now reference source/Tools/Interop/Interop.hMailServer.dll (tlbimp wrapper, regeneration documented) instead of a build-time COMReference — so they build with plain dotnet build anywhere, and CI gains a tools job with warnings-as-errors, closing the blind spot where tool/test project breakage was invisible to PR checks.
  • Pixel-stable dialogs: ToolApplication.Initialize() pins the WinForms defaults the dialogs were designed against (Microsoft Sans Serif 8.25pt, system-DPI).
  • Installer: tools ship as publish folders; the .NET 8 Desktop Runtime becomes a server-component prerequisite installed from [Code] before the DB tools execute at ssPostInstall (with the bundle's exit code checked); the .NET Framework 4.5 gate is removed; MinVersion raised to Windows 10 1607 (the .NET 8 runtime floor).
  • Silent CLI contract preserved: /silent, password:, /SilentIfOk and window titles are unchanged (verified against the installer's invocations and VMTestRunner's TestEnvironments.json).
  • ImportTool fixes every documented VB6 defect: dropped last message, CRLF mboxes, Thunderbird From - lines, mboxrd quoting, dot-stuffing corruption, SQL-injection-laden direct-MySQL writes (now via Utilities.ImportMessageFromFileToIMAPFolder).

Validation

  • dotnet build clean, zero warnings, all six projects.
  • DBUpdater /SilentIfOk and DBSetupQuick /silent exit 0 against the live 6.2.11 server from a merged Bin layout; DBSetup renders correctly.
  • MboxParser: 7 crafted edge-case tests (LF/CRLF, single-message, Thunderbird, mboxrd, From-in-body, CRLF passthrough) all pass.
  • End-to-end against the live server: CRLF and LF-only mboxes each import completely with correct subjects, senders, header-derived dates and bodies; test domain cleaned up.
  • A five-lens adversarial review (12 findings, 9 confirmed) ran over the branch; every confirmed defect is fixed in the final commit, including a critical LF-only line-ending issue and a latent wizard crash that predates this branch.

DBSetup, DBSetupQuick, DBUpdater, DataDirectorySynchronizer and Shared
move from .NET Framework 4.8.1 old-style projects to SDK-style
net8.0-windows WinForms, following the ControlPanel conventions
(x64, framework-dependent win-x64, pinned bin\$(Configuration) output,
version 6.2.11).

- The hMailServer COM API is now consumed through a checked-in tlbimp
  wrapper (source/Tools/Interop/Interop.hMailServer.dll, regeneration
  documented alongside) instead of a build-time COMReference, so the
  tools build with plain dotnet build on machines without a registered
  typelib - including hosted CI.
- New Shared.ToolApplication.Initialize() pins the WinForms defaults the
  dialogs were designed against (Microsoft Sans Serif 8.25pt,
  system-DPI awareness) so the absolute layouts keep their metrics.
- formEnterPassword's help link sets UseShellExecute, required to open
  a URL on .NET 8; DBSetupQuick resolves DBUpdater.exe relative to the
  executable instead of the working directory.
- AssemblyInfo/Settings/Resources boilerplate, app.config files and the
  per-project solutions are superseded by the SDK and removed;
  "hMailServer Tools.sln" is regenerated listing the five projects.

Validated: dotnet build clean (0 warnings); DBUpdater /SilentIfOk and
DBSetupQuick /silent exit 0 against the live 6.2.11 server from a
merged Bin layout; DBSetup shows the password dialog with correct
rendering.
The Migration folder held four VB6 wizards (ArgoSoft, IMail,
MboxImporter, Textfile) plus the C# Mercury tool. None could be built:
the VB6 projects reference Common\VB sources that are not in the
repository, and Mercury links Common\Admin files that were moved to
Tools\Shared long ago. ArgoSoft, IMail and Mercury migrate from products
that have been dead for roughly two decades and are retired outright.

The two with lasting value return as one supported tool,
source/Tools/ImportTool (net8.0-windows WinForms, same conventions as
the other tools):

- Accounts from a text file: same comma-separated format
  (name,password,maxsize-MB, # comments), now with trimmed fields,
  strict 3-field validation, per-line error reporting instead of a
  crash, and a created/updated/failed summary. Upsert behaviour is kept
  and now stated in the UI.
- Messages from mbox files: one IMAP folder per mbox file (extension
  stripped, "inbox" maps to INBOX). The importer streams the file and
  goes through the supported COM API -
  Utilities.ImportMessageFromFileToIMAPFolder - instead of the VB6
  tool's raw MySQL INSERTs against a decades-old schema.

The new MboxParser fixes every defect documented in the VB6 version:
the last message of a file is no longer dropped, CRLF mboxes parse
correctly, Thunderbird "From - " lines are recognised, mboxrd ">From "
quoting is reversed, and no SMTP dot-stuffing corrupts stored messages.
Verified against crafted LF/CRLF/single-message/mboxrd/From-in-body
samples.
Installer:
- The database tools now ship as dotnet publish folders: DBUpdater,
  DBSetup and DBSetupQuick merge into {app}\Bin (identical shared
  files), DataDirectorySynchronizer keeps its Addons folder, and the new
  ImportTool ships under {app}\Addons\ImportTool with a Start Menu icon.
- The .NET 8 Desktop Runtime becomes a prerequisite of the server
  component, not just the Control Panel: the runtime package is now
  gated "server controlpanel", and a new InstallDotNetRuntime() runs at
  the start of RunPostInstallTasks - the database tools execute from
  [Code] at ssPostInstall, which is before the [Run] section installs
  the runtime, so without this a server-only install would run them on
  a machine with no runtime.
- The .NET Framework 4.5 InitializeSetup gate is removed; it guarded
  the last .NET Framework tools.
- {app}\Bin keeps a copy of Interop.hMailServer.dll (now sourced from
  the checked-in source/Tools/Interop wrapper) for external .NET
  scripts, gated "server admintools". The separate Shared.dll entry for
  admintools-only installs is dropped - nothing in that component used
  a WinForms helper library.

Build/CI:
- build/build-tools.ps1 publishes all five tools; README and AGENTS.md
  document it as the tools build entry point.
- ci.yml gains a "Build .NET tools" job (dotnet build -warnaserror +
  publish smoke check). The tools build against the checked-in interop,
  so hosted runners need no registered typelib - this closes the CI
  blind spot where test/tool project breakage was invisible.
- MboxParser now normalizes line endings to CRLF when writing message
  bytes: the server's message pipeline requires CRLF, so an LF-only
  (Unix) mbox previously produced stored .eml files the server could
  not parse. Verified end to end: an LF-only mbox imports with correct
  subjects, senders, dates and bodies.
- ImportTool writes each message as {account}\XY\{GUID}.eml (the
  server's on-disk convention) so ImportMessageFromFileToIMAPFolder
  never relocates the file; previously a failure after the server's
  internal move orphaned the message at a path the cleanup could not
  see. Cleanup also no longer aborts the import on non-IO errors.
- ucWizard.ShowPage restores the pre-validation button states instead
  of force-enabling all three; force-enabling let a failed validation
  on the first page enable Previous, and clicking it crashed with
  ShowPage(-1). Latent for DBSetup (its first page never fails);
  ImportTool's first pages validate, making it reachable.
- ucMboxSelect skips mail-client index files (.msf/.dat) so a
  Thunderbird profile folder imports cleanly (Inbox.msf previously
  collided with the Inbox mbox and imported garbage).
- ImportTool refuses /silent - the shared wizard would otherwise
  auto-run all pages with empty selections.
- InstallDotNetRuntime checks the runtime bundle's exit code (0/3010 =
  success); Exec only reports launch failures, so a failed install
  previously passed silently and the DB tools then could not start.
  MinVersion raised to Windows 10 1607, the .NET 8 runtime's floor -
  on older Windows the runtime install fails and the server would be
  left without a database.
- build-tools.ps1 cleans each publish folder before publishing;
  dotnet publish never removes orphans, and the installer wildcards
  the folder's entire contents into {app}\Bin.

Findings produced by a five-lens adversarial review (12 raised, 9
confirmed after refutation passes, all fixed).
@github-code-quality

github-code-quality Bot commented Aug 10, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: C#

C# / code-coverage-agent

The overall coverage in commit 51668f1 in the net8-tools branch remains at 100%, unchanged from commit 7a9e402 in the master branch.


Updated August 10, 2026 19:16 UTC

{
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var current = new MemoryStream();
if (messageStarted)
{
onMessage(TrimTrailingBlankLine(current));
current = new MemoryStream();
/// </summary>
private static byte[] ReadLine(Stream stream)
{
var buffer = new MemoryStream();
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
System.Windows.Forms.Application.SetDefaultFont(new Font("Microsoft Sans Serif", 8.25f));
var domain = Globals.GetApp().Domains.get_ItemByName((string) comboDomains.SelectedItem);
var account = domain.Accounts.get_ItemByAddress(address);

SelectedAccount = address;
var account = domain.Accounts.get_ItemByAddress(address);

SelectedAccount = address;
SelectedAccountID = account.ID;
return false;
}

SelectedFile = textFile.Text;
}

SelectedFile = textFile.Text;
SelectedDomain = (string) comboDomains.SelectedItem;
Comment on lines +113 to +117
foreach (var b in line)
{
if (b != '\r' && b != '\n')
return false;
}
Comment on lines +31 to +34
catch (Exception ex)
{
AddToLog("The import failed: " + ex.Message);
}
@chrisholloway5
chrisholloway5 merged commit 2f388fd into master Aug 10, 2026
7 checks passed
@chrisholloway5
chrisholloway5 deleted the net8-tools branch August 10, 2026 19:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant