I’ve added a new Azure DevOps YAML template for deploying .NET 10 ClickOnce applications to network shares.
The new clickOnceExample.yml shows how to use the templates clickOnceNetBuildAndTest.yml and clickOnceEnvironmentPublish.yml to mimic the Visual Studio publish and streamline the process of building, testing, and deploying ClickOnce desktop applications using modern .NET CLI tools in Azure DevOps pipelines to a newtork drive.
I started from Ramesh Kanjinghat’s post , then upgraded it to .Net 10, removed the Migrations, added versioning, added the App.config to set the environment and split out to reusable templates. GitHub Copilot was helpful in this process.
Then things got difficult and I had many trial and error attempts over may hours 🕰️. I found out I can’t use dotnet publish and have to use msbuild.exe. After looking at many websites, asking GitHub Copilot and trial by error I found a working path.
The Microsoft hosted windows-latest had MSBuild 18 available, but was using MSBuild 17 for publishing which does not support .Net 10 ClickOnce publishing. I had to force the use of MSBuild by setting the msbuildLocation. Hopefully this will not be needed in the future.
I came across these as I researched how to do this:
https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md
,
https://github.com/actions/runner-images/issues/13294
,
https://github.com/actions/runner-images/issues/13291
.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
....
The .pubxml file is required to define the ClickOnce publish settings. Here is an example MyClickOnceApp.pubxml file.
Use this in the msbuild step.
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<!-- The pipeline will increment and override this, matching the version to the BuildId.
Change the pipeline parameter Major and Minor versions as needed
-->
<ApplicationRevision>1</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion>
<ProductName>My App</ProductName>
<PublisherName>AlignedDev</PublisherName>
<TargetFramework>net10.0-windows</TargetFramework>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish>
<GenerateManifests>true</GenerateManifests>
<Install>True</Install>
<InstallFrom>Unc</InstallFrom>
<IsRevisionIncremented>True</IsRevisionIncremented>
<IsWebBootstrapper>False</IsWebBootstrapper>
<MapFileExtensions>True</MapFileExtensions>
<OpenBrowserOnPublish>False</OpenBrowserOnPublish>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net10.0-windows\win-x64\app.publish\</PublishDir>
<PublishProtocol>ClickOnce</PublishProtocol>
<PublishReadyToRun>False</PublishReadyToRun>
<PublishSingleFile>False</PublishSingleFile>
<SelfContained>True</SelfContained>
<SignatureAlgorithm>(none)</SignatureAlgorithm>
<SignManifests>False</SignManifests>
<SkipPublishVerification>false</SkipPublishVerification>
<UpdateEnabled>True</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<CreateDesktopShortcut>False</CreateDesktopShortcut>
<TargetCulture>en-US</TargetCulture>
<UpdateRequired>True</UpdateRequired>
<WebPageFileName>Publish.html</WebPageFileName>
<InstallUrl>\\networkDrive\APPLICATIONS\MyApp\Publish\Dev\</InstallUrl>
<MinimumRequiredVersion>1.0.0.1</MinimumRequiredVersion>
<PublishUrl>C:\git\MyApp\publish\</PublishUrl>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
</Project>
The clickOnceEnvironmentPublish.yml template overrides a lot of these pubxml settings via msbuild arguments.
msbuildArguments: '/target:publish /p:PublishProfileFullPath=${{ parameters.publishProfilePath }} /p:PublishDir=$(Build.ArtifactStagingDirectory) /p:PublishUrl=${{ parameters.clickOncePublishUrl }} /p:InstallUrl=${{ parameters.clickOncePublishUrl }}/ /p:ApplicationVersion=${{ parameters.majorVersion }}.${{ parameters.minorVersion }}.$(Build.BuildId).0 /p:MinimumRequiredVersion=${{ parameters.majorVersion }}.${{ parameters.minorVersion }}.0.0 /p:CreateWebPageOnPublish=true /p:Configuration=${{ parameters.buildConfiguration }} /p:RestorePackages=false'
See the example in the repo.
Here’s a snippet, but the link will be up to date, if I modify it in the future.
variables:
majorVersion: 1
minorVersion: 0
netVersion: "10.x"
projectName: "MyClickOnceApp"
artifactName: "MyClickOnceApp"
rootFolder: "src"
publishUrlDevelopment: "\\\\server\\share\\DEV\\MyApp\\"
publishUrlProduction: "\\\\server\\share\\Prod\\MyApp\\"
${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
isMain: "true"
${{ else }}:
isMain: "false"
selfContained: "true"
stages:
- stage: 'BuildAndTest'
displayName: 'Build, Test and prepare artifacts the Application'
jobs:
- template: clickOnceNetBuildAndTest.yml
parameters:
netVersion: ${{ variables.netVersion }}
rootFolder: ${{ variables.rootFolder }}
projectName: ${{ variables.projectName }}
artifactName: ${{ variables.artifactName }}
isMain: ${{ variables.isMain }}
selfContained: ${{ variables.selfContained }}
majorVersion: ${{ variables.majorVersion }}
minorVersion: ${{ variables.minorVersion }}
- stage: 'DeployClickOnceDev'
displayName: 'Deploy ClickOnce - Development'
dependsOn: BuildAndTest
condition: succeeded()
jobs:
- deployment: 'DeployClickOnceDev'
displayName: 'Deploy ClickOnce to Network Share - Development'
workspace:
clean: all
environment:
name: 'ADO-ClickOnce-Deployment-Development'
resourceType: 'virtualMachine'
strategy:
runOnce:
deploy:
steps:
- checkout: self
clean: true
fetchDepth: 1
- template: clickOnceEnvironmentPublish.yml
parameters:
environmentName: Development
rootFolder: ${{ variables.rootFolder }}
projectName: ${{ variables.projectName }}
artifactName: ${{ variables.artifactName }}
clickOncePublishUrl: ${{ variables.publishUrlDevelopment }}
deployRootPath: ${{ variables.publishUrlDevelopment }}
buildConfiguration: ${{ variables.buildConfiguration }}
selfContained: ${{ variables.selfContained }}
isMain: ${{ variables.isMain }}
removeAppSettings:
- appsettings.json
- appsettings.Production.json
majorVersion: ${{ variables.majorVersion }}
minorVersion: ${{ variables.minorVersion }}
This Azure DevOps YAML template simplifies the deployment of ClickOnce applications using .NET 10. By leveraging existing build infrastructure and automating the deployment process to a network drive. I hope this is helpful and you avoid the many hours of trial and error I went through! Happy Deployments! 🚀
Check out my Resources Page for referrals that would help me.