Tuesday, September 12, 2006

How to create a guidance package

After creating my first test guidance package using the Guidance Automation Toolkit (GAT), I've cobbled together some bullet point steps on how to do it. This is really rough at the moment, but I'll be adding to it as my knowledge grows. It took a while because there's nothing similar to it, like a walkthrough, and I didn't like using the meta guidance package because I wanted to understand how it all hung together first.

Creating the initial solution

  • Create a new solution with a class library project.
  • Add References:
EnvDTE
EnvDTE80
Microsoft.Practices.Common
Microsoft.Practices.ComponentModel
Microsoft.Practices.RecipeFramework
Microsoft.Practices.RecipeFramework.Common
Microsoft.Practices.RecipeFramework.Library
Microsoft.Practices.RecipeFramework.VisualStudio
Microsoft.Practices.WizardFramework
System
System.Windows.Forms
  • Tools->Guidance Package Manager->Enable/Disable Packages->Choose Guidance Package Development
  • Create a new class library project, name it Installer
  • Add References
Microsoft.Practices.RecipeFramework
Microsoft.Practices.RecipeFramework.VisualStudio
Microsoft.VisualStudio.TemplateWizardInterface
System
System.Cofiguration.Install
  • Set a project dependence of your guidance package project on the installer project
  • Create a new class called 'InstallerClass' in the installer project.
using Microsoft.Practices.RecipeFramework; 
namespace TestGuidancePackageInstaller
{
    /// <summary>
    /// Installer class for the guidance package
    /// </summary>
    [System.ComponentModel.ToolboxItem(false)]
    public class InstallerClass : ManifestInstaller
    {
    }
}
  • Create the guidance package xml configuration document, MyGuidancePackageName.xml, see documentation for details
<?xml version="1.0" encoding="utf-8" ?>
<GuidancePackage xmlns="http://schemas.microsoft.com/pag/gax-core"
Name="GuidancePackageName"
Caption="My Guidance Package"
Description="A test guidance package"
BindingRecipe="BindingRecipe"
Guid="fdd8f06f-6d6d-4228-96db-f842076764af"
SchemaVersion="1.0">
<Overview Url="Docs\Overview.htm"/>
<Recipes>
<Recipe Name="BindingRecipe">
<Types>
<TypeAlias Name="RefCreator" Type="Microsoft.Practices.RecipeFramework.Library.Actions.CreateUnboundReferenceAction, Microsoft.Practices.RecipeFramework.Library"/>
</Types>
<Caption>Creates unbound references to the guidance package</Caption>
</Recipe>
</Recipes>
</GuidancePackage>
  • Set the properties of MyGuidancePackageName.xml to BuildAction="Content", Copy to Output Directory="Copy if newer"
  • Build the solution
  • On the solution context menu select 'Register Guidance Package'
  • Open a new instance of VS, create a new project, go to Tools->Guidance Package Manager->Enable/Disable Packages
  • Should see you package

Create the Binding Recipe

  • Add a new Recipe element under Recipes
  • Set its name to 'BindingRecipe'
  • Add attribute BindingRecipe="BindingRecipe" to the GuidancePackage root element
  • Add types:
<Types>
<TypeAlias Name="RefCreator" Type="Microsoft.Practices.RecipeFramework.Library.Actions.CreateUnboundReferenceAction, Microsoft.Practices.RecipeFramework.Library"/>
</Types>
  • For each recipe you want to reference, add Actions:
<Action Name="<name of action>" Type="RefCreator" AssetName="<name of recipe to bind>" ReferenceType="<unboundRecipeReference class>" />

Create a recipe

  • Add a new Recipe element under Recipes
  • Set attribtes Name="its name" Bound="false"
  • Add Caption
  • Add HostData, this adds the recipe to the Solution, Project or Item context menus
