Tuesday, February 28, 2006

When using MSBuild you can import external declarations into your project by using the Import element. This is very useful for distributing your shared targets across your organization, and is a way to segregate where things are located. When MSBuild encounters this tag, a few things happen. First the current directory is changed to that of the file that is being imported. This is done so assembly locations specified by the Using Task and other imports are resolved correctly. What you may be surprised to find out is that if the importing project file declares any items, those item declarations are always relative to the top project file location. To clarify this if you have the following directory structure:

C:.

├───one

   └───Two

           YourProject.proj

├───Shared

      CurrentDirectory.dll

      SharedTargets.targets

  

   └───Another

           SharedTargets_2.targets

└───utils

        CommandLine.txt

In this scenario YourProject.proj is the top project file, it imports both SharedTargets.targets and SharedTargets_2.targets files. If the SharedTargets.targets had an item declaration of

<ItemGroup>
    <Test Include="test.txt"/>
</ItemGroup>

When imported by YourProject.proj this item declaration would actually resolve to C:\one\two\test.txt instead of the expected C:\Shared\test.txt value. In 95% of the time this is not an issue at all. But for that other 5% how can we accomplish this?

Well, there is no magic reserved MSBuild property for this. So we're gonna have to do some work here. To accomplish this we'll have to create a custom MSBuild task. If you've never created an MSBuild task you might be surprised how easy it is!

I created a new project named DirectoryTask which will house this task. Following this I added a reference to Microsoft.Build.Framework and Microsoft.Build.Utilities assemblies. Following this I wrote the task. It is shown below

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Build.Framework;

using Microsoft.Build.Utilities;

namespace CurrentDirectory

{

    /// <summary>

    /// Task that will return the folder that contains the current project.

    /// Inheriting from AppDomainIsolatedTask causes this task to execute in its own

    /// App domain. Not necessary here, only for demonstration.

    ///

    /// Sayed Ibrahim Hashimi

    /// www.sedodream.com

    /// </summary>

    public class CurrentDir : AppDomainIsolatedTask

    {

        private ITaskItem currentDir;

        [Output]

        public ITaskItem CurrentDirectory

        {

            get

            {

                return this.currentDir;

            }

        }

        public override bool Execute()

        {

            System.IO.FileInfo projFile = new System.IO.FileInfo(base.BuildEngine.ProjectFileOfTaskNode);

            this.currentDir = new TaskItem(projFile.Directory.FullName);

           

            return true;

        }

    }

}

As you can see this is a pretty simple task. When Execute is called the directory is gathered from the project file that contains the task invocation. The name of the assembly that I built this into is CurrentDirectory.dll.

Now to see this in action I will use the same directory structure shown above. The contents of the SharedTargets.targets file is:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 

  <UsingTask AssemblyFile="CurrentDirectory.dll" TaskName="CurrentDir"/>

 

  <Target Name="SharedTarget">

    <CurrentDir>

      <Output ItemName="CurrentDir" TaskParameter="CurrentDirectory" />

    </CurrentDir>

 

 

    <Message Text="Inside the SharedTargets.targets" Importance="high"/>

    <Message Text="Location: @(CurrentDir->'%(Fullpath)')"/>

  </Target>

 

</Project>

This uses the CurrentDirectory task to determine what the current directory is. The SharedTargets_2.targets file is very similar and is:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 

  <Target Name="SharedTarget2">

 

    <CurrentDir>

      <Output ItemName="CurrentDir2" TaskParameter="CurrentDirectory" />

    </CurrentDir>

 

 

    <Message Text="Inside the SharedTargets_2.targets" Importance="high"/>

    <Message Text="Location: @(CurrentDir2->'%(Fullpath)')"/>

  </Target>

 

</Project>

 

Now let’s take a look at the very simple YourProject.Proj file.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Print">

  <Import Project="..\..\Shared\SharedTargets.targets"/>

  <Import Project="..\..\Shared\Another\SharedTargets_2.targets"/>

 

  <PropertyGroup>

    <PrintDependsOn>

      SharedTarget;

      SharedTarget2

    </PrintDependsOn>

  </PropertyGroup>

 

  <Target Name="Print" DependsOnTargets="$(PrintDependsOn)">

  </Target>

</Project>

 

This file simply imports the two other files and defines the print target. Now we want to invoke the Print target on this project file. To do this open the Visual Studio2005 command prompt and go the directory containing the YourProjec.proj file. Then execute the following:

>msbuild.exe YourProject.proj /t:Print

 

The results of this invocation are:

__________________________________________________

Project "C:\Data\Community\msbuild\MSBuildDirectoryExample\one\Two\YourProject.proj" (default targets):

 

