# Signing ClickOnce Artifacts: Resolving Manifest Signing Errors in Multi-Version Deployments
## Summary
Signing ClickOnce artifacts using the `sign` command failed because the target directory contained multiple versioned subfolders. The signing tool attempted to sign every `MyApp.exe.manifest` found across all versions, causing a duplicate file error. Root cause was incorrect path specification for the base directory in the signing command.
## Root Cause
- The `sign` command was invoked on the `.application` file in the root publish directory.
- This command automatically searches for referenced manifest files in the current directory (`/publish`) and all subdirectories (`Application Files\MyApp_*`).
- Multiple manifests were found (one per version), causing the sign tool's duplicate file error due to ambiguous targeting.
- The manual `Target` attempted to sign the `.application` file with an incorrect base path (`$(PublishUrl)` wasn't specific to the current build's version).
## Why This Happens in Real Systems
- ClickOnce deployments retain historical versions in subdirectories (by default) for rollback/cached version support.
- Automated tools (e.g., `sign` command) traverse all subdirectories unless explicitly limited.
- Solutions often hardcode paths assuming single-version deployments or fail to dynamically pass the current build's versioned path.
- Build pipelines lacking cleanup between executions amplify directory clutter.
## Real-World Impact
- **Security Blocking**: Unsigned artifacts trigger OS/AV security warnings ("Unknown Publisher"), blocking installation.
- **Deployment Failure**: Installers fail to run unsigned ClickOnce manifests/applications.
- **User Trust Erosion**: Repeated security warnings erode end-user confidence.
- **CI/CD Breakdown**: Automated deployments stall post-build due to unsigned artifacts.
## Example or Code
**Original Problematic Target:**
```xml
<Target Name="SignClickOnce" AfterTargets="_CopyFilesToPublishFolder">
<Exec Command="sign code trusted-signing $(AssemblyName).application ... -b "$(PublishUrl)"" />
</Target>
Fixed Target Using Dynamic Paths:
<Target Name="SignClickOnceDeploy" AfterTargets="Publish">
<!-- Extract the current versioned path -->
<PropertyGroup>
<VersionDir>$([System.IO.Directory]::GetDirectories(`$(PublishDir)Application Files`, `$(AssemblyName)_*`).GetValue(0))</VersionDir>
</PropertyGroup>
<!-- Sign the version-specific manifest first -->
<Exec Command="sign code trusted-signing "$(VersionDir)\$(AssemblyName).exe.manifest" -tse ... -tscp ... -an "$(AssemblyName)"" />
<!-- Then sign the .application file -->
<Exec Command="sign code trusted-signing "$(PublishDir)$(AssemblyName).application" -tse ... -tscp ... -an "$(AssemblyName)"" />
</Target>
How Senior Engineers Fix It
- Isolate the Current Version Path: Use environment variables/MSBuild properties to capture the current build’s version directory dynamically.
- Sign Manifests First: Explicitly sign the
exe.manifestwithin its versioned subdirectory before signing the top-level.applicationfile. - Leverage Publish Targets: Hook into the
Publishtarget (rather than file copies) for better timing guarantees. - Clean Workspaces: Implement pre-publish cleanup to remove historical artifacts (if retention isn’t required).
- Parameterize Credentials: Store signing credentials in secure variables (e.g., Azure Key Vault) to avoid hardcoded values.
Why Juniors Miss It
- Assumption of Flat Structures: Misunderstanding ClickOnce’s multi-version directory hierarchy.
- Undocumented Dependencies: Unaware that
.applicationfiles implicitly reference manifests in subdirectories. - Tool Behavior Blind Spots: Not realizing
signtraverses subdirectories by default. - Over-Reliance on Tooling: Expecting the signing tool to “just work” without auditing actual directory topology.
- Insufficient Logging: Missing verbose file-existence checks to capture duplicity before signing.