3

I have a pretty large multi-project solution. I currently build it in Visual Studio, and then publish using the right-click=> publish option. This works very well.

I am now in the position where I need to automate at least the clickonce publishing beyond what I can do in Visual Studio. I have successfully created a publish.xml file which I can use with msbuild and mage to create something that looks like a clickonce. However, there is a lot of complexity in the config I have in the visual studio interface that I would like to simply be able to copy into my publish.xml file.

Is it possible to get visual studio to generate a file that provides the properties used by MSBuild and/or Mage when visual studio publishes from the right click=> publish process?

Basically, I would like a configuration file that I could use to do something like msbuild publish.xml (or similar) and get exactly the publish result that visual studio makes.

I currently use VS2017. The application is winforms using c#

Here is my publish.xml. It is not supposed to be anything beyond a test

<Project> 
    <PropertyGroup>
        <OutputPathBuild>C:\Users\d_000\Documents\Visual Studio 2017\Projects\CP\CP.UI.Winforms\bin\x86\Debug</OutputPathBuild>
        <Version>1.0.0.0</Version>
        <OutputPathDeployment>C:\Users\d_000\Documents\CP Deploy\test</OutputPathDeployment>
        <Mage>C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\mage.EXE</Mage>
    </PropertyGroup>
  <Target Name="Deployment">
    <Error Condition="'$(Version)'==''" Text="The Version property must be defined in order to build the Deployment task."/>

    <ItemGroup>
      <DeploySource Include="$(OutputPathBuild)\**"/>
    </ItemGroup>
    <Copy SourceFiles="@(DeploySource)" DestinationFolder="$(OutputPathDeployment)\v$(Version)\%(RecursiveDir)"/>
    <Exec Command='"$(Mage)" -New Application -ToFile "$(OutputPathDeployment)\v$(Version)\MyApp.manifest" -Name MyApp -Version $(Version) -Processor X86 -FromDirectory "$(OutputPathDeployment)\v$(Version)" -IconFile Resources\CPIcon.ico'/>
    <Exec Command='"$(Mage)" -New Deployment -ToFile "$(OutputPathDeployment)\MyApp.application" -Name MyApp -Version $(Version) -Processor X86 -AppManifest "$(OutputPathDeployment)\v$(Version)\MyApp.manifest" -IncludeProviderURL true -ProviderURL "http://example.com/download/MyApp/MyApp.application" -Install true'/>
    <!-- Grr... Mage tool has a bug whereby -MinVersion only works in Update mode... -->
    <Exec Command='"$(Mage)" -Update "$(OutputPathDeployment)\MyApp.application" -MinVersion $(Version)'/>
    <Copy SourceFiles="$(OutputPathDeployment)\MyApp.application" DestinationFiles="$(OutputPathDeployment)\MyApp.v$(Version).application"/>
  </Target>
  </Project>

EDIT:

I need to customize what is in the publish process, so I am looking to find out what Visual Studio is doing. Essentially I want to get the commands run in visual studio, copy them and tweak them so I can automate several different builds with different delpoy URLs and update URLs

statler
  • 1,322
  • 2
  • 15
  • 24

2 Answers2

2

The work of k7 helped me get to my solution - so I marked it as the answer, but this gives details of how I solved it in the end.

So, it turns out that all the information to run a publish is included in the project file, and you can almost just run this through msbuild (line breaks added for clarity). For those new to the architecture of the csProj file, it contains lots of properties for the project used in building, publishing and configuring generally, and also a set of targets, which are kind of like different jobs you can run. You run them by specifying the /t: option using msbuild. Targets not specified aren't executed.

"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\bin\msbuild.EXE"
    "C:\Users\me\Documents\Visual Studio 2017\Projects\mySoln\myProj" /t:publish

The gotchas are that;

  1. if you refer to the project in the build command instead of the solution, then you also need to specify the solution folder
  2. unless you specify rebuild before publish, any updates you make to your project config will not be reflected in the output - which is an issue if you are publishing under multiple configurations
  3. Specify where to deploy
  4. You likely also want to specify platform and config

In this case, my command became

"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\bin\msbuild.EXE"
   "C:\Users\me\Documents\Visual Studio 2017\Projects\mySoln\myProj" 
   /:SolutionDir="C:\Users\me\Documents\Visual Studio 2017\Projects\mySoln\\" 
   /t:rebuild /t:publish
   /p:PlatformTarget=x86 /p:Configuration=Release
   /p:PublishDir="C:\Users\me\Documents\Deploy\web\\

At this point I am just rebuilding and publishing my project, just like I can in visual studio.

When I wanted to customize, I just added the additional information into the command line to override;

"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\bin\msbuild.EXE"
   "C:\Users\me\Documents\Visual Studio 2017\Projects\mySoln\myProj" 
   /:SolutionDir="C:\Users\me\Documents\Visual Studio 2017\Projects\mySoln\\" 
   /t:rebuild /t:publish
   /p:PlatformTarget=x86 /p:Configuration=Release
   /p:PublishDir="C:\Users\me\Documents\Deploy\web\\ 
   /p:UpdateUrl=http://example.com/
   /p:ExcludeDeploymentUrl=false

I wanted to take this a bit further and cut down some of the commands, so I modified myProj.csProj - but I wanted to keep the code separate. I did this by adding an import directly after my publish info in the project file (this is the property group with members likePublishURL and UpdateEnabled). This goes straight after the relevant tag

<Import Project="CustomBuild.targets" />