Target SharedTarget:

    Inside the SharedTargets.targets

    Location: C:\Data\Community\msbuild\MSBuildDirectoryExample\Shared

Target SharedTarget2:

    Inside the SharedTargets_2.targets

    Location: C:\Data\Community\msbuild\MSBuildDirectoryExample\Shared\Another

 As you can see the correct location was resolved for both of these items. Previously if you had command line utilities in source control and .targets files that would manage invoking those tools, it was error prone. This is because the .targets file wouldn’t be able to resolve the location of the command line util, even if in the same directory. The solution to this problem it to rely on the developer to set a property (or environment variable) which states where this tool can be located; or some other similar means. With this task we no longer have to rely on such a solution. The .targets file is able to resolve the location of the command line utility. 

I have bundled all related files into a zip file which you can download below.

MSBuildDirectoryExample.zip (you may have to right-click->Save As)

 http://www.sedodream.com/content/binary/MSBuildDirectoryExample.zip

Sayed Ibrahim Hashimi

Tuesday, February 28, 2006 6:23:33 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Wednesday, February 08, 2006

The other day I received an intersting comment on my previous blog enty. The comment was asking how to send the details of a build error via email to a recipient(s). When I was starting with MSBuild I was also wondering how to do this, so I would like to address this topic now. This is a very useful scenario to implement if you builds are automated. If you are lucky enough to be using Team Foundation Server then you job is a lot easier because this is taken care of for you. But if you are like most .NET developers odd are that you aren't. So we need a good way to do this.
There is an SmtpTask that you can use to send an email through an MSBuild task. At first thought you might think that this will work, but you'll be saddended to find out that it doesn't. This is because the only way to trigger this task to be executed is from the project file. The only way to achieve that is by using the MSBuild OnError element to state what to do in the case of an error occuring during the course of a target executing. When you build a VS project you are most likely to execute the Build (or Rebuild which calls Build) target. This target actually perfroms no work itself, it is delegated to other targets. So having and OnError element in that target is of no use. You'd have to place one in each of the other targets that get executed from its dependencies. This means sticking that element in a lot of different places, which is no fun. So there is no way to say "If an error occurs at any time during my build execute this target". But if we change that phrase to "If an error occurs anywhere during my build then do ...", we can achive that. We do that by writing and using a custom logger!

Writing a custom logger may sound to be daunting at first, but rest assured it is not! Once you write one logger you can write many, and I'll show you how to write your first one right here! Let's cover some basics first. MSBuild ships with 2 loggers, the ConsoleLogger and the FileLogger. These classes are a part of the Microsoft.Build.BuildEngine namespace, and you can extend them. So this means that we can use the already written functionality and extend it to fit our needs. This is exactly what we are going to do in this example. We will extend the FileLogger to email the log file if an error has occured. Let's get started on this.

First create a new C# (or other managed lang of you choice) Class Library project. I named this project EmailLogger. You'll first want to add references to the following libraries.

  1. Microsoft.Build.BuildEngine
  2. Microsoft.Build.Utilities
  3. Microsoft.Build.Framework

At this point we are ready to start coding. We want to make the EmailLogger class a sub-class of the FileLogger to get its behavior. Here are some logger basics now. The logging mechanism in MSBuild is an event based system. Events include such things as ProjectStarted, BuildFinished, ErrorRaised and others. We're obviously very interested in the last one. The method

     public void Initialize(Microsoft.Build.Framework.IEventSource eventSource)

will be called before MSBuild starts to build your project. In this method is where you tell MSBuild what events you are interested in handling. In our case we will be handling only two events. Those events are; ProjectStarted and ErrorRaised. We handle the ProjectStarted because we need to grab all of the properties defined in the project file and put them somewhere. And we handle ErrorRaised to make a flag that an error has occured. Along with Initilize() we are promised that

    public override void Shutdown()

will be called after the build has finished and its time for the logger to say goodnight. This is where we will actually send the email. Let's have a look at the Initalize method shown below.

        /// <summary>

        /// This is guarenteed to be called by MSBuild before any build steps occur

        /// </summary>

        /// <param name="eventSource"></param>

        public override void Initialize(Microsoft.Build.Framework.IEventSource eventSource)

        {

            //This call will set the correct values from the parameters

            base.Initialize(eventSource);

            this.SetParameters(base.Parameters);

            //add the logger delegates here

            eventSource.ErrorRaised += new BuildErrorEventHandler(this.BuildError);

            eventSource.ProjectStarted += new ProjectStartedEventHandler(this.ProjectStarted);

        }