<HostData>
<Icon ID="<icon number>"/>
<CommandBar="Project"/>
  • Add Arguments to specify the arguments that this recipe requires
  • Add GatheringServiceData to define the wizard that gets the arguments
  • Add Actions to specify what the recipe should do.

How to create and execute a T4 template

  • Create a folder 'Templates' in the guidance package project
  • Create a folder 'Text' in the 'Templates' folder
  • Add a template file with the extension .t4
  • Set the properties of the .t4 file: 'Build Action = Content', 'Copy to output directory = Copy if newer'
  • Write your .t4 template (see documentation on this)
  • Create a new Recipe as above
  • Create Arguments for all the properties of the .t4 template
  • Add an argument for the TargetFileName that adds .cs to the class name argument
<Argument Name="TargetFileName">
<ValueProvider Type="Microsoft.Practices.RecipeFramework.Library.ValueProviders.ExpressionEvaluatorValueProvider, Microsoft.Practices.RecipeFramework.Library" 
Expression="$(ClassName).cs">
<MonitorArgument Name="ClassName"/>
</ValueProvider>
</Argument>
  • Add an argument for the currently selected project
<Argument 
Name="CurrentProject" 
Type="EnvDTE.Project, EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<ValueProvider Type="Microsoft.Practices.RecipeFramework.Library.ValueProviders.FirstSelectedProject, Microsoft.Practices.RecipeFramework.Library" />            
</Argument>
  • Add an action to execute the template:
<Action Name="<action name>" 
Type="Microsoft.Practices.RecipeFramework.VisualStudio.Library.Templates.TextTemplateAction, Microsoft.Practices.RecipeFramework.VisualStudio.Library"
Template="Text<name of template>.t4">
<Input Name="<name of template property>" RecipeArgument="<recipe argument name>"/>
… as many input elements as you have properties
<Output Name="Content"/>
</Action>
  • Add an action to write a file to the currently selected project:
<Action Name="<actio name>" Type="Microsoft.Practices.RecipeFramework.Library.Actions.AddItemFromStringAction, Microsoft.Practices.RecipeFramework.Library" Open="true">
<Input Name="Content" ActionOutput="GenerateHelloClassAction.Content" />
<Input Name="TargetFileName" RecipeArgument="TargetFileName" />
<Input Name="Project" RecipeArgument="CurrentProject" />
</Action>

How to use solution and project templates to create a new solution structure from the New->Project menu in VS

  • Add a project folder, 'Templates', to the Guidance Package project.
  • Add a sub folder to 'Templates' called 'Solutions'.
  • Add a file called Solution.vstemplate to the 'Solutions' folder
  • Add an icon named Solution.ico to the 'Solutions' folder
  • Write the Solution.vstemplate. This is a 'multi-project' vstemplate with additions for GAT, example below:
<VSTemplate
Version="2.0"
Type="ProjectGroup"
xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
<TemplateData>
<Name>Test Guidance Package</Name>
<Description>A guidance package created to learn how to create guidance packages</Description>
<ProjectType>CSharp</ProjectType>
<Icon>Solution.ico</Icon>
<CreateNewFolder>false</CreateNewFolder>
<DefaultName>GatTest</DefaultName>
<ProvideDefaultName>true</ProvideDefaultName>
</TemplateData>
<TemplateContent>
<ProjectCollection>
<ProjectTemplateLink ProjectName="$ProjectName$">Projects\Domain\Domain.vstemplate</ProjectTemplateLink>
</ProjectCollection>
</TemplateContent>
<WizardExtension>
<Assembly>Microsoft.Practices.RecipeFramework.VisualStudio, Version=1.0.60429.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
<FullClassName>Microsoft.Practices.RecipeFramework.VisualStudio.Templates.UnfoldTemplate</FullClassName>
</WizardExtension>
<WizardData>
<Template xmlns=http://schemas.microsoft.com/pag/gax-template
SchemaVersion="1.0"
Recipe="CreateSolution">
<References>
</References>
</Template>
</WizardData>
</VSTemplate>
  • Note the Recipe="CreateSolution" attribute of the template element under WizardData. This should point to a recipe defined in the MyGuidancePackage.xml file. This recipe is executed when the solution loads so you can use it to gather information from the user and execute any actions to build the solution items.
  • Under the 'Solutions' folder, create a folder called 'Projects'
  • Under the 'Projects' folder, create a folder for each project. Give it the project name
  • Add a ProjectName.vstemplate file to the project folder. Here's an example:
