ClickOnce and Nant – The Plot Thickens

Turns out that these ClickOnce deployment builds aren’t as piss-easy as I once thought.
Turns out that the builds need to be customised for different environments, nothing new there, but (and here comes the catch), all the environmental settings have to be applied at BUILD TIME!!! Why? I hear you ask, and the answer is: because if you edit the config files post-build it changes some checksum jiggery pokery wotnot and then the thingumyjig goes and fails!!! Typical. (Basically the details you need to configure are held in files you cannot edit post build because the manifest file will do a checsum evaluation and see that someone has edited the file, and throw errors).
So what I’ve decided to do is this….

  1. Copy all configurable files to seperate environmentally named folders pre-build.
  2. Use SED to replace tokens for each environment in these files
  3. Copy 1 of them back to the build folder
  4. Compile
  5. Copy the output to the environmentally named folder

redo steps 3,4,5 for all the environments.
And hey presto, this works.

<target name=”changeconfigs”>
<!–This bit sets up some folders where I’ll do the prep work for each environment–>
<delete dir=”${config.dir}\${project.name}” verbose=”true” if=”${directory::exists(config.dir+’\’+project.name)}” />
<mkdir dir=”${config.dir}\${project.name}\TestArea” />
<mkdir dir=”${config.dir}\${project.name}\DevArea” />
<mkdir dir=”${config.dir}\${project.name}\Staging” />
<mkdir dir=”${config.dir}\${project.name}\UAT” />
<mkdir dir=”${config.dir}\${project.name}\Live” />

<!–This bit moves a tokenised config file to these folders–>
<copy file=”${source.dir}\App_Master.config” tofile=”${config.dir}\${project.name}\TestArea\app.config” />
<copy file=”${source.dir}\App_Master.config” tofile=”${config.dir}\${project.name}\DevArea\app.config” />
<copy file=”${source.dir}\App_Master.config” tofile=”${config.dir}\${project.name}\Staging\app.config” />
<copy file=”${source.dir}\App_Master.config” tofile=”${config.dir}\${project.name}\UAT\app.config” />
<copy file=”${source.dir}\App_Master.config” tofile=”${config.dir}\${project.name}\Live\app.config” />

<!–This bit calls sed, which replaces the tokens with relevant values for each environment, more on sed another time!–>
<exec program=”${sedUAT.exe}” commandline=”${sedParse.dir}” />
<exec program=”${sedTestArea.exe}” commandline=”${sedParse.dir}” />
<exec program=”${sedDevArea.exe}” commandline=”${sedParse.dir}” />
<exec program=”${sedStaging.exe}” commandline=”${sedParse.dir}” />
<exec program=”${sedLive.exe}” commandline=”${sedParse.dir}” />
</target>

<!–This bit copies the edited file back to the build directory–>
<target name=”prepTestArea”>
<delete file=”${source.dir}\app.config” />
<copy file=”${config.dir}\${project.name}\TestArea\app.config” tofile=”${source.dir}\app.config” />
</target>

<!–This bit builds the ClickOnce project–>
<target name=”publishTestArea” >
<msbuild project=”${base.dir}\Proj1\ClickOnce.vbproj”>
<arg value=”/t:Rebuild” />
<arg value=”/property:Configuration=Release”/>
<arg value=”/p:ApplicationVersion=${version.num}”/>
<arg value=”/p:InstallUrl=http://testarea/ClickOnce/”/>
<arg value=”/t:publish”/>
<arg value=”/p:UpdateRequired=true”/>
<arg value=”/p:MinimumRequiredVersion=${version.num}”/>
</msbuild>
</target>

<!–This bit copies the output to an environment-named folder, ready for deployment–>
<target name=”copyfilesTestArea”>
<mkdir dir=”${versioned.dir}\TestArea” />
<copy todir=”${versioned.dir}\TestArea” includeemptydirs=”true”>
<fileset basedir=”${base.dir}\Proj1\bin\Release\”>
<include name=”**.publish\**\*.*” />
</fileset>
</copy>
</target>

REPEAT LAST 3 TARGETS FOR THE OTHER ENVIRONMENTS.

Now that wasn’t too hard, and it doesn’t take up too much extra time.
I suppose I’d better mention some of the arguments I’m passing in the MSBuild calls:

<arg value=”/t:Rebuild” /> – I do this because it must re build the .deploy files each time, or you get the previous builds environment settings left in there because MSBuild decides to skip files that haven changed….

<arg value=”/property:Configuration=Release”/> – Obvious

<arg value=”/p:ApplicationVersion=${version.num}”/> – ClickOnce apps have a version stamped on them for various reasons, one of them being for use in automatic upgrades – people with installshield knowledge will know what a joke that can be!

<arg value=”/p:InstallUrl=http://testarea/ClickOnce/”/> – A pretty important one this, it stamps the URL for the download onto the manifest or application file.

<arg value=”/t:publish”/> – just calls the publish task, I do this because this makes the setup.exe

<arg value=”/p:UpdateRequired=true”/>
<arg value=”/p:MinimumRequiredVersion=${version.num}”/> – These 2 together mean the app will do a forced upgrade when a new version becomes available

So far, so good. My next trick will hopefully be how to get 2 installations working side-by-side. Currently it doesn’t work because one will overwrite the other. I’m working on it okay!!??

Advertisement

Building ClickOnce Applications with NAnt

Since I don’t like to actually do any work, and would much rather automate everything I’m required to do, I decided to automate a ClickOnce application build, because doing it manually was taking me literally, er, seconds, and this is waaaaaay too much like hard work. So, I naturally turned to NAnt, which is so often the answer to all my deployment questions….The answer came in the form of using NAnt to call MSBuild and pass the publish target, along with the version number. So, this is what you need to do:

Add a property to your nant script containing your build number (you can get this from CruiseControl.Net if you’re using CCNet to do your builds)
<property name=”version.num” value=”1.2.3.4″/>
Then just compile the project using NAnt’s MSBuild task, and call the publish target:

<target name=”publish” >

<msbuild project=”${base.dir}\ClickOnce.vbproj”>

<arg value=”/property:Configuration=Release”/>

<arg value=”/p:ApplicationVersion=${version.num}”/>

<arg value=”/t:publish” />

</msbuild>

</target>

The next thing you need to do is create or update the publish.htm file. What I’ve done for this is to take a copy of a previously generated publish.htm, and replace the occurrences of the application name with a token. Then in the NAnt script, I replace the token with the relevant application name with a version number. I do this because the version number will change with each build, and rather than manually update it, which is much too complicated for me, I’d rather just automate it so that I can go back to sleep while it builds.I tokenised the application name because of a much darker, more sinister reason that I’ll maybe explain at another time, but the world’s just not ready for that yet.
Anyway, here’s all that in NAntish:

<copy todir=”${config.dir}\${project.name}”>

<fileset basedir=”.”>

<include name=”publish.htm” />

</fileset>

<filterchain>

<replacetokens>

<token key=”VERSION” value=”${version.num}” />

<token key=”APPNAME” value=”${appname}” />

</replacetokens>

</filterchain>

</copy>