In this method we aren't doing anything fancy, we call SetParameters because we need to get the file location where the log is going to be saved to. When you are writing loggers remember it is upto you to parse and validate any parameters that are passed to it. If you are using the Logger class as your base class then the Verbosity is handled for you. You should chose this over implementing the ILogger interface directly. Anywayz back to the topic at hand, besides that we are registering our delegates to the appropriate events. After this the build starts, our ProjectStarted delegate gets invoked. In this method we are storing all the properties defined in the build file to a dictionary so we know how to send the email.

This logger works like this, it will gather the settings for sending the email from the project's properties. These can either be defined in the project file itself, or passed in as command line arguments to msbuild.exe using the /p switch. The properties that it will look for are named:

        EmailLogger_SmtpHost

        EmailLogger_From

        EmailLogger_To

        EmalLogger_CC

        EmailLogger_SmtpPort

        EmailLogger_TimeOut

        EmailLogger_SendOnSuccess

The alternative to this approach which would actually be more correct is to send all of these in a parameters to the logger. But that would be really annoying to have to type all of these values in at the command line each time you want to perform a build. Even putting these into an msbuild.rsp file is not as convienent. So I figured to put them into the project is not such a bad idea.

 

The ProjectStarted handler is shown below.

        /// <summary>

        /// The event that is raised when the project is started.

        /// This will examine all of the properties defined in the project file

        /// and store them in the <code>properties</code> dictionary

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        void ProjectStarted(object sender, ProjectStartedEventArgs e)

        {

            if (e.Properties != null)

            {

                foreach (DictionaryEntry entry in e.Properties)

                {

                    this.properties.Add(entry.Key.ToString(), entry.Value.ToString());

                }

            }

        }

The BuildError method is very simple, it is shown below.

        /// <summary>

        /// This is called in the event of an error.

        /// In this example we just mark that there was an error and the email is sent

        /// at the end of the build in the <code>Shutdown</code> method.

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        void BuildError(object sender, BuildErrorEventArgs e)

        {

            this.errorOccurred = true;

        }

Besides this we are pretty much just left with the Shutdown() method. It is shown below.

        /// <summary>

        /// This is called before msbuild exits.

        /// This is the method that will actually send the email out if there was an error

        /// or if send on success was specified as a property to 'true'

        /// </summary>

        public override void Shutdown()

        {

            //this should close down the log file

            base.Shutdown();

 

            //get the values for the email from the properties           

 

            if (this.properties == null || this.properties.Count <= 0)

            {

                return;

            }

 

            Emailer emailer = new Emailer();

 

            emailer.SmtpHost = this.GetProperty(EmailLoggerStrings.SmtpHostString);

            emailer.From = this.GetProperty(EmailLoggerStrings.FromString);

            emailer.To = this.GetProperty(EmailLoggerStrings.ToStr);

            emailer.Cc = this.GetProperty(EmailLoggerStrings.CcString);

            emailer.SetSmtpPort(this.GetProperty(EmailLoggerStrings.SmtpPortString));

            emailer.SetTimeout(this.GetProperty(EmailLoggerStrings.TimeoutString));

 

            bool emailOnSuccess = false;

            string emailOnSuccessStr = this.properties[EmailLoggerStrings.EmailOnSuccessString];

            if (!string.IsNullOrEmpty(emailOnSuccessStr))

            {

                emailOnSuccess = bool.Parse(emailOnSuccessStr);

            }

            if ( errorOccurred || emailOnSuccess )

            {

                //we need to send to email the log file to recipient here

                emailer.Subject = "Build log " + DateTime.Now.ToShortDateString();

                emailer.Body = "Log file attached";

                emailer.Attachment = this.logFile;

 

                emailer.SendEmail();

            }

        }

In this method we let the base (FileLogger) shutdown first. This will cause the logfile to be written out to disk, then we create the email and send if if there was an error, or if it was stated to send even on success. This is pretty much it from an MSBuild perspective. We have the Emailer class, but there is no MSBuild stuff there, just stuff to send the email.

Now we need to know how to use this logger. You are going to want to place the dll, in my case it's named EmailLogger.dll in a location that is accessible by other projects during the build process. In my case I have a SharedLoggers directory that contains these guys. So I copied it there. Then at the command line when we perform a build we need to tell MSBuild to use this logger. Do that by:

>msbuild.exe

PROJECT_FILE_NAME /l:EmailLogger,(PATH_TO_LOGGER)\EmailLogger.dll;logfile=test.log;verbosity=detailed

And you'll have to specify those above properties in your project file that you are building like so:

  <PropertyGroup>

    <EmailLogger_SmtpHost>fill this in</EmailLogger_SmtpHost>

    <EmailLogger_From>sayed.hashimi@gmail.com</EmailLogger_From>

    <EmailLogger_To> sayed.hashimi@gmail.com </EmailLogger_To>

    <EmalLogger_CC></EmalLogger_CC>

    <EmailLogger_SendOnSuccess>true</EmailLogger_SendOnSuccess>

  </PropertyGroup>

