Jay Harris is Cpt. LoadTest

a .net developers blog on improving user experience of humans and coders
Home | About | Speaking | Contact | Archives | RSS
 
Filed under: NAnt | Task Automation | Tools

Scott Hanselman posted an entry yesterday about Managing Multiple Configuration File Environments with Pre-build Events. His design uses pre-build events in Visual Studio to copy specific configuration files to the default file name, such as having "web.config.debug" and a "web.config.release" configuration files and the pre-build copying the appropriate file to "web.config" based on which build configuration you are in. This is a great idea, but large web.config files can get tedious to maintain, and there is a lot of repeated code. Even using include files as Scott suggests would help, but major blocks, such as Application Settings, may have to be repeated even though only the values change. This exposes human error, since an app setting may be forgotten or misspelled in one of your web.config versions.

At Latitude, we manage this problem through NAnt. One of our former developers, Erik Nelsestuen–brilliant guy–authored the original version of what we call "ConfigMerge". Essentially, our projects have no web.config under source control. Instead, we have a web.format.config. The format config is nearly identical to the web.config, except all of the application settings and connection strings have been replaced with NAnt property strings. Rather than have a seperate web.config for each environment and build configuration, we simply have NAnt property files. Our build events (as well as our automated build scripts) pass the location of the format file and the location of the property file and the output is a valid web.config, with the NAnt property strings replaced with their values from the environment property file.

It's simple. It only takes one NAnt COPY command.

default.build

<project default="configMerge">
  <property name="destinationfile"
    value="web.config" overwrite="false" />
  <property name="propertyfile"
    value="invalid.file" overwrite="false" />
  <property name="sourcefile"
    value="web.format.config" overwrite="false" />
 
  <include buildfile="${propertyfile}" failonerror="false"
    unless="${string::contains(propertyfile, 'invalid.file')}" />
 
  <target name="configMerge">
    <copy file="${sourcefile}"
        tofile="${destinationfile}" overwrite="true">
      <filterchain>
        <expandproperties />
      </filterchain>
    </copy>
  </target>
</project>

For an example, lets start with a partial web.config, just so you get the idea. I've stripped out most of the goo from a basic web.config, and am left with this:

web.confg

<configuration>
  <system.web>
    <compilation defaultLanguage="c#" debug="true" />
    <customErrors mode="RemoteOnly" /> 
  </system.web>
</configuration>

In a debug environment, we may want to enable debugging and turn off custom errors, but in release mode disable debugging and turn on RemoteOnly custom errors. The first thing we will need to do is create a format file, and then convert the values that we want to make dynamic into NAnt property strings.

web.format.config

<configuration>
  <system.web>
    <compilation defaultLanguage="c#" debug="${debugValue}" />
    <customErrors mode="${customErrorsValue}" /> 
  </system.web>
</configuration>

Next, we need to make NAnt property files, and add in values for each NAnt property that we've created. These property files will include the values to be injected into the web.config output. For the sake of simplicity, I always give my property files a '.property' file extension, but nant will accept any file name; you can use '.foo' if you like.

debugBuild.property

<project>
   <property name="debugValue" value="true" />
   <property name="configMergeValue" value="Off" />
</project>

releaseBuild.property

<project>
   <property name="debugValue" value="false" />
   <property name="configMergeValue" value="RemoteOnly" />
</project>

Finally, we just execute the NAnt script, passing in the appropriate source, destination and property file locations to produce our environment-specific web.config.

nant configMerge -D:sourcefile=web.format.config -D:propertyfile=debugBuild.property -D:destinationfile=web.config
web.config output

<configuration>
  <system.web>
    <compilation defaultLanguage="c#" debug="true" />
    <customErrors mode="Off" /> 
  </system.web>
</configuration>

And that's all there is to it. This is extendable further using the powers of NAnt. Using NAnt includes, you can put all of your base or default values in one property file that's referenced in your debugBuild.property or releaseBuild.property, further minimizing code. You can use Scott's pre-build event idea to have each build configuration have its own NAnt command to make mode-specific configuration files.

Feel free to use the above NAnt script however you like; but, as always YMMV. Use it at your own risk.

Enjoy.

Friday, 21 September 2007 22:00:26 (Eastern Daylight Time, UTC-04:00)  #    Comments [5] - Trackback

Filed under: NAnt | Task Automation | Tools

Both NAnt and NAntContrib released version 0.85 on Sunday. The changes to NAnt from 0.85 rc4 only include a few bug fixes. NAntContrib has added the ability to specify the encoding on SQL files. All-in-all, not much has changed since 0.85 rc4, but that is a good thing, since it indicates the version is finally ready for release. The first release candidate was made available nearly two years ago.

Despite the minimal changes in the final package, consider upgrading just to get rid of the ‘release candidate’ tag.

NAnt v0.85 [ homepage | download | release notes ]
NAntContrib v0.85 [ homepage | download | release notes ]

Tuesday, 17 October 2006 22:42:02 (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - Trackback

NAnt hates .Net’s resource files, or .resx. Don’t get me wrong–it handles them just fine–but large quantities of resx will really bog it down.

Visual Studio loves resx. The IDE will automatically create a resource file for you when you open pages and controls in the ‘designer’ view. Back when we still used Visual SourceSafe as our SCM, Visual Studio happily checked the file in and forgot about it. Now, our 500+ page application has 500+ resource files. Most of these 500+ resource files contain zero resources, making them useless, pointless, and a detriment to the build.

This morning I went through the build log, noting every resx that contained zero resources, and deleted all of these useless files.

The compile time dropped by 5 minutes.

Moral of the story: Be weary of Visual Studio. With regards to resx, VS is a malware program that’s just filling your hard drive with junk. If you use resx, great, but if you don’t, delete them all. NAnt will love you for it.

Wednesday, 15 February 2006 11:31:31 (Eastern Standard Time, UTC-05:00)  #    Comments [0] - Trackback

I know. I haven’t posted in a while. But I’ve been crazy busy. Twelve hour days are my norm, right now. But enough complaining; let’s get to the good stuff.

By now you know my love for PsExec. I discovered it when trying to find a way to add assemblies to a remote GAC [post]. I’ve found more love for it. Now, I can remotely execute my performance tests!

Execute LoadRunner test using NAnt via LoadRunner:

<exec basedir="${P1}"
  program="psexec"
  failonerror="false"
  commandline='\${P2} /u ${P3} /p ${P4} /i /w "${P5}" cmd /c wlrun -Run
    -InvokeAnalysis -TestPath "${P6}" -ResultLocation "${P7}"
    -ResultCleanName "${P8}"' />

(I’ve created generic parameter names so that you can read it a little better.)
P1: Local directory for PsExec
P2: LoadRunner Controller Server name
P3: LoadRunner Controller Server user username. I use an Admin-level ID here, since this ID also needs rights to capture Windows PerfMon metrics on my app servers.
P4: LoadRunner Controller Server user password
P5: Working directory on P2 for 'wlrun.exe', such as C:\Program Files\Mercury\Mercury LoadRunner\bin
P6: Path on P2 to the LoadRunner scenario file
P7: Directory on P2 that contains all results from every test
P8: Result Set name for this test run

'-InvokeAnalysis' will automatically execute LoadRunner analysis at test completion. If you properly configure your Analysis default template, Analysis will automatically generate the result set you want, save the Analysis session information, and create a HTML report of the results. Now, put IIS on your Controller machine, and VDir to the main results directory in P7, and you will have access to the HTML report within minutes after your test completes.

Other ideas:

  • You can also hook it up to CruiseControl and have your CC.Net report include a link to the LR report.
  • Create a nightly build in CC.Net that will compile your code, deploy it to your performance testing environment, and execute the performance test. When you get to work in the morning, you have a link to your full performance test report waiting in your inbox.

The catch for all of this: you need a session logged in to the LoadRunner controller box at all times. The '/i' in the PsExec command means that it interacts with the desktop.

Sidenote

PsExec is my favorite tool right now. I can do so many cool things. I admit, as a domain administrator, I also get a little malicious, sometimes. The other day I used PsExec to start up solitaire on a co-workers box, then razzed him for playing games on the clock.

Friday, 14 October 2005 11:35:40 (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - Trackback

With our new nightly database restore we now have the desire to automatically run all of the change scripts associated with a project. We’ve found a way; I created a NAnt script that will parse the Visual Studio Database Project (or "DBP") and execute all of the change scripts in it. Here’s how we got there.


Problem 1: Visual Studio Command Files are worthless

Our first idea was to have everyone update a command file in the DBP, and have NAnt run it every night. Visual Studio command files are great and all, but we have discovered a problem with them: they do not keep the files in order. We have named all of our folders (01 DDL, 02 DML, etc) and our change scripts (0001 Create MyTable.sql, 0002 AddInfoColumn to MyTable.sql) accordingly so that they should run in order. We have found that the command file feature of VS.Net 2003 does not keep them in order but rather seems to sort them first by extension, then by order, or some similar oddness. Obviously, if I try to at InfoColumn to MyTable before MyTable exists, I’m going to have a problem. So, the command file idea was axed.

Problem 2: Visual SourceSafe contents can’t be trusted

Our second idea was to VSSGET the DBP directory in VSS and execute every script in it. However, the VSS store cannot be trusted. If a developer creates a script in VS.Net called ‘0001 Crate MyTable.sql’ and checks it in to the project, then proceeds to correct the spelling error in VS.Net to ‘0001 Create MyTable.sql’, VS does not rename the old file in VSS. Instead, it removes the old file from the project, renames it locally, then adds the new name to the project and to VSS. It also never deletes the old file name from the VSS store. Now, both files (’0001 Crate MyTable.sql’ and ‘0001 Create MyTable.sql’) exist in VSS. Performing a VSSGET and executing all scripts will run both scripts, which could lead to more troubles.


So, we can’t use a command file, because it won’t maintain the order. We can’t trust VSS, since it can have obsolete files. We can only trust the project, but how do we get a list of files, ourselves?

Fortunately, DBP files are just text in a weird XML-wannabe format. The NAnt script will open the file and run through it looking for every ‘SCRIPT’ entry in the file. If it finds a ‘BEGIN something’ entry, it assumes that ’something’ is a folder name, and appends it to the working path until it finds ‘END’, at which time it returns to the parent directory.

It’s not perfect. It still runs in to some problems, but here it is in v0.1 form.

<project name="RunDBPScripts" default="RunScripts">
<!–-
Execute all scripts in a VS.Net DBP
Author: Jay Harris, http://www.cptloadtest.com, (c) 2005 Jason Harris
License: This work is licensed under a  
   Creative Commons Attribution 3.0 United States License.  
   http://creativecommons.org/licenses/by/3.0/us/ 

This script is offered as-is.
I am not responsible for any misfortunes that may arise from its use.
Use at your own risk.
-–>
<!-– Project: The path of the DBP file –->
<property name="project" value="Scripts.dbp" overwrite="false" />
<!-– Server: The machine name of the Database Server –->
<property name="server" value="localhost" overwrite="false" />
<!-– Database: The database that the scripts will be run against –->
<property name="database" value="Northwind" overwrite="false" />
<target name="RunScripts">
        <property name="currentpath"
            value="${directory::get-parent-directory(project)}" />
        <foreach item="Line" property="ProjectLineItem" in="${project}">
            <if test="${string::contains(ProjectLineItem, 'Begin Folder = ')}">
                <regex pattern="Folder = &quot;(?’ProjectFolder’.*)&quot;$"
                    input="${string::trim(ProjectLineItem)}" />
                <property name="currentpath"
                    value="${path::combine(currentpath, ProjectFolder)}" />
            </if>
            <if test="${string::contains(ProjectLineItem, 'Script = ')}">
                <regex pattern="Script = &quot;(?’ScriptName’.*)&quot;$"
                    input="${string::trim(ProjectLineItem)}" />
                <echo message="Executing Change Script (${server+"\"+database}): ${path::combine(currentpath, ScriptName)}" />
                <exec workingdir="${currentpath}" program="osql"
                    basedir="C:\Program Files\Microsoft SQL Server\80\Tools\Binn"
                    commandline=’-S ${server} -d ${database} -i “${ScriptName}" -n -E -b’ />
            </if>
            <if test="${string::trim(ProjectLineItem) == 'End’}">
                <property name="currentpath"
                    value="${directory::get-parent-directory(currentpath)}" />
            </if>
        </foreach>
    </target>
</project>

I used an <EXEC> NAnt task rather than <SQL>. I found that a lot of the scripts would not execute in the SQL task because of their design. VS Command Files use OSQL, so that’s what I used. I guess those command files were worth something after all.

If you know of a better way, or have any suggestions or comments, please let me know.

Thursday, 25 August 2005 12:15:41 (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - Trackback

Filed under: NAnt | Task Automation | Tools

Hopefully this will save a few of you some time: I have created a registry entry that will create file associations and commands for your NAnt .build files. It will associate .build files as “NAnt Build Files” and create two commands for right-clicking a .build file in Explorer: “Edit” will open the file in Notepad; “Run” will execute the file in NAnt using a persistent command window (the window won’t disappear when the script is finished).

NAnt Build File Associations

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.build]
@=”build_auto_file”

[HKEY_CLASSES_ROOT\build_auto_file]
@=”NAnt Build File”
“EditFlags”=dword:00000000
“BrowserFlags”=dword:00000008

[HKEY_CLASSES_ROOT\build_auto_file\shell]
@=”Edit”

[HKEY_CLASSES_ROOT\build_auto_file\shell\&Run]
@=”Run”

[HKEY_CLASSES_ROOT\build_auto_file\shell\&Run\command]
@=”C:\WINDOWS\system32\CMD.EXE /k “C:\Program Files\NAnt\bin\NAnt.exe” -buildfile:%1″

[HKEY_CLASSES_ROOT\build_auto_file\shell\&Run\ddeexec]

[HKEY_CLASSES_ROOT\build_auto_file\shell\&Run\ddeexec\Application]
@=”NAnt”

[HKEY_CLASSES_ROOT\build_auto_file\shell\&Run\ddeexec\Topic]
@=”System”

[HKEY_CLASSES_ROOT\build_auto_file\shell\edit]
@=”&Edit”

[HKEY_CLASSES_ROOT\build_auto_file\shell\edit\command]
@=”C:\WINDOWS\system32\NOTEPAD.EXE %1″

[HKEY_CLASSES_ROOT\build_auto_file\shell\edit\ddeexec]

[HKEY_CLASSES_ROOT\build_auto_file\shell\edit\ddeexec\Application]
@=”NOTEPAD”

[HKEY_CLASSES_ROOT\build_auto_file\shell\edit\ddeexec\Topic]
@=”System”

Use this code/file at your own risk. I offer it as is, without any support. By downloading this file or using this code you take full responsibility for any repercussions that it may have on your computer.

Thursday, 11 August 2005 13:46:23 (Eastern Daylight Time, UTC-04:00)  #    Comments [0] - Trackback