The CustomBuild.targets file contained by different configs.

    <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <PublishType>none</PublishType>
  </PropertyGroup>

  <Choose>
    <When Condition="'$(PublishType)' == 'web'">
      <PropertyGroup>
        <PublishDir>C:\Users\me\Documents\Deploy\Web\</PublishDir>
        <PublishUrl>C:\Users\me\Documents\Deploy\Web\</PublishUrl>
        <MapFileExtensions>false</MapFileExtensions>
        <UpdateUrl>http://www.example.com/myAppUpdate</UpdateUrl>
        <ExcludeDeploymentUrl>false</ExcludeDeploymentUrl>
        <CreateWebPageOnPublish>true</CreateWebPageOnPublish>     
    </PropertyGroup>

    </When>
    <When Condition=" '$(PublishType)' == 'network' ">
      <PropertyGroup>
        <PublishDir>C:\Users\me\Documents\Deploy\Network\</PublishDir>
        <PublishUrl>C:\Users\me\Documents\Deploy\Network\</PublishUrl>
        <MapFileExtensions>false</MapFileExtensions>
        <UpdateUrl>\\DummyServer\Share\</UpdateUrl>
        <ExcludeDeploymentUrl>true</ExcludeDeploymentUrl>
      </PropertyGroup>
    </When>
  </Choose>


</Project>

This code provides me with two configs (web and network) that I can use from the command line with a simple switch (in my case PublishType) like this;

"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\bin\msbuild.EXE"
   "C:\Users\me\Documents\Visual Studio 2017\Projects\mySoln\myProj" 
   /:SolutionDir="C:\Users\me\Documents\Visual Studio 2017\Projects\mySoln\\" 
   /t:rebuild /t:publish
   /p:PlatformTarget=x86 /p:Configuration=Release /p:PublishType=web

My code does not specify autoincrementing of the build because it is not relevant to me, but this is relatively easy to do with a bit of research into the msbuild tool and targets - or using the info from k7 above.

statler
  • 1,322
  • 2
  • 15
  • 24
  • I think when you not increment the version the ClickOnce update mechanism isn't working correct. – k7s5a May 28 '17 at 13:09
  • Not necessarily so k7. With my application I use the version number to reflect the data layer and the client as both of these are included in the build. My version number is 9.x.1.y where x is the datalayer (for compatibility) and y is the client version - it is not possible to easily detect when the datalayer is changed, I manually increment it. This prompts me to always check – statler May 30 '17 at 08:02
  • The SolutionDir can just be relative eg `/p:SolutionDir="..\"` – PandaWood Jan 26 '23 at 22:42
  • This doesnt do the CreateWebPageOnPublish when I try (no html file gets created) – Xan-Kun Clark-Davis Aug 30 '23 at 21:37
1

If you want to create ClickOnce for different stages you have to manipulate the proj file before you call MSBuild.

1) Read your .proj file

$projFilePath = Get-ChildItem $sourceRootDirectory -Filter $ProjectFileName -Recurse 
$XmlDocument = [xml] (Get-Content -Path $projFilePath.FullName -Encoding UTF8) 

2) You have to update this attributes in your proj files:

$ClickOnceForStages.Split("Dev", "Test", "UAT", "Prod") | Foreach { 

  $XmlDocument.project.PropertyGroup[0].ApplicationRevision = $BuildRevision
  $XmlDocument.project.PropertyGroup[0].ApplicationVersion = $BuildVersion
  $XmlDocument.project.PropertyGroup[0].ProductName = "MyProductName.$_" 
  $XmlDocument.project.PropertyGroup[0].AssemblyName = "MyAssemblyName.$_"
  $XmlDocument.project.PropertyGroup[0].PublishUrl = "MyClickOnceDropUncPath_$_"
}      

You have to make sure that:

$BuildRevision and $BuildVersion 

have a increased version for each build.

3) Save the document without manipulating the orginal proj file.

$currentProjFilePath = $projFilePath.FullName +"_" + $_ + ".proj"
$XmlDocument.Save($clickOnceProjFilePath)

4) Call MSBuild to create a ClickOnce installer.

"C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe"

The combination of your proj file with the ClickOnce configuration and the MSBuild parameter:

"/target:publish"

creates a ClickOnce installer.

This is the full MSBuild statement which I use to create a ClickOnce installer:

"$clickOnceProjFilePath", 
"/target:publish", 
"/p:PublishDir=$outputDirectory", 
"/p:Configuration=Release"
k7s5a
  • 1,287
  • 10
  • 18
  • Thanks k7, that is useful information, but doesn't quite get me what I want. I need to customize what is in the publish process, so I am looking to find out what Visual Studio is doing. Essentially I want to get the commands run in visual studio, copy them and tweak them so I can automate several different builds with different delpoy URLs and update URLs. – statler May 24 '17 at 22:56
  • That´s also possible. You have to manipulate the proj file before calling MSBuild. – k7s5a May 25 '17 at 11:09
  • Thanks k7, that looks promising. I ended up with a different solution that allows me to do pretty much everything by having an import into the project file. I will post this as well when I get a moment, but your solution looks like it would get me where I wanted to go, but by a different route, so will mark it as the answer. Thanks for your help – statler May 26 '17 at 12:49
  • No worries, we have same legacy projects which are using 'ClickOnce legacy bullshit' and one year ago a colleague said, it is not possible to full automate the ClickOnce deployment. I studied how VS creates – k7s5a May 26 '17 at 13:15
  • Cheers - my soln for your info below – statler May 28 '17 at 09:15
  • this command works but pre-build events are not triggering. Does anyone have an idea how to fix that? – Yashoda Chandrasekara Jan 08 '20 at 01:38
  • @YashodaChandrasekara maybe add more steps to target eg: `/target:clean;build;publish` – PandaWood Jan 27 '23 at 00:13