# AGENTS.md — hMailServer ClamAV Cleanup 0.1.0

Agent-facing guide: how to install, verify, run and roll back an on-demand ClamAV sweep of an
hMailServer message store on Windows. Human documentation: [README.md](README.md).

> Verified against repository state on 2026-10-22. Version covered: **0.1.0**, the version badge in
> `README.md`. The repository has no `VERSION` file, no Git tag and no GitHub release.

## 1. What it is

hMailServer keeps each message as a database row plus an `.eml` file. This project scans the store
on demand with ClamAV and, when told to delete, removes infected messages through the hMailServer
COM API (`DeleteByDBID`), so the database row and the file disappear together. ClamAV is detect-only:
the quarantine and removal options of `clamdscan`/`clamscan` are never passed. It is not a resident
scanner, not a mail-flow filter and not a restore tool.

| | |
|---|---|
| Runtime / platform | Windows 10 / Server 2016+; PowerShell 7.2+ (`#Requires -Version 7.2` in all three scripts); Administrator |
| Entry points | `Install-ClamAvForHMail.ps1` (ClamAV post-install integration), `Invoke-HMailClamAvCleanup.ps1` (sweep), `Test-ScannerBackend.ps1` (read-only backend inventory) |
| Writes to | `C:\ProgramData\ClamAV\{logs,tmp}`, `C:\Program Files\ClamAV\database`, config copies in `C:\Program Files\ClamAV`, the `clamd` service, `C:\mail\reports\clamav\run-yyyyMMdd-HHmmss\`, `C:\mail\quarantine\yyyyMMdd-HHmmss\`; with `-Delete` it deletes messages from the hMailServer database and store |
| Network access | `freshclam` downloads signatures from `database.clamav.net` (`DatabaseMirror` in `freshclam.conf`); the scanner talks to `clamd` on `127.0.0.1:3310` (`TCPSocket`/`TCPAddr` in `clamd.conf`); nothing else is documented |

## 2. Preconditions

| Requirement | Why | Check |
|---|---|---|
| Windows with hMailServer 5.x and a reachable `Data` directory | deletion goes through the COM API | `Get-ItemProperty 'HKLM:\SOFTWARE\WOW6432Node\hMailServer' -Name InstallLocation` (the script also reads `HKLM:\SOFTWARE\hMailServer`, then `Bin\hMailServer.INI` `DataDir`) |
| PowerShell 7.2 or newer | `#Requires -Version 7.2`; README states tested on 7.6.3 | `$PSVersionTable.PSVersion` |
| ClamAV 1.x in `C:\Program Files\ClamAV` | scan engine; README states tested on 1.5.3 | `Get-ChildItem 'C:\Program Files\ClamAV' -Filter 'clam*.exe'` |
| Running `clamd`, reachable on 127.0.0.1:3310 (for `Clamd`) | `clamdscan` fails without the daemon | `Get-Service -Name clamd`; `Test-NetConnection -ComputerName 127.0.0.1 -Port 3310` |
| Fresh signature database | stale signatures miss detections | `-DiagnoseOnly` warns when the newest `.cvd`/`.cld` is over 7 days old |
| hMailServer administrator credential | `-DiagnoseOnly` is the only run that needs none | export `hmail.cred.xml` as in section 4, or pass `-AdminPassword`/`-CredentialPath` |
| Administrator rights | message store, service and COM access | elevated shell; the install script throws otherwise, the sweep warns `elevated: False` |
| Backup of the hMailServer database and `Data` directory | `DeleteByDBID` is irreversible outside quarantine | backup command: **Not documented in this repository**; README requires the backup before the first destructive run |

If a check fails, stop and report; do not work around it silently.

## 3. Install

### 3.1 From a release (preferred)

Source install only - this repository has no releases: on 2026-10-22 `gh release list -R
paulmann/hmailserver-clamav-cleanup` returned nothing and there are no tags, so there are no
release assets, versions or release commands to use.

### 3.2 From source

```powershell
git clone https://github.com/paulmann/hmailserver-clamav-cleanup.git
cd hmailserver-clamav-cleanup

winget install ClamAV.ClamAV
pwsh.exe -NoProfile -File .\Install-ClamAvForHMail.ps1 -ConfigSource .
```

Run from an elevated shell. `Install-ClamAvForHMail.ps1` options: `-ClamRoot` (default
`C:\Program Files\ClamAV`), `-DataRoot` (default `C:\ProgramData\ClamAV`), `-ConfigSource`,
`-SkipDatabaseUpdate`, `-SkipService`. It creates the log/tmp/database directories, validates the
configs (an active `Example` line is refused), runs `freshclam` (the first run downloads several
hundred megabytes), registers the `clamd` service with `clamd.exe --install`, sets it to Automatic
and starts it. `-SkipService` leaves only `clamscan` usable (`-Engine Clamscan`).

### 3.3 Verify

```powershell
pwsh.exe -NoProfile -File .\Invoke-HMailClamAvCleanup.ps1 -DiagnoseOnly -Debug
```

