Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create separate ClickOnce installs that can be installed together by changing assembly name with MSBUILD

I am using an MSBUILD script to create a publish, it looks something like this:

msbuild "<Project>.vbproj" 
          /t:Publish 
          /p:Configuration=Release 
          /p:ProductName="<Application Name> - <Region Name>" 
          /p:PublishDir="<Region Specific>"

So far this is working properly.

Is it possible to use MSBUILD to create multiple publishes to different locations that can be installed together? I know that ClickOnce by default doesn't let you install an application from a different location if the application is the same (I believe it determines this by using the assembly name).

I reviewed this thread here:

Multiple ClickOnce installations with different Deployment Identity, but same Application Identity

And because of it, I modified my script to do this:

msbuild "<Project>.vbproj" 
          /t:Publish 
          /p:Configuration=Release 
          /p:ProductName="<Application Name> - <Region Name>" 
          /p:PublishDir="<Region Specific Unc>"
          /p:AssemblyName="<Application Name>_<Region Name>"

However I get a massive amount of errors (like 1300+) but I'm not sure what's going on. But if I change the assembly name in Visual Studios and build it everything is perfectly fine.

Any thoughts?

like image 614
test Avatar asked Nov 13 '13 22:11

test


People also ask

What is ClickOnce installation?

ClickOnce is a deployment technology that enables you to create self-updating Windows-based applications that can be installed and run with minimal user interaction.

How do I add files to ClickOnce deployment?

To add a file to a groupClick the Publish tab. Click the Application Files button to open the Application Files dialog box. In the Application Files dialog box, select the Group field for a file that you wish to include in the new group. In the Download Group field, select a group from the drop-down list.

How do I publish a ClickOnce application using the Publish Wizard?

In Solution Explorer, right-click the application project and click Properties. The Project Designer appears. Click the Publish tab to open the Publish page in the Project Designer, and click the Publish Wizard button. The Publish Wizard appears.


2 Answers

What happens is that the property AssemblyName is being overriden in all projects msbuild builds in the chain of dependency of your main project. That is causing that many compilation errors. When you change the assemblyName via visual studio you are changing only the main project, which makes it able to build correctly. In one of my projects what i did was add a property on the main .vbproj file called OverridenAssemblyName and set AssemblyName = OverridenAssemblyName when OverridenAssemblyName is not null. That way you can set the AssemblyName only for the project to publish keeping the others intact.

EDIT:

Let's imagine a scenario where you have 2 projects. Project A, which is the project to publish, and Project B, which is referenced by project A. Inside the .vbproj files you have tags like this <AssemblyName>YourProjectName</AssemblyName>. So, in Project A file you would have <AssemblyName>Project A</AssemblyName>, and <AssemblyName>Project B</AssemblyName>in Project B.

That tag is what defines the name of the assembly that is created for the project during the build.

When you pass /p:AssemblyName="<Application Name>_<Region Name>" in your msbuild command line you are overwriting the tag AssemblyName for each project in the build process. And since that property is what defines the assembly name of the project, all projects are having their assemblies generated with the same name. That, probably is the cause of your problem.

A possible solution is to do the following:

  1. Add this to your main project (Project to be published)

    <PropertyGroup Condition="$(PublishAssemblyName) != ''"> <AssemblyName>$(PublishAssemblyName)</AssemblyName> </PropertyGroup>

  2. Change your command line to this:

msbuild "<Project>.vbproj" /t:Publish /p:Configuration=Release /p:ProductName="<Application Name> - <Region Name>" /p:PublishDir="<Region Specific Unc>" /p:PublishAssemblyName="<Application Name>_<Region Name>"

I hope that helps you.

like image 78
Arthur Rizzo Avatar answered Sep 27 '22 15:09

Arthur Rizzo


I was only able to be successful by doing the msbuild/publish once, then write a separate "deploy" program to change and re-sign the manifests as they moved to each environment. It will deploy to QA giving it a new name and config file. Then later "deploy" again to production, giving it a new name and config file at that time as well.

The process requires renaming to remove the .deploy extension, replacing the config file, change the app manifest, change the deployment manifest, (also in my case, update the .xlsx file because I was doing an vsto excel add-in), then resign app manifest, restore the .deploy extension, resign the deployment manifest, and finally copy the results out to the deploy location.