Then start your build. If you are building a solution, then these properties have to be defined in a project file that is being built, or at the command line. If you have multiple definitions this could get confusing from this implementation. Maybe more on that at a later date, too late now. That should be about all you need to know. The files are made available here for your convienence. Oh yeah, this hasn't been throughly tested, I just put this together so use it at your own risk. If you have modifications I'd appreciate you letting me know so I can fix my version as well.

 

EmailLoggerProjectFiles.zip (23.26 KB)   You may have to right-click Save as to get this file.

EmailLogger.dll (20 KB)  You may have to right-click Save as to get this file


Sayed Ibrahim Hashimi

 

Wednesday, February 08, 2006 7:22:28 AM (GMT Standard Time, UTC+00:00)  #    Comments [4]  | 
Tuesday, January 31, 2006
I've seen this question asked a few times, here and there. I'm sure the answer is in a few other places as well as here, but I don't have the link. Anywayz the topic is how to create a file that contains the biuld log contents. This is a pretty simple question because MSBuild ships with a file logger. So all we have to do is to tell MSBuild to use it and pass it the options that we want to use.
First you'll need to open the Visual Studio 2005 Command Prompt and navigate to the directory that contains your project. Lets call this project, DreamcatcherUI.proj. To specify to use the file logger we will have to use the /l switch when we invoke msbuild on it. This is the logger switch, the long version of this is /logger.
The syntax for using this switch is:
     /l:<logger class>,<Assembly>[;Parameters 4 logger]
In order to use the MSBuild file logger we can use the following syntax
    /l:FileLogger,Microsoft.Build.Engine;verbosity=detailed;logfile=mylog.log
So the msbuild command to build the DreamcatcherUI.proj would be:
>msbuild.exe /t:Build /l:FileLogger,Microsoft.Build.Engine;verbosity=detailed;logfile=mylog.log
The verbosity is the level of detail that you want in your log file, there are 5 settings: quiet[q]; minimal[m];normal[n];detailed[d]; and diagnostic[diag]. The name in brackets is the short name, you can use these in place of the full name if desired. The logfile parameter is the location that you want the file saved to. If not specified then a file msbuild.log will be created with the log in the current directory.
It is important to mention that the parameters section of the logger is upto the logger implementation to parse and make sense of. For the FileLogger you have 2 other parameters that you can pass in those append and encoding. If append=true then the file will be appended to and overwriten if otherwise. You can use the encoding to specify a certain encoding for the log file that is to be created.

Sayed Ibrahim Hashimi

Tuesday, January 31, 2006 5:16:12 AM (GMT Standard Time, UTC+00:00)  #    Comments [3]  | 
Monday, January 30, 2006
So recently I bought a 21" Gateway monitor. This is one of their newest monitors that are available. The monitor works good with my Dell desktop machine, but refuses to work on my Toshiba notebook. So I called the support a spoke with a tech there, he pretty much told me that it was an issue with the notebook. Before this I made sure to upgrade all available drivers for my notebook, and even called their support. Anywayz I ended up speaking with a support supervisor there @ Gateway, and she made some statments to me that were really surprising. I asked her if they had tested this monitor with Toshiba notebooks, or any notebooks for that matter. She told me "...We don't test our products with competitors' devices. There's no reason for us to buy products to test against because we have plenty here." How can a compnay create a general purpose device, such as this monitor, and not test it aginst a range of products that it is supposed to work for??? This is just ridiculious. To make a long story short I'm very dis-satisifed with the customer service provided by Gateway, and will never buy another Gateway device. This was the first and only Gateway device that will ever be in my ownership. When I bought this, I had a felling that I should be buying from a company such as ViewSonic, who I have had good experiences with. If you're looking at this monitor I would suggest looking into the ViewSonic VP2130b monitor, it may be more expensive but I'm sure they have tested it much better than Gateway cares to.

Sayed Ibrahim Hashimi

Monday, January 30, 2006 5:15:50 PM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Thursday, January 26, 2006

Ok,

I thought this was pretty straight forward but I had two people ask me about this recently so I thought I'd post a blog discussing it. There are many occasions where you'd like to create a Form that and use it as the base for inheritance by other forms. At the same time you'd like to still be able to use the Visual Studio Designer to design your Forms that inherit from that. In order to achieve this you'll have to create what's called an Inherited Form. You can use this form many things, including Visual Inheritance.

For this entry I have created a very simple example, you can download the files at the bottom of this entry if you are interested. To re-create the sample follow along. I created a new C# Windows Form Application. Then renamed the Form1.cs file to MyBaseForm.cs. This is the class that I would like to use as the base class for other forms. At this point I added a docked label to the form and add some specific stylistic changes to it. The resultant form is shown below.