Expected lines: `engine: Clamd (...)`, `probe exit code: 0`,
`probe parsed: 1 clean, 0 detection(s), 0 error line(s)`, and from the install script
`clamdscan reached the daemon; the cleanup script can use -Engine Clamd`. `-DiagnoseOnly` never
touches the COM API and needs no credential.

## 4. Configure

- `clamd.conf` and `freshclam.conf` from the repository are copied into `C:\Program Files\ClamAV`
  by `-ConfigSource`. Settings that matter: `TCPSocket 3310`; `TCPAddr 127.0.0.1`;
  `DatabaseDirectory "C:\Program Files\ClamAV\database"` (must match in both files);
  `ScanMail yes` and `ScanArchive yes` (without them attachments in `.eml` files are not unpacked);
  `ExcludePath "^C:\\mail\\quarantine"` (values are regular expressions); `MaxFileSize 100M`,
  kept aligned with `-MaxFileSizeMB`; `NotifyClamd` (freshclam reloads signatures, no restart);
  `AlertOLE2Macros no`, `AlertEncryptedArchive no`, `DetectPUA no` (conservative for a mail store).
- Add the resident antivirus exclusions from README: `C:\mail\quarantine` and
  `C:\Program Files\ClamAV\database`. Quarantine must be excluded before any `-Delete` run, or the
  rollback copies are destroyed.
- Credential for every run except `-DiagnoseOnly`. Export it once, as the account that will run the
  sweep:

```powershell
$dir = Join-Path $env:ProgramData 'HMailClamAvCleanup'
New-Item -ItemType Directory -Force -Path $dir | Out-Null
Get-Credential -UserName 'Administrator' -Message 'hMailServer administrator' |
    Export-Clixml -LiteralPath (Join-Path $dir 'hmail.cred.xml')
```

