Discussion of ClickOnce Packages

This thread is going to be in response to this github issue asking for help in creating a script to allow building a ClickOnce package only a single time, and updating all necessary details at deployment time. Below I will post the script we use, scrubbed slightly to remove any company-specific information.

First, the easy part: the UI for the ClickOnce package. This is a sample ClickOnce site based off an old format, and the first thing our deployments do is deploy this site. This allows for internal download onto fresh servers, and easily displays all the proper info. The only file of note is the html file below, which we set the Octopus feature “Replace variables in Files” to run on. Obviously the company name should be replaced everywhere, and any references to APP_NAME.application would need to be changed to your specific application.

demo.html (15.1 KB)

Then, deployed to the same directory, but without purging files, we deploy the package containing the ClickOnce app. A couple things to note here: We found using OctoPack to package the ClickOnce site was missing out on key files, so we went with a nuspec file instead, just packaging all files in the bin directory post build. We include the .pfx file used to sign the application, as well as various app.ENVIRONMENT.config transformation files, once for each environment. During deployment of the package itself, we use the config transforms feature to run the app.ENVIRONMENT.config files onto the base application config file.

EDIT: Be sure to delete all config transforms as an additional step. After they’ve been run, you don’t want them floating around, as they will get included in the manifest otherwise.

Okay, here’s the final script. It should include the Cert parameters as optional, depending on if you are signing things with the pfx or not. Hopefully most of the script includes appropriate comments or verbose output, but if you have any questions at all, please ask.

Update-ClickOncePackage.ps1 (5.3 KB)

The above should handle the following:

  1. Temporarily rename all files to remove the .deploy extension.
  2. Run mage.exe -Update on the *.manifest file
  3. Re-append the .deploy extension onto all files that previously had it
  4. (Optional) Sign the *.manifest file with either the CertFile or CertHash using mage.exe -Sign
  5. Run mage.exe -Update on the *.application file passing in the new -ProviderUrl and the path to the -AppManifest
  6. (Optional) Sign the *.application file with either the CertFile or CertHash using mage.exe -Sign
  7. Call the setup.exe file passing in /url=

Note that in the script, I incorrectly named two of the variables, deploymentFile and applicationFile. Even though they are confusingly named, the script does work as intended. I would advise for future reference renaming applicationFile -> manifestFile, and then deploymentFile -> applicationFile.

Hi Chris,

Thanks for getting in touch and taking the time to provide this great detailed information! I’m sure it’ll be appreciated by everyone looking to accomplish the same thing. Kudos for sharing! :slight_smile:

Best regards,


I agree with @Kenneth_Bates.

Thanks a lot for sharing this :slight_smile:

I am at the point where I got the script working after changing it to our needs and it works fine when run from Visual Studio.

Now I am strugling at bit with calling it with the right variables from inside Octopus.

Specifically the OutputDir as I am trying to execute the script on the Octopus server before deploying it.

Maybe thats the wrong approach…

Ill continue working on it tomorrow :slight_smile:

@JesperRisager I just got back from some PTO time, so hopefully you were able to resolve the OutputDir issue. If not, here’s what I do when deploying in Octopus.

  1. Set the publish package to have a custom installation directory of #{installDirWeb}, run config transforms, and then delete all transforms
  2. Pass in the following parameters when executing the script:
    -Version ‘#{Octopus.Release.Number}’ -OutputDir ‘#{installDirWeb}’ -ClickOnceUrl ‘#{clickOnceSite}’ - CertificateFile ‘#{certificateFile}’

This allows for execution at deployment time, and since installDirWeb is a variable, you can change the installation directory of the ClickOnce package in different environments.

Sorry for the late reply. We had to make a lot of other changes as well, but in the end we were able to get it all working using a variant of your script. Thank you very much!

1 Like

Hello Chris_Camburn

I applied the same instruction I d’ont know why with octopus “Custom Deployment Scripts” in " “Pre-deployment script” not working ihave this errors
This certificate cannot be used for signing - “dc1bdce4f266f1d88f5c99fe94f2cfce0763e455”
When I replace CertHash by CertFile I have this error :
Internal error, please try again. A certificate chain could not be built to a trusted root authority.

but when I execute the same code in target deploy machine by prompt command (cmd) it’s working fine

my code:

$ConfigPath= “client\Application Files*” | Resolve-Path
Rename-Item -Path “$ConfigPath\Oddo.TrainingProject.Presentation.Shell.exe.config.deploy” -NewName “old.Oddo.TrainingProject.Presentation.Shell.exe.config.deploy”
Move-Item -Path “#{ClientConfigFileName}” -Destination “$ConfigPath\Oddo.TrainingProject.Presentation.Shell.exe.config.deploy”
Get-ChildItem “$ConfigPath*.deploy” | Rename-Item -NewName { $_.Name -replace ‘.deploy’,’’ }

Invoke-Expression “& ‘C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\mage’ -Update ‘$ConfigPath\Oddo.TrainingProject.Presentation.Shell.exe.manifest’”
Invoke-Expression “& ‘C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\mage’ -Sign ‘$ConfigPath\Oddo.TrainingProject.Presentation.Shell.exe.manifest’ -CertHash 79753AEBF417CE7D3335B769185B6A3CB08E3F46”

Get-ChildItem “$ConfigPath*.dll” | Rename-Item -NewName { $.Name -replace ‘.dll’,’.dll.deploy’ }
Get-ChildItem “$ConfigPath*.exe” | Rename-Item -NewName { $
.Name -replace ‘.exe’,’.exe.deploy’ }
Get-ChildItem “$ConfigPath*.config” | Rename-Item -NewName { $.Name -replace ‘.config’,’.config.deploy’ }
Get-ChildItem “$ConfigPath*.ico” | Rename-Item -NewName { $
.Name -replace ‘.ico’,’.ico.deploy’ }
Get-ChildItem “$ConfigPath*.xml” | Rename-Item -NewName { $_.Name -replace ‘.xml’,’.xml.deploy’ }

Invoke-Expression “& ‘C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\mage’ –Update client\Oddo.TrainingProject.Presentation.Shell.application -CertFile D:\App\Certification\My_Cert.pfx -Password xxxxx -AppManifest ‘$ConfigPath\Oddo.TrainingProject.Presentation.Shell.exe.manifest’”