As you can see from the image above there's not much to this form, but this is just a sample :-).
Now I'd like to provide an easy way to customize the description in my new label. To do this I'll create a new property and give it some attributes to be used by the Designer. That property is shown below.

[Description("Description")]
[Category("Form Base")]
public string Description
{
    get
    {
        return this.labelDescription.Text;
    }
    set
    {
        this.labelDescription.Text = value;
    }
}

This is just a normal property with the exception that It has the System.ComponentModel.Description attribute as well as the System.ComponentModel.Category attribute. The Description the name of this attribute that will be shown in the properties grid of the designer, and the category is the category that it will be in. If the Category is not present then it will default to the Misc category. Let's see how this makes a difference. Keep in mind that this is the simplest type of integration with the designer that you can achieve. The possibilities are endless.

At this point we should build the project. Before we can create the Inherited Form we must build the project, otherwise you'll get an error stating that no assemblies are present which contain a Form. So after the build has completed we want to add a new item to the project. One method for this is Right Click on the Project->Add->New Item. At this point you'll be presented a dialog to select the template type that you want to create. From this list select "Inherited Form". Have a look at the image below.

I'll call the new form MyInheritedForm.cs. After that you're given a dialog to select which Form class it should inherit from, then your form is created. For clarity about the designer integration here is a view of the properties grid from the MyInheritedForm designer view.

As you can see we have the Description property available in the Form Base category, just what we wanted! Now if the developer changes the value on this property then the text in the description label will be updated. Like I said earlier the designer integration is very capable, and this is the simplest form. Using these Inherited Forms for simple things like Visual Inheritance is a very clean way of mainting visual changes in a single form. But don't view this as the only use of Inherited Forms.

InheritedFormEx.zip (19.8 KB)

 

Sayed Ibrahim Hashimi

Thursday, January 26, 2006 5:26:38 AM (GMT Standard Time, UTC+00:00)  #    Comments [0]  | 
Tuesday, January 17, 2006

If you are using MSBuild 4.0 (i.e. Visual Studio 2010) then you can now use a new technique that I outline at MSBuild: Extending the solution build

Over the past few months I have noticed a need by many people to place some specific steps into the process that is used when MSBuild is invoked on solution files. When people research this topic they quickly discover that the Visual Studio Solution file is NOT in the MSBuild format. MSBuild is able to consume these files, but you are not able to actually extend the process used for the solution as a whole.
Recently there was an entry at the MSDN MSBuild Forum related to this issue. So I figured I'd put my two cents into the discussion as well.

For some background, when MSBuild needs to build a solution file, it is converted in memory to an MSBuild project file. If you have the envrionment variable msbuildemitsolution set to the value of 1. Then this file will be written out to a file. The file name will be SOLUTION_NAME.sln.proj.

Ok, previously there was another entry on the MSDN Forum about how to set an Envrionment variable using MSBuild. To let you know where I'm going with this, I'd like to have MSBuild be responsible for creating this file and for me to add steps before and after the building of my solution. The task to create the envrionment variable is shown below, this was written by Keith Hill and is at the previous link

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Sedodream.MSBuild
{
    ///


    /// Taken from http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=73225&SiteID=1
    /// Written by Keith Hill
    ///

    public class SetEnvVar : Task
    {
        private string _variable;
        private string _value;
        [Required]
        public string Variable
        {
            get { return _variable; }
            set { _variable = value; }
        }
        [Required]
        public string Value
        {
            get { return _value; }
            set { _value = value; }
        }
        public override bool Execute()
        {
            Environment.SetEnvironmentVariable(_variable, _value);
            return true;
        }
    }
}

You can download this file at the bottom of this entry (SetEnvVar.cs).

So we need to take this file and create an assembly from it. Make sure to add Microsoft.Build.Framework and Microsoft.Build.Utilities to your project's references. Oh yeah you could skip this part of the process if you just make sure to set that envrionment variable, but that's no fun.

After you create this assembly place in a known location. I called this assembly Sedodream.MSBuild.dll and placed it in a folder named tasks one folder above the solution I was trying to build.

Now that we are done with that we need to create the msbuild file that will do the rest of the work for us.

The whole file is shown below and can be downloaded at the end of this blog (DreamCatcher_SlnBuild.proj)

<Project InitialTargets="SetMSBuildEmit"
   
DefaultTargets="BuildSolution"
   
xmlns=http://schemas.microsoft.com/developer/msbuild/2003>

<PropertyGroup>

<SharedTasksDir>..\tasks\SharedTasksDir>

