Funcions de System.Deployment.Application
Aquestes ja no estan disponibles amb NET6 [i als docs se fa referència] però afortunadament es poden simular via lectura de les variables d’entorn [informació en aquesta request a github] , dels fitxers de clickonce i dels directoris propis del desplegament.
Fent servir part del codi d’exemple amb de desplegament de una aplicació NET6 WPF com aquest luncher he fet una llibreria tipificant les constants del desplegament que més fem servir així com simular el update silenciós de clickonce que en realitat no és un altre cosa que instal·lar per enrere i apagar la aplicació un cop està instal·lada la nova versió (el restart de NET Framework ara no funcionarà):
https://github.com/Ruekov/ClickOnceNET6
MSBuild
La preferència de les aplicacions amb NET6 és preferible utilitzar la comanda dotnet msbuild el problema més important en aquest cas és que clickonce es fonamenta bàsicament en NET Framework 3.5 així que haurem d’utilitzar MSBuild.
Com passava ja amb MSBuild i clickonce, els perfils o les ordes de publicació es comporten diferent amb Visual Studio que executades des MSBuild. A més a més, tenim el handicap de que les eines de msbuildtasks de loresoft no acaben de funcionar bé.
Per aquesta raó utilitzant els perfils de publicació (Propierties/PublicationProfiles/ClickOnceProfile.pubxml) amb paràmetres postcompilació afegirem la tasca què:
- Separi els fitxers de desplegament (setup.exe, xxxxx.application, launcher.exe i carpeta ApplicationFiles)
- Copi un template del index.html per personalitzar-lo (de nou MSBuild no és capaç de fer-lo posant-hi el WebPageFileName a true)
- Faci un ZIP amb els continguts del deplegament
<Target Name="ZipPublishOutput" AfterTargets="ZipDeployment">
<ItemGroup>
<LauncherFile Include="$(PublishDir)\Launcher.exe"/>
<ApplicationFile Include="$(PublishDir)\$(MSBuildProjectName).application"/>
<SetupFile Include="$(PublishDir)\setup.exe"/>
<ApplicationFiles Include="$(PublishDir)\Application Files\**\*.*"/>
</ItemGroup>
<Copy SourceFiles="$(ProjectDir)\index.html" DestinationFiles="$(PublishDir)\..\..\index.html"/>
<WriteLinesToFile File="$(PublishDir)\..\..\index.html" Lines="$([System.Text.RegularExpressions.Regex]::Replace($([System.IO.File]::ReadAllText('$(PublishDir)\..\..\index.html')), ''{AppVersion}'', ''$(AssemblyVersion)''))" Overwrite="true" Encoding="Unicode" />
<WriteLinesToFile File="$(PublishDir)\..\..\index.html" Lines="$([System.Text.RegularExpressions.Regex]::Replace($([System.IO.File]::ReadAllText('$(PublishDir)\..\..\index.html')), ''{AppName}'', ''$(MSBuildProjectName)''))" Overwrite="true" Encoding="Unicode" />
<Copy
SourceFiles="@(LauncherFile)"
DestinationFolder="$(PublishDir)\..\..\"
/>
<Copy
SourceFiles="@(SetupFile)"
DestinationFolder="$(PublishDir)\..\..\"
/>
<Copy
SourceFiles="@(ApplicationFile)"
DestinationFolder="$(PublishDir)\..\..\"
/>
<Copy
SourceFiles="@(ApplicationFiles)"
DestinationFolder="$(PublishDir)\..\..\Application Files\%(RecursiveDir)"
/>
<RemoveDir Directories="$(OutputPath)" />
<ZipDirectory SourceDirectory="$(PublishDir)\..\..\" DestinationFile="$(PublishDir)\..\..\..\PRO_$(MSBuildProjectName)_$(AssemblyVersion).zip" Overwrite="true" />
</Target>
Aquest Target haurà de estar especificat al csproj del projecte:
<Target Name="ZipDeployment">
</Target>
Al mateix csproj podrem afegir una tasca per mantenir actualitzat als prefils de publicació el número de versió:
<Target Name="updateVersionCLickOnce">
<PropertyGroup>
<FilePath>.\Properties\PublishProfiles\ClickOnceProfile.pubxml</FilePathPRO>
</PropertyGroup>
<WriteLinesToFile File="$(FilePath)" Lines="$([System.Text.RegularExpressions.Regex]::Replace($([System.IO.File]::ReadAllText('$(FilePath)')), '>\d+\.\d+\.\d+\.\d+<', ''>$(AssemblyVersion)<''))" Overwrite="true" Encoding="Unicode" />
</Target>
Al ser una tasca que hauria de ser executada abans de començar la compilació de la publicació haurem de especificar-la al inici del XML del csproj:
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="updateVersionCLickOnce">
Ara ja podem executar el msbuild generant la sortida correcta i esperada:
msbuild /t:Publish,ZipDeployment /p:DeployOnBuild=true /p:PublishProfile=ClickOnceProfile.xml /restore
Recursos
- Perfils de publicació amb VS2022: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/visual-studio-publish-profiles?view=aspnetcore-6.0
- Perfils de publicació amb VS2022: ClickOnce https://learn.microsoft.com/en-us/visualstudio/deployment/building-clickonce-applications-from-the-command-line?view=vs-2022
- Crear un arxiu de projecte per a MSBuild: https://learn.microsoft.com/en-us/visualstudio/msbuild/walkthrough-creating-an-msbuild-project-file-from-scratch?view=vs-2022
- MSBuild doesn’t respect PublishUrl: https://stackoverflow.com/questions/1919625/msbuild-doesnt-respect-publishurl-property-for-my-clickonce-app
- Common MSBuild project propierties: https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-properties?view=vs-2022
- Default.html page is not created: https://developercommunity.visualstudio.com/t/unable-to-publish-a-net-50-winforms-project-via-cl/1457240
- Publish with MSBuild: https://developercommunity.visualstudio.com/t/publish-with-msbuild/848335
- Clickonce not longer works: https://developercommunity.visualstudio.com/t/ClickOnce-no-longer-works/1288425
- How to replace string in file using msbuild?: https://stackoverflow.com/questions/7837644/how-to-replace-string-in-file-using-msbuild
- Using msbuild to execute a File System Publish Profile: https://stackoverflow.com/questions/16246562/using-msbuild-to-execute-a-file-system-publish-profile
- Improve update detection for .NET (Core) applications deployed with ClickOnce https://github.com/dotnet/deployment-tools/issues/27