This results in a deploy to QA that when "click-once'd" creates an "Application-QA" in add/remove programs, and a production deploy that when "click-once'd" creates an "Application-PROD". The two can run concurrently because the assembly name and "solutionId" guid have been updated to be different in each environment.

Below is some code on how to change the app and deployment manifests to give them unique names in each environment. If you decide on this approach and would like code for resigning as well I can help.

Private Function UpdateAppManifestBasedOnTarget(caller As IReleaseExecutionCaller, appName As String, appManifestFileInfo As IO.FileInfo) As String

    Log.Write(Me.Name, String.Format("update the app manifest based on the target environment..."))

    Dim appManifestXML As New Xml.XmlDocument()
    Dim appManifestNamespaces As New Xml.XmlNamespaceManager(appManifestXML.NameTable)
    appManifestNamespaces.AddNamespace("asmv1", "urn:schemas-microsoft-com:asm.v1")
    appManifestNamespaces.AddNamespace("vstav3", "urn:schemas-microsoft-com:vsta.v3")
    appManifestNamespaces.AddNamespace("vstov4", "urn:schemas-microsoft-com:vsto.v4")
    appManifestXML.Load(appManifestFileInfo.FullName)
    'assemblyIdentity
    Dim assemblyNode = appManifestXML.SelectSingleNode("/asmv1:assembly/asmv1:assemblyIdentity", appManifestNamespaces)
    assemblyNode.Attributes("name").Value = appName & "-" & caller.Release.EnvironmentCode & ".dll"
    'description
    Dim descNode = appManifestXML.SelectSingleNode("/asmv1:assembly/asmv1:description", appManifestNamespaces)
    descNode.InnerXml = appName & "-" & caller.Release.EnvironmentCode
    'soluionid guid
    Dim custNode = appManifestXML.SelectSingleNode("/asmv1:assembly/vstav3:addIn/vstav3:application/vstov4:customizations/vstov4:customization/vstov4:document", appManifestNamespaces)
    Dim currentGUID = custNode.Attributes("solutionId").Value

    Dim newGuid As String = String.Format("{0:x8}{1}", caller.Release.EnvironmentCode.ToLower.GetHashCode(), currentGUID.Substring(8))
    custNode.Attributes("solutionId").Value = newGuid
    appManifestXML.Save(appManifestFileInfo.FullName)

    Return newGuid

End Function

Private Sub UpdateDeploymentManifestBasedOnTarget(caller As IReleaseExecutionCaller, appName As String, vstoFileInfo As IO.FileInfo)

    Log.Write(Me.Name, String.Format("update the deployment manifest based on the target environment..."))
    Dim vstoXML As New Xml.XmlDocument
    Dim vstoManifestNamespaces As New Xml.XmlNamespaceManager(vstoXML.NameTable)
    vstoManifestNamespaces.AddNamespace("asmv1", "urn:schemas-microsoft-com:asm.v1")
    vstoManifestNamespaces.AddNamespace("asmv2", "urn:schemas-microsoft-com:asm.v2")
    vstoXML.Load(vstoFileInfo.FullName)
    'assemblyIdentity
    Dim assemblyNode = vstoXML.SelectSingleNode("/asmv1:assembly/asmv1:assemblyIdentity", vstoManifestNamespaces)
    assemblyNode.Attributes("name").Value = appName & "-" & caller.Release.EnvironmentCode & ".vsto"
    'description
    Dim descNode = vstoXML.SelectSingleNode("/asmv1:assembly/asmv1:description", vstoManifestNamespaces)
    descNode.Attributes("asmv2:product").Value = appName & "-" & caller.Release.EnvironmentCode
    'dependancy assemblyIdentity
    Dim depAssmblyIdentityNode = vstoXML.SelectSingleNode("/asmv1:assembly/asmv2:dependency/asmv2:dependentAssembly/asmv2:assemblyIdentity", vstoManifestNamespaces)
    depAssmblyIdentityNode.Attributes("name").Value = appName & "-" & caller.Release.EnvironmentCode & ".dll"
    vstoXML.Save(vstoFileInfo.FullName)

End Sub
like image 45
Andy Avatar answered Sep 27 '22 17:09

Andy