Automating the Creation of an OS Design Project

Posted by: Brent Bishop

As promised in my previous post this time we'll get started automating Platform Builder by taking a look at the automation interfaces of the New OS Design Wizard.  For this add-in we'll add a menu item to Platform Builder that will create a new OS design with a randomly chosen BSP and OS design template and then ask the user if they want to build it or not.

Step 1: Create a new Visual Studio add-in following step 1-10 in this previous blog with three modifications.

  1. In step 3, I named my project OSDesignSample (in Visual Studio's New Project dialog).

  2. I gave it the following name and description in step 7 (on page 3 of the Visual Studio Add-in Wizard):

    Name: Platform Builder - OS Design Sample
    Description: Shows how to use the Platform Builder object model to create and build an OS design project.

  3. In step 8, I selected "Yes, create a 'Tools' menu item..." and did not select “I would like my Add-in to load when the host application starts” (on page 4 of the Visual Studio Add-in Wizard).

Visual Studio - Add-in Wizard Page 4

Step 2: Add a reference to Microsoft.PlatformBuilder.Automation.6.00.dll by right clicking on the References node in the Solution Explorer and then clicking on "Add Reference...". When the dialog comes up, find and select Microsoft.PlatformBuilder.Automatio.6.00 and click OK.

Visual Studio - Add Reference Dialog

Step 3: Use the same process to add a reference to System.Windows.Forms.dll. 

Step 4: Add a new class to the project for all the code that we will be adding for the add-in. Add a new class to the project by right clicking on the project in the Solution Explorer and clicking Add -> Class. Next name the class, I named my class OSDesignSample, and click Add.

Step 5: Open Connect.cs (created by Visual Studio) and scroll to the bottom. The Exec method is called when our add-in's menu item is clicked. Here we add a call to a new static method in our newly created OSDesignSample class.

    OSDesignSample.Run(_applicationObject);

We need to pass a reference to Visual Studio's automation interfaces to our OSDesignSample class so that we can call those interfaces from our class. The completed method now looks like this:

    public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)
{
handled = false;
if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if(commandName == "OSDesignSample.Connect.OSDesignSample")
{
OSDesignSample.Run(_applicationObject);
handled = true;
return;
}
}
}

While I was here in this file I also changed the text of the menu item by changing the following line in OnConnection from:

    Command command = commands.AddNamedCommand2(_addInInstance, "OSDesignSample", "OSDesignSample", "Executes the command for OSDesignSample", ...

to:

    Command command = commands.AddNamedCommand2(_addInInstance, "OSDesignSample", "PB - OS Design Sample", "Creates an OS design project.", ...

Step 6: Add the following using statements to the top of OSDesignSample.cs:

    using System.IO;
using System.Windows.Forms;
using EnvDTE80;
using Microsoft.PlatformBuilder.Automation;
using PBWizards = Microsoft.PlatformBuilder.Automation.Wizards;

Step 7: I changed the class to a static class because I only need two static methods in it:

     internal static class OSDesignSample

Step 8: We need to implement the static Run method we just made a call to. In addition to implementing the Run method we added a call to a few minutes ago, I added a constant string to use as the caption for message boxes and method for getting a unique name for the project.  Here's the completed class:

    /// <summary>
/// The caption to use in message boxes.
/// </summary>
private const string Caption = "PB - OS Design Sample";

/// <summary>
/// Runs the sample that creates and builds a OS design project.
/// </summary>
/// <param name="dte">A reference to the DTE for controlling Visual Studio.</param>
public static void Run(DTE2 dte)
{
try
{
// Variables
string name;
string location;
string message = "The project was created with the following parameters:\r\n\r\n";
Random random = new Random();

// Get the wizard interface
PBWizards.OSDesign.OSDesignWizard wizard = (PBWizards.OSDesign.OSDesignWizard)dte.GetObject("PBCEOSWizard");
location = Path.Combine(wizard.LocationStep.DefaultWinceroot, "OSDesigns");
name = GetUniqueName(location);
wizard.LocationStep.Directory = Path.Combine(location, name);
wizard.LocationStep.Name = name;
wizard.LocationStep.Winceroot = wizard.LocationStep.DefaultWinceroot;

// Select a random BSP (from those installed on the machine)
int bsp = random.Next(wizard.BspStep.Bsps.Count) + 1;
wizard.BspStep.Bsps.Item(bsp).Selected = true;
message += "\tBSP: " + wizard.BspStep.Bsps.Item(bsp).Name + "\r\n";

// Select a random template (from the ones available)
int template = random.Next(wizard.TemplateStep.OSDesignTemplates.Count) + 1;
wizard.TemplateStep.OSDesignTemplates.Item(template).Selected = true;
message += "\tTemplate: " + wizard.TemplateStep.OSDesignTemplates.Item(template).Name + "\r\n";

// Select a random flavor (from the ones available)
if (wizard.TemplateStep.FlavorStep != null &&
wizard.TemplateStep.FlavorStep.OSDesignFlavors != null &&
wizard.TemplateStep.FlavorStep.OSDesignFlavors.Count > 0)
{
int flavor = random.Next(wizard.TemplateStep.FlavorStep.OSDesignFlavors.Count) + 1;
wizard.TemplateStep.FlavorStep.OSDesignFlavors.Item(flavor).Selected = true;
message += "\tFlavor: " + wizard.TemplateStep.FlavorStep.OSDesignFlavors.Item(flavor).Name + "\r\n";
}

// Create the project
string project = wizard.CreateOSDesign();

// Create a solution if one is not open
if (!dte.Solution.IsOpen)
{
dte.Solution.Create(wizard.LocationStep.Directory, wizard.LocationStep.Name);
}

// Add the project to the solution
dte.Solution.AddFromFile(project, false);

// Save the solution
dte.ExecuteCommand("File.SaveAll", "");

// Display a message when done
message += "\r\nDo you want to build the project that was created?";
if (MessageBox.Show(message, Caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
dte.Solution.SolutionBuild.Build(false);
}
}
catch (Exception e)
{
// Show the error message (NOTE: capturing all exceptions for debugging because Visual Studio will eat all unhandled exceptions thrown by an add-in)
MessageBox.Show(e.Message, Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

/// <summary>
/// Find the next available project name at the location.
/// </summary>
/// <param name="location">The path to where the project will be created.</param>
/// <returns>A unique project name.</returns>
private static string GetUniqueName(string location)
{
// Variables
string name;
string path;

// Make sure the directory exists
if (!Directory.Exists(location))
{
Directory.CreateDirectory(location);
}

// Get a list of existing OS design directories
string[] projects = Directory.GetDirectories(location, "OSDesign*");

// Find the next available project directory
for (int i = 1; i < 100; i++)
{
// Variables
bool found = false;

// Create the project name
name = string.Format("OSDesign{0}", i);
path = Path.Combine(location, name);

// See if that project already exists
foreach (string project in projects)
{
if (path.ToLower() == project.ToLower())
{
found = true;
break;
}
}

// Return the next available project
if (!found)
{
return name;
}
}

// Wasn't able to find one
throw new Exception("ERROR: Unable to fine a unique project name.");
}
}

Step 9: Now we can build and try out the new add-in.  After building the project you click Debug -> Start Without Debugging to start a new instance of Visual Studio that will have the add-in loaded.  The Menu item for our add-in will be added to the Tools menu.  So we can click Tools -> PB - OS Design Sample to run the add-in.

OS Design Created

That's what it takes to programmatically create a new OS design with the Platform Builder automation object model.