<EnvAssemblyFilename>Sedodream.MSBuild.dllEnvAssemblyFilename>

<BuildSolutionDir>.\BuildSolutionDir>

PropertyGroup>

<ItemGroup>

<SlnFiles Include="$(BuildSolutionDir)*.sln"/>

ItemGroup>

<UsingTask AssemblyFile="$(SharedTasksDir)$(EnvAssemblyFilename)" TaskName="SetEnvVar"/>

<Target Name="SetMSBuildEmit">

<SetEnvVar Variable="msbuildemitsolution" Value="1"/>

Target>

<Target Name="BuildSolution" DependsOnTargets="SetMSBuildEmit">

<MSBuild Projects="@(SlnFiles)" Targets="Build"/>

<CreateItem Include="*.sln.proj">

<Output TaskParameter="Include" ItemName="SolutionMSBuildFiles"/>

CreateItem>

<MSBuild Projects="$(MSBuildProjectFile)"

Targets="DoBuildSolution"

Properties="@(SolutionMSBuildFiles->'theSolution=%(Filename)%(Extension)')"/>

Target>

<PropertyGroup>

<DoBuildSolutionDependsOn>

BeforeDoBuildSolution;

CoreBuildSolution;

AfterDoBuildSolution

DoBuildSolutionDependsOn>

PropertyGroup>

<Target Name="DoBuildSolution" DependsOnTargets="$(DoBuildSolutionDependsOn)" />

<Target Name="BeforeDoBuildSolution">

<Message Text="*****Before building your solution*****" Importance="high"/>

Target>

<Target Name="CoreBuildSolution">

<MSBuild Projects="$(theSolution)" Targets="Build"/>

Target>

<Target Name="AfterDoBuildSolution">

<Message Text="###After building your solution######" Importance="high"/>

Target>

Project>

 

Now all you need to do is to place this file into the folder that contains your solution and call msbuild on it with:
    >msbuild.exe DreamCatcher_SlnBuild.proj /t:BuildSolution
The DefaultTargets is set to BuildSolution so you can take that off actually.

The are many advantages to this solution like:
    1) You extend the solution build process in similar ways as extending the project build proces
    2) You can re-use any existing MSBuild tasks
    3) You can add more steps to the build process easily
    4) You can place this file into source control and all team members can use it.

If you need more info please let me know, I would explain further now but I'm not feeling very well currently.

 

Sayed Ibrahim Hashimi


SetEnvVar.cs(.9kb)

DreamCatcher_SlnBuild.proj(3.19kB)

Tuesday, January 17, 2006 5:55:45 AM (GMT Standard Time, UTC+00:00)  #    Comments [14]  | 
Monday, January 16, 2006

There are many situations where you are building a solution that contains custom user controls and custom forms that other classes are dependent on. In a project that I'm currently working on we have this need as well. I have created a User Control, this control is kind of like the Visual Studio toolbox. This control is in a project that a few projects have a dependency on. In the main form this control is placed onto the form using the VS Designer. Sometimes when I need to change how the main form looks like, I get the following error.

*************************************************************************************************

One or more errors encountered while loading the designer. The errors are listed below. Some errors can be fixed by rebuilding your project, while others may require code changes.


Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.

Hide    


at System.Reflection.Module.GetTypesInternal(StackCrawlMark& stackMark)
at System.Reflection.Assembly.GetTypes()
at Microsoft.VisualStudio.Shell.Design.AssemblyObsoleteEventArgs..ctor(Assembly assembly)
at Microsoft.VisualStudio.Design.VSDynamicTypeService.ReloadAssemblyIfChanged(String codeBase)
at Microsoft.VisualStudio.Design.VSDynamicTypeService.CreateDynamicAssembly(String codeBase)
at Microsoft.VisualStudio.Design.VSTypeResolutionService.AssemblyEntry.get_Assembly()
at Microsoft.VisualStudio.Design.VSTypeResolutionService.AssemblyEntry.Search(String fullName, String typeName, Boolean ignoreTypeCase, Assembly& assembly, String description)
at Microsoft.VisualStudio.Design.VSTypeResolutionService.SearchProjectEntries(AssemblyName assemblyName, String typeName, Boolean ignoreTypeCase, Assembly& assembly)
at Microsoft.VisualStudio.Design.VSTypeResolutionService.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase, ReferenceType refType)
at Microsoft.VisualStudio.Design.Serialization.CodeDom.AggregateTypeResolutionService.GetType(String name, Boolean throwOnError, Boolean ignoreCase)
at Microsoft.VisualStudio.Design.Serialization.CodeDom.AggregateTypeResolutionService.GetType(String name, Boolean throwOnError)
at System.ComponentModel.Design.Serialization.CodeDomSerializerBase.GetType(ITypeResolutionService trs, String name, Dictionary`2 names)
at System.ComponentModel.Design.Serialization.CodeDomSerializerBase.FillStatementTable(IDesignerSerializationManager manager, IDictionary table, Dictionary`2 names, CodeStatementCollection statements, String className)
at System.ComponentModel.Design.Serialization.TypeCodeDomSerializer.Deserialize(IDesignerSerializationManager manager, CodeTypeDeclaration declaration)
at System.ComponentModel.Design.Serialization.CodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager manager)
at Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.PerformLoad(IDesignerSerializationManager serializationManager)
at Microsoft.VisualStudio.Design.Serialization.CodeDom.VSCodeDomDesignerLoader.DeferredLoadHandler.Microsoft.VisualStudio.TextManager.Interop.IVsTextBufferDataEvents.OnLoadCompleted(Int32 fReload)

**************************************************************************************************************************

I knew that all the code for the main form and its dependencies were OK because, I had viewed the form recently & everything builds fine. I ran into this problem a while ago when I was writing forms using Managed C++ with VS 2003. I assumed that the problem had been resolved, since I haven't run into it while using VS 2005 & C#, but seems like I was wrong.
I did a search and found that there are a few work items at the MSDN Product Feedback Center, in particular there is this item which is closely related. Based on some of the workarounds there and some other information that I have found it seems like the soultion is to

  1. Close all open files in Visual Studio
  2. Close Visual Studio
  3. Delete all temporary files
  4. Rebuild solution
  5. Open form

Doing all of this is a hassel, but better then not being able to visually design your forms! To reduce the pain of this process I have created an MSBuild file which will find and delete all the file located under every bin and obj folder. If your solution contains a different OutputPath which is not under the bin folder, or a folder other than obj for the BaseIntermediateOutputPath, then you'll need to edit this file. Also if for some reason you have files under these directories that you do not want to delete then please don't use this file because that would just be sily.

You can download the file form the link at the bottom of this page (DeleteTempFiles.proj). We will discuss its content and how to use it now. The file content is shownn below.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="CleanFiles">
    <
ItemGroup>
        <
FilesToDelete Include="**\bin\**\*;**\obj\**\*"/>
    </
ItemGroup>

    <
Target Name="CleanFiles" DependsOnTargets="ShowFiles">
        <
Delete Files="@(FilesToDelete)" ContinueOnError="true" />
    </
Target>
    <
Target Name="ShowFiles">
        <
Message Text="Files%0d%0a:@(FilesToDelete,'%0d%0a')"/>
    </
Target>

</Project>

There are two targets in this file, the CleanFiles target and the ShowFiles target. As you might expect the CleanFiles target deletes all of those files and the ShowFiles target simply prints a list of all the files that will be deleted. You can use this to help determine if you should execute the CleanFiles target. You should place this file in a folder at the top of the solution heirarchy. If you have projects in different locations other then under the same path then you'll have to modify the FilesToDelete declaration to pick those up as well. Before you run this file you should close all open files in Visual Studio and then exit out of Visual Studio. Following this open the Visual Studio Command Prompt, navigate to the folder which has the DeleteTempFiles.proj file. To show which files will be deleted you can do:
    >msbuild.exe DeleteTempFiles.proj /t:ShowFiles
To delete these files:
    >msbuild.exe DeleteTempFiles.proj /t:CleanFiles
Following this open Visual Studio, rebuild the solution and then open the form in the designer. If all goes well you should see your form again!

DeleteTempFiles.proj (.43 KB)
Monday, January 16, 2006 5:46:42 AM (GMT Standard Time, UTC+00:00)  #    Comments [1]  | 
Sunday, January 08, 2006

Previously I posted about a bug in MSBuild that doesn't allow you to create a property (or item) and use this in a target that is invoked with the CallTarget task. There is a way to invoke the calling target with the new value of the Property and that is to use the MSBuild Task to achieve this. This task allows you to use MSBuild to execute a specified set of targets. Using this task you can also send a set of properties that you'd like for it to use when building the project. So to workaround our previous issue, you must use the MSBuild task and pass in the properties that you desire. Have a look at the workaround project file:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Create">
    <
Target Name="Create">

        <
CreateProperty Value="E:\Data">
            <
Output TaskParameter="Value" PropertyName="DestFolder" />
        </
CreateProperty>
        <
CreateItem Include="*.*">
            <
Output TaskParameter="Include" ItemName="TestFiles"/>
        </
CreateItem>

        <!-- This doesn't work, the property & item have no value in the print target -->   
        <
CallTarget Targets="Print"/>

        <!-- Using this we are able to pass the property but not the item value -->
        <
MSBuild
           
Projects="$(MSBuildProjectFile)"
           
Targets="Print"
           
Properties="DestFolder=$(DestFolder)"
       
/>
    </
Target>
   
    <
Target Name="Print">
        <
Message Text="Print target called"/>
        <
Message Text="Dest: $(DestFolder)"/>
        <
Message Text="TestFiles: @(TestFiles)"/>
    </
Target>

</Project>

Here is the output when you execute the default target (Create)

Project "C:\Data\Dreamcatcher_NET\Dreamcatcher\Sample_Workaround.proj" (default targets):

Target Create:
    Target Print:
        Print target called
        Dest:
        TestFiles:
    __________________________________________________
    Project "C:\Data\Dreamcatcher_NET\Dreamcatcher\Sample_Workaround.proj" is building "C:\Data\Dreamcatcher_NET\Dreamcatcher\Sample_Workaround.proj" (Print target(s)):

    Target Print:
        Print target called
        Dest: E:\Data
        TestFiles:

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

That's kind of interesting how the property & item value are immediately available but not when you use the CallTarget task. At least you can get around the Property passing issue somewhat. There are some key limitations to this method, the most important of which is the fact that you must specify which properties to send to that target, and there is no way to send all the properties. And of course you can't send any items. The good thing about this issue is that I don't think this will impact a lot of people. You are not very likely to need this functionality, and there are ways around it. Namely if you use target dependencies you can execute the targets without the need to use the CallTarget task, but it would be convenient if it worked.
You can download the workaround file from the link below, if this doesn't fix your situation I'd be interested to know more about it. Maybe there is a way around it that does. Also I have updated the MSDN Product Feedback item, I added this workaround.

Sample_Workaround.proj (.73 KB)


Sunday, January 08, 2006 5:53:12 AM (GMT Standard Time, UTC+00:00)  #    Comments [5]  | 
Wednesday, January 04, 2006

A few weeks ago I had to write a JAXB bindings file, this is an xml file that describes customizations to Java xml bindings. I started by just typing these in by hand, but then quickly realized how painful it was becoming. Since I was faced with creating a fairly large bindings file, I had to come up with a better way to do this. I decided to use Visual Studio to create this file with Intellisense. To enable intellisense your xml file must have an xmlns namespace specified. This is how Visual Studio matches the file you are creating with the correct xsd file to drive Intellisense. Since Visual Studio obviously doesn't ship with the xsd for JAXB bindings files, I had to find it. I don't remember where I found it, but it is at the bottom of this post for your convenience. You simply place the xsd file in the directory: %Program Files%Microsoft Visual Studio 8\Xml\Schemas
You may have to restart Visual Studio, can't remember. But after that you should have Intellisense for the file that you are working on!

Here is an image of using Visual Studio with Intellisense for the JAXB bindings file.

I don't know if Eclipse supports anything like this. There is probably a plugin somewhere.

bindingschema_1_0.xsd (16.23 KB)

 

Sayed Ibrahim Hashimi

Wednesday, January 04, 2006 6:51:37 AM (GMT Standard Time, UTC+00:00)  #    Comments [5]  | 
Sunday, January 01, 2006

Lately I've been using Team Foundation Server (TFS) for my source control and Team Build to perfom builds on a dedicated machine. Everything was working pretty good until the other night, I created a new team project and added some projects to the source control. Following this I created a new Team Build and tried to execute it, but I received an error, TF42053: The build machine is not configured to build for server..." I received this error when I tried to invoke Team Builds that were previously building correctly as well, not just on the new project. The image of the dialog box is shown below (click for larget image).

As instructed I opened up the TFSBuildService.exe config file which is at %Program Files%/Microsoft Visual Studio 8/Common7/IDE/PrivateAssemblies/TFSBuildService.exe.config, I opened up that file and located the AllowedTeamServer key element. Originally it looked like:

<add key="AllowedTeamServer" value="" />

By the way my TFS setup is on a single tier, so the application and data tier are both on the same machine, and that is also the machine that I was trying to run the build on. So I changed the value to:

<add key="AllowedTeamServer" value="MACHINE_NAME"/>

Then I restarted the service and invoked the team build again, and all was fine. I'm not sure why I was able to build previously but not now. The only things I can think of a few changes to the envrionment:
  1) a minor hardware change (I disconnected a removable hard drive from that machine)
  2) the IP address of that machine changed
  3) machine was restarted a few times
  4) new team project with its source control
So I'm not quite sure why I received this error, but I'm glad it was an easy fix. Now I can continue working on my project.

Sayed Ibrahim Hashimi

Sunday, January 01, 2006 6:03:08 PM (GMT Standard Time, UTC+00:00)  #    Comments [5]  | 

Theme design by Jelle Druyts