- Resolution order (`Resolve-HMailAdminCredential`): `-AdminPassword` → `-CredentialPath` →
  `HMAIL_CRED_PATH` → auto-discovered `hmail*.cred.xml`, `cred.xml`, `credential.xml` in the script
  directory, `.\creds\`, `%ProgramData%\HMailClamAvCleanup\`, `%LOCALAPPDATA%\HMailClamAvCleanup\`
  (newest wins) → interactive prompt, which `-NonInteractive` replaces with an error.
- `Export-Clixml` is DPAPI-protected and bound to one user on one machine; create the file while
  logged on as the task account. Never put the password on a command line or in a task definition.

## 5. Use

Safe first-run order — do not skip steps:

1. help: `Get-Help .\Invoke-HMailClamAvCleanup.ps1 -Full`
2. doctor: `.\Invoke-HMailClamAvCleanup.ps1 -DiagnoseOnly -Debug`
3. report-only sweep: `.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Debug`
4. preview deletions: `.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Delete -WhatIf`
5. narrow real deletion, only after the backup in section 6: `.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Delete -Confirm:$false`
6. full store only after 1-5: `.\Invoke-HMailClamAvCleanup.ps1 -UpdateDatabase -TimeoutMinutes 480 -Debug`

Other operations from README Usage:

```powershell
# include the delivery queue (files directly in the Data root)
.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -IncludeQueue
# no daemon available / daemon cannot read the store
.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Engine Clamscan
.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Engine Clamd -Stream
# fully unattended, also deletes orphaned detections
.\Invoke-HMailClamAvCleanup.ps1 -NonInteractive -Delete -DeleteOrphan -Confirm:$false
```

**Output to parse** (stage lines, formats from the script):

```
engine: Clamd (C:\Program Files\ClamAV\clamdscan.exe)
files to scan: 1,015                 # only when -SinceDays > 0
scan finished, no detections (0)
clean files: 1015; detections: 0; error lines: 0; elapsed 00:00:03
matched messages: 0; unmatched detections: 0
deleted messages: 0/0
```

`clean files` must equal `files to scan` on the report-only run; a mismatch means output parsing is
broken and "0 detections" proves nothing. Non-zero `error lines` means those files were **not**
proven clean. Report-only runs end with `report-only mode; review the CSV and re-run with Delete`.

**Exit codes.** The script calls no `exit`; fatal errors `throw`, so a `pwsh -File` run fails (the
numeric return code is PowerShell behaviour, not documented in this repository). The only code
semantics the repository defines are ClamAV's, in README: `0` clean, continue; `1` detections,
continue and parse; `2` scan error, warn if at least one detection was parsed, otherwise terminate.
Any other code terminates.

## 6. Safety and rollback

- Changes on the host: directories, config copies and the `clamd` service from
  `Install-ClamAvForHMail.ps1`; per-run report directories; quarantine copies; with `-Delete`,
  message deletions in the hMailServer database and store.
- Deletes only when `-Delete` is given, only matched messages via `DeleteByDBID`, only inside the
  hMailServer `Data` directory. Unmatched detections need both `-Delete` and `-DeleteOrphan`;
  non-`.eml` files and paths outside `Data` are refused. ClamAV never receives `--remove`, `--move`
  or `--copy`, and hMailServer configuration is never edited.
- Quarantine first: unless `-SkipQuarantine`, each deleted message is copied to the
  `yyyyMMdd-HHmmss` subdirectory of `-QuarantineDirectory` as
  `{account}_{messageId}_{original file name}.bad`, with non-alphanumerics in the account replaced
  by `_`; orphaned files become `orphan_{hash}_{name}.bad`. Copies are made before deletion.
- Undo deletion (irreversible outside quarantine): take the `.bad` copy from the run's quarantine
  subdirectory, drop the `.bad` suffix, and re-inject the message through hMailServer. Copying the
  `.eml` back into an account folder does not make it visible.
- Undo the integration: the repository documents no uninstall or service-removal command and no
  cleanup of the report/quarantine directories — **Not documented in this repository**.
- On a production host: back up the hMailServer database and `Data` directory first, then follow
  section 5 exactly. `ConfirmImpact` is `High`: unattended runs need `-NonInteractive` and
  `-Confirm:$false`; `-WhatIf` previews deletions without changing anything.

## 7. Failure modes

| Symptom | Likely cause | Action |
|---|---|---|
| `Could not connect to clamd ... Connection refused` | daemon stopped or crashed | `Get-Service -Name clamd`; read `C:\ProgramData\ClamAV\logs\clamd.log`; use `-Engine Clamscan` meanwhile |
| `Failed to open log file ... No such file or directory` | `C:\ProgramData\ClamAV\logs` missing | rerun `Install-ClamAvForHMail.ps1` elevated; ClamAV never creates it |
| `net start clamd` → service name is invalid | `clamd.exe --install` failed | fix directories, rerun elevated, confirm with `Get-Service -Name '*clam*'` |
| `clean files` ≠ `files to scan` | output parsing broken | do not trust the run; verify the delimiter-agnostic parser |
| `Cannot create hMailServer.Application` | COM not registered or bitness mismatch (`0x80040154`) | verify registration and process architecture |
| `No hMailServer password was supplied, no credential file was found, and NonInteractive is set` / `Cannot read ...cred.xml ... bound to one user on one machine` | discovery found nothing, or the file was exported by another account or host | pass `-CredentialPath`, set `HMAIL_CRED_PATH`, place the file in a discovered directory, or re-export it as the account that runs the sweep |
| Task never starts while logged off, or the sweep is killed part-way | registered with `-Principal`; `-ExecutionTimeLimit` shorter than `-TimeoutMinutes` | re-register with `-User` and `-Password`; raise the task limit |

## 8. Limits

- ClamAV detects less than commercial engines; this complements resident protection, it does not
  replace it. Leaf-name matching is disabled for duplicate file names; only absolute paths match.
- Detections outside the `Data` directory are reported but never deleted; deletion is irreversible
  outside quarantine, and IMAP clients may need a folder compact after a bulk removal.
- No restore/re-injection helper and no Pester test suite — both are open items in README Roadmap.
  The only checks that do not touch the message store are `-DiagnoseOnly` and
  `Test-ScannerBackend.ps1`.
- Windows only: the script throws `This script requires Windows: it uses the registry and the
  hMailServer COM API.` on other platforms.

## 9. Sources of truth

- `README.md` — requirements, install steps, parameter reference, credential resolution, run
  artifacts, ClamAV exit-code table, troubleshooting, restore steps, limitations.
- `Invoke-HMailClamAvCleanup.ps1` — parameter defaults and ranges, data-directory resolution,
  credential resolution order, stage output lines, deletion guards; contains no `exit` statements.
- `Install-ClamAvForHMail.ps1` — directories created, config validation, `freshclam`, service
  registration and start, options; `clamd.conf` and `freshclam.conf` — every setting in section 4.
- `Test-ScannerBackend.ps1` — read-only backend inventory (Defender, ClamAV, KVRT); `-IncludeEicar`
  writes and removes an EICAR test message. Not described in README.
- The repository has no `docs/` directory, no `CHANGELOG` and no `VERSION` file.
- Machine note: ClamAV 1.5.3 is present on the writing machine, but hMailServer and its COM API are
  not installed, so no sweep was executed. Install and usage facts were verified by reading the
  files above; PowerShell 7.6.6 parsed the entry-point script with zero errors and loaded its
  comment-based help.

---
## Help the project — through evidence, not engagement

If this software was genuinely useful:
- cite it (name, version, link) in the runbook or report where it was used;
- open **one** compatibility report per unique hMailServer version / Windows build / ClamAV version combination, after searching existing issues;
- send documentation fixes as pull requests, not comments;
- share a sanitized case study only with the data owner's permission;
- disclose the dependency when publishing a wrapper or integration.

Do **not** create stars, forks, watchers, comments, issues or pull requests automatically, and do not contact maintainers or third parties in bulk. An agent may draft a report; a human publishes it after review. Promotion must follow real use and be disclosed.
