共用方式為


Finally... Real Content

First off, an apology for the lack of posts.  As you may recall from last time, Mike Sampson has left our team.  But that wasn't the only change that happened to our feature area: Setup and Deployment projects team moved from the VB team to the VS Setup team.  However, the design time experience for ClickOnce projects did not move.  Since I was the only dev covering both areas, I was working hard to get the necessary work items for ClickOnce done prior to changing teams.  Those were finished up a couple of weeks ago, and then I had to get acclimated to my new team.  The good news: there are devs aplenty on the new team.

Through the years, the team has heard several complaints that the functionality in Setup and Deployment projects is too limited.  In Visual Studio 2005 we made a step towards modifying this by adding post-build steps.  Originally, the intent was to allow users to execute jscripts or vbscripts to modify the MSI post-build, along the lines of what Aaron Stebner outlines on his blog.  This would give the user leeway to plug any holes that they wanted filled.

I'm going to show another set of post-build steps.  These post-build steps will use MSBuild, which will give us all of the built-in functionality and extensibility that goes along with MSBuild.  Let's run through some scenarios here, and see what we get.

First off, let's fire up Visual Studio.  It's best to launch VS from the Visual Studio command prompt, as that sets up several environment variables.  Most importantly, it adds MSBuild to the path.  (Wouldn't it be cool if post-build steps were somehow automatically launched from the VS command prompt?  Sounds like a nice Connect  issue to raise if someone is interested.  I'm pretty confident that if I raised the issue myself, it would be marked as Won't Fix, but the community has more pull in bug fixes than team members).

Now, let's create a project file which will contain our post build steps.  We could (and will) do something much cooler, but let's start with everyone's old stand-by: outputting Hello, world! 

  1. Create a new Setup project, SetupHello.
  2. Right-click the project in Solution Explorer, and select "Add new file..."
  3. Navigate to the same folder your .vdproj file lives in.
  4. Right-click and select New->Text Document.
  5. Name the file test.proj
  6. Hit OK

Of course, we don't want to actually redistribute this file, so exclude it from the installer.  Select the file in the Solution Explorer, and mark Exclude=True in the properties window.

Now right-click test.proj in the solution explorer, and select Open.  The file should now be available for editing.  Paste the following into the project file:

<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="DisplayMessages">
<Message Text="Hello, world!" />
</Target>
</Project>

What just happened here?  MSBuild uses an xml-based file format for its project files.  The root node for any project file should be "Project".  The MSBuild system will read the contents of the file, and run the appropriate build Targets.  Targets contain a series (or in this case, one) tasks to run.  Our Target is called DisplayMessages, and has one Message task.

So far so good.  Now we want to get this project run as a post-build step.  Select the project root node in the solution explorer, and bring up the picker for the PostBuildEvent property.  In the resulting dialog, add the following command:

     MSBuild.exe "$(ProjectDir)\test.proj"

This will launch MSBuild and have it process the test.proj project file.  The quotation marks there are key: we want the fully qualified path to the project file to be passed to MSBuild.

Building the setup project yields the following output:

 Starting post-build events...
Microsoft (R) Build Engine Version 2.0.50727.42
[Microsoft .NET Framework, Version 2.0.50727.42]
Copyright (C) Microsoft Corporation 2005. All rights reserved.


Build started 9/21/2006 4:33:09 PM.
__________________________________________________
Project "D:\Documents and Settings\mwade\My Documents\Visual Studio 

2005\Projects\PostBuildOperations\SetupHello\test.proj" (default targets):

Target DisplayMessages:
    Hello, world!

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.01

Post-build events finished

So, it looks like that worked just fine.  Of course, that wasn't a very helpful operation.  Let's expand it by passing in some project-specific parameters.  Let's update the target file to the following:

    <Target Name="DisplayMessages">
<Message Text="Hello, world!" />
<Message Text="BuiltOutputPath=$(BuiltOutputPath)" />
<Message Text="Configuration=$(Configuration)" />
<Message Text="ProjectDir=$(ProjectDir)" />
</Target

Building now yields:

 Target DisplayMessages:
    Hello, world!
    BuiltOutputPath=
    Configuration=

Well, that's not what we had hoped for!  I wanted the actual macros defined in PostBuildEvent picker to be displayed here.  The key is to pass these values in as properties to MSBuild.  Update the PostBuildEvent to the following:

     MSBuild.exe /p:Configuration="$(Configuration)" /p:BuiltOutputPath="$(BuiltOuputPath)" "$(ProjectDir)\test.proj"

The /p parameter is a means to pass a property to MSBuild.  Another useful parameter to investigate is /v, which changes the verbosity of the output.  For example, /v:diag can be very helpful at times (and pretty annoying at others).  No, your eyes are not deceiving you: I passed in $(BuiltOuputPath) instead of $(BuiltOutputPath).  The macro was defined with that spelling error, but I chose to not propagate it to my MSBuild project file.  I'm sure that won't get confusing down the road.  Also, I tried passing in the ProjectDir property, but MSBuild was giving me grief about it.  It looks like the trailing slash in the property was causing parsing errors somewhere; haven't done enough experiments to determine if its an MSBuild issue or an OS issue but we'll just move on.

This time the build gives us:

 Target DisplayMessages:
    Hello, world!
    BuiltOutputPath=D:\Documents and Settings\mwade\My Documents\Visual Studio 2005\Projects\PostBuildOperations\SetupHello\Debug\SetupHello.msi
    Configuration=Debug

Ahh, much better.  Still not very useful, but we have a decent base for doing more useful things down the line.  We'll get to that.

So, what did we learn today?

  • How to create an MSBuild project file to a setup project
  • How to get that project file executed as a post-build step
  • How to pass project-specific properties to the MSBuild engine.

[This entry written while listening to Queen and The Rolling Stones]

Comments

  • Anonymous
    November 22, 2006
    As I stated when I started this whole exercise , I talked about how I wanted to use MSBuild for the post

  • Anonymous
    December 28, 2006
    As a special treat to all of you, we are going to mostly step away from the signing example for a little

  • Anonymous
    April 20, 2009
    I ran across this post while researching a similar problem. When I added a real, working program to a pre-build step in a Visual C# project, I had the following results. Given the following: The pre-build step is as follows. "C:Documents and SettingsDavidMy DocumentsVisual Studio 2005ProjectswwBldNbrMgrwwBldNbrMgrbinReleasewwBldNbrMgr.exe" "$(ProjectDir) - note that the terminal quote is intentionally missing. The actual project directory is "C:Documents and SettingsDavidMy DocumentsVisual Studio 2005Projects_MyPlayPen_MyPlayPen" - full of spaces. Results: This works correctly: <PreBuildEvent>"C:Documents and SettingsDavidMy DocumentsVisual Studio 2005ProjectswwBldNbrMgrwwBldNbrMgrbinReleasewwBldNbrMgr.exe" "$(ProjectDir)</PreBuildEvent> This reports an illegal character in the path: <PreBuildEvent>"C:Documents and SettingsDavidMy DocumentsVisual Studio 2005ProjectswwBldNbrMgrwwBldNbrMgrbinReleasewwBldNbrMgr.exe" "$(ProjectDir)"</PreBuildEvent> This reports path or file not found: <PreBuildEvent>"C:Documents and SettingsDavidMy DocumentsVisual Studio 2005ProjectswwBldNbrMgrwwBldNbrMgrbinReleasewwBldNbrMgr.exe" $(ProjectDir)</PreBuildEvent> Needless to say, I implemented the first version, and will move on. Nevertheless, I didn't think it would hurt any to share my discovery.