<VSTemplate
Version="2.0"
Type="Project"
xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
<TemplateData>
<Name>Domain model</Name>
<Description>A domain model for the application</Description>
<Icon>Domain.ico</Icon>
<ProjectType>CSharp</ProjectType>
<CreateNewFolder>false</CreateNewFolder>
<DefaultName>Domain</DefaultName>
<ProvideDefaultName>true</ProvideDefaultName>
</TemplateData>
<TemplateContent>
<Project File="Domain.csproj" ReplaceParameters="true">
<ProjectItem ReplaceParameters="true">Properties\AssemblyInfo.cs</ProjectItem>
</Project>
</TemplateContent>
<WizardExtension>
<Assembly>Microsoft.Practices.RecipeFramework.VisualStudio, Version=1.0.60429.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
<FullClassName>Microsoft.Practices.RecipeFramework.VisualStudio.Templates.UnfoldTemplate</FullClassName>
</WizardExtension>
<WizardData>
<Template xmlns=http://schemas.microsoft.com/pag/gax-template
SchemaVersion="1.0">
<References>
</References>
</Template>
</WizardData>
</VSTemplate>
  • Add a ProjectName.csproj file. This is a standard project template. Here's an example, but you can take any existring .csproj file as a template (just insert the appropriate $variables$ at the right place) 

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>$guid1$</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>$safeprojectname$</RootNamespace>
<AssemblyName>$safeprojectname$</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System"/>
<Reference Include="System.Data"/>
<Reference Include="System.Xml"/>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
</Project>

  • Add a ProjectName.ico icon file
  • Set the properties of all the items added above to Build Action = "Content", Copy to output directory = "Copy if newer"
  • Add a new folder 'Properties' under the project folder
  • Add an AssemblyInfo.cs file under the Properties folder
  • Insert the appropriate $variables$ to replace the values that visual studio automatically provides. Here's an example:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("$projectname$")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("$registeredorganization$")]
[assembly: AssemblyProduct("projectname")]
[assembly: AssemblyCopyright("Copyright © $registeredorganization$ $year$")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("$guid1$")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

  • Set the properties of the AssemblyInfo.cs file to Build Action="Content", Copy to output directory = "Copy if newer"
  • Build the solution and Register the guidance package
  • Open a new instance of Visual Studio, select File->New->Project, Your Guidance Automation Package should now appear under 'Guidance Packages'.

Adding documentation to your guidance package

  • Create a new solution folder called 'Docs'.
  • Add a new HTML page called 'Overview.htm'
  • Set the properties of the Overview.htm file to Build Action="Content", Copy to output directory = "Copy if newer"
  • Add the following <Overview> element to your MyGuidancePackage.xml file under the document element:
<Overview Url="Docs\Overview.htm"/>
  • When you choose your guidance package, the Overview.htm page will display in the guidance navigator window 

2 comments:

Anonymous said...

Is there any chance i too could retrieve your source this really has been doing my head in.

Email addy: grantspiteri@hotmail.com thanks.

Mike Hadlow said...

Anonymous,

I'm really sorry, but I did this work at a client site more than a year and a half ago which I've long since left :( I imagine the GAT has moved on significantly since then so the instructions in this post are probably out of date. I haven't done any GAT work since, so I'm pretty out of touch with what's happening with it. The only thing I can suggest is putting your questions up on the GAT forum.

Mike