9.3 Custom Wizard Engines

We are not obliged to use the wizard engine in our custom wizards. Instead, we can write a COM server, implementing the IDTWizard interface (the same interface that the wizard engine implements) and have more control over the way the wizard looks and works.

There are two reasons you might choose to write a custom IDTWizard implementation instead of using the wizard engine infrastructure. One is that you can write your wizard in the language of your choice (either managed or unmanaged), rather than being forced to use JScript. The second reason is that, if your wizard has a UI, you get total control over ityou are not limited to using HTML.

The downside of writing the custom implementation is that you can no longer use the convenient template directive syntaxyou are responsible for generating any files yourself. However, if you plan to support both VB.NET and C#, this may well not be a problem: if you use the classes in the System.CodeDom namespace, you can write a single code generator that can create source code in either language, removing the need to have two different sets of template files for each language.

The CodeDom is currently supported for only C# and VB.NET, so you cannot use this technique to generate J# or C++ files.

As an example, let's build a wizard that creates a new file containing a class with a skeleton implementation of the IDTWizard interface. (So we will write a wizard wizard, so to speak.) We will use the CodeDom API to generate the necessary source files. The CodeDom is a .NET API, so we will write this wizard in C#. (But once we've written it, it will be able to generate new VB projects as well as new C# projects.)

Besides implementing the IDTWizard interface, the class that our wizard generates will need to support COM registration. For a .NET project, this means the containing assembly must be signed using a strong name, as well as have the appropriate attributes to support COM registration. This wizard is aiming only to add new classes to an existing project, so we can use the SN Class Library wizard we wrote earlier to create a project that will be signed with a strong name. This new wizard will just have to add a Guid attribute to the class it creates.

The IDTWizard interface has only one method, Execute. The first argument to Execute is a reference to the DTE object (the top-level object in the VS.NET object model). The ContextParams parameter is an array, the contents of which depend on whether this wizard is executing as a project wizard or an item wizard. Table 9-5 shows what is passed in each case.

Table 9-5. IDTWizard.Execute ContextParams argument


Project wizard

Item wizard


WizardType enum

WizardType enum


Project name string

Project name string


Local directory

ProjectItems object


VS.NET install directory

Local directory


FExclusive (Boolean indicating whether the project should be created in a brand-new solution or be added to the current solution)

ItemName (name of the item to be added)



VS.NET install directory

The third parameter, CustomParams, is a collection of the Param elements from the .vsz file. For each Param=<Value> line in the .vsz file of the wizard, there will be a string in the CustomParams array. These strings are in the same format as they appear in the .vsz file. So, with the .vsz file shown in Example 9-13, there would be two entries: "MY_PARAM = Foo", and "This is another parameter".

Example 9-13. Custom parameters in a .vsz file


Param="MY_PARAM = Foo"

Param="This is another parameter"

The last parameter of the Execute method is the logical return valueit indicates the outcome of the wizard. It must be set to one of the enumerated values listed in Table 9-6.

Table 9-6. wizardResult enum






Wizard succeeded



Wizard failed



Wizard was canceled



Wizard was backed out of (i.e., Back button on UI was clicked, causing execution to stop)

Our Execute method must perform the following steps:

  1. Determine the language of the project

  2. Create a CodeDom object appropriate to the language

  3. Display a Windows Forms-based UI, which asks the user for the class name, namespace, and ProgID of the class to be generated

  4. Use the CodeDom to generate the source code file, which will contain a class that implements IDTWizard with a skeleton implementation of the Execute method

  5. Add the ProgID and Guid attribute to the class

  6. Add the file to the project

  7. Add a reference to envdte.dll to the project references

Example 9-14 shows the implementation of Execute. It starts by caching the reference to the Project object in a member variable. Next, it switches code path based upon the language of the project. (If the project is a language other than VB.NET or C#, the wizard raises an error message.)

Example 9-14. IDTWizard implementation
public void Execute(object Application, int hwndOwner,

    ref object[  ] ContextParams, ref object[  ] CustomParams,

    ref EnvDTE.wizardResult retval)


    // The third item in ContextParams is the pProjectIitems object.

    ProjectItems pi = (ProjectItems)ContextParams[2];


    // Get the project from the ProjectItems reference.

    project = pi.ContainingProject;


    // We use the CodeModel to find out which language is in use.

    CodeModel cm = project.CodeModel;


    retval = EnvDTE.wizardResult.wizardResultSuccess;


    {    // Switch based upon language of project.

        case CodeModelLanguageConstants.vsCMLanguageCSharp:

            DoCSharp(  );



        case CodeModelLanguageConstants.vsCMLanguageVB:

            DoVB(  );




            MessageBox.Show("This wizard can only be used from "

                            "C# or VB.NET projects");

            retval = EnvDTE.wizardResult.wizardResultFailure;




The CodeDom defines an abstract API for generating code. This means that the majority of our code generation will be common for the VB.NET and C# code paths. Our example wizard has a class called CodeGen that provides a generic implementation for generating the code file using any CodeDomProvider, shown in Example 9-15.

Example 9-15. Code generation with CodeDom
class CodeGen


    public static string Generate(string filename, string extension,

        string classname, string nspace, string progid,

        CodeDomProvider cdp)


        // Create a code generator.

        ICodeGenerator cg = cdp.CreateGenerator(  );


        // Need two namespaces so that the namespace imports appear

        // in the "outer" namespace that doesn't have a name.

        System.CodeDom.CodeNamespace cnamespace2 =

                 new System.CodeDom.CodeNamespace(  );

        System.CodeDom.CodeNamespace cnamespace =

                 new System.CodeDom.CodeNamespace(nspace);


        // Add the approprate imports.

        cnamespace2.Imports.Add(new CodeNamespaceImport("System") );

        //  The vs.net object model.

        cnamespace2.Imports.Add(new CodeNamespaceImport("EnvDTE") );

        //  Needed for the COM interop attributes.


                new CodeNamespaceImport("System.Runtime.InteropServices"));

        //  If this is VB--add the VisualBasic namespace.

        if(cdp.GetType(  )=  =typeof(VBCodeProvider))


                        new CodeNamespaceImport("Microsoft.VisualBasic"));


        // Create the new class.

        CodeTypeDeclaration co = new CodeTypeDeclaration (classname);


        // Add the Guid attribute.

        Guid g = Guid.NewGuid(  );


                new CodeAttributeDeclaration("Guid",

                        new CodeAttributeArgument(

                        new CodePrimitiveExpression(g.ToString("D"))))) ;


        // Implement IDTWizard. (Must also add Object base type

        // for VB.NET, otherwise the VBCodeDom uses "Inherits IDTWizard" 

        // instead of "Implements IDTWizard".)

        CodeTypeReference ctr =

                new CodeTypeReference(typeof (EnvDTE.IDTWizard));


        co.BaseTypes.Add( ctr);


        // Add the type to the namespace.

        cnamespace.Types.Add (co);


        // Add the Execute method.

        CodeMemberMethod cm = new CodeMemberMethod(  );

        cm.Name = "Execute";

        cm.PrivateImplementationType = ctr;

        cm.Attributes = MemberAttributes.Public | MemberAttributes.Final ;


        // Add parameters.

        cm.Parameters.Add (

                new CodeParameterDeclarationExpression(typeof(object),


        cm.Parameters.Add (

                new CodeParameterDeclarationExpression(typeof(int),


        CodeParameterDeclarationExpression cp =

                new CodeParameterDeclarationExpression(typeof(object[  ]),



        cm.Parameters.Add (cp);

        cp = new CodeParameterDeclarationExpression(typeof(object[  ]),


        cp.Direction = FieldDirection.Ref;


        cp = new CodeParameterDeclarationExpression(



        cp.Direction = FieldDirection.Ref;



        // Add the method to the type.

        co.Members.Add (cm);        


        // Create the text file.

        using (TextWriter w = new StreamWriter(fullFileName, false));


            //Generate the code.

            cg.GenerateCodeFromNamespace( cnamespace2,w,null);

            cg.GenerateCodeFromNamespace (cnamespace, w, null);


        return fullFileName;



Recall that our IDTWizard implementation in Example 9-14 called one of two functions depending on the project language. With the code generation class in Example 9-15 in place, all these two functions need to do is create an instance of the appropriate CodeDomProvider:

private void DoCSharp(  )


    CSharpCodeProvider cdp = new CSharpCodeProvider(  );

    InternalExecute(cdp, ".cs");


private void DoVB(  )


    VBCodeProvider cdp = new VBCodeProvider(  );

    InternalExecute(cdp, ".vb");


The rest of the wizard code is shown in Example 9-16. It is dedicated to displaying the UI, executing the CodeDom class, adding the file to the project, and adding a reference to envdte.dll to the project. (This UI is a simple Windows Forms dialog that asks the user for a class name and a namespace. It contains no code of direct relevance to writing wizards, so it is not shown here.)

Example 9-16. Finishing off the wizard
private void GetInputs(  )


    // Display the form to retrieve user settings.

    wf = new WizardForm(  );

    wf.ShowDialog(  );

    wf.Dispose(  );



private void InternalExecute(CodeDomProvider codeDom, string ext)


    GetInputs(  );

    // Get a temp directory.

    TempFileCollection tfc = new TempFileCollection(  );

    string cn = wf.classNameTextBox.Text;

    string ns = wf.namespaceTextBox.Text;

    string filepath = tfc.BasePath + wf.classNameTextBox.Text;

    string progid = wf.progIdTextBox.Text;


    // Execute the code generation method.

    string filefull = CodeGen.Generate(filepath, ext, cn, ns, progid,



    // Add generated file to project.




private void AddFiletoProject(string filename,string realname)


    // Add a reference to envdte.dll.

    VSProject vsp = (VSProject)project.Object;



    // Add the file to the project.



One last step is required to make this wizard work: we need to add an entry to the appropriate .vsdir file(s) and add the corresponding .vsz file to the appropriate directory.

Since this is an item wizard, the .vsz file will go the item directories for both VB and C#VC#\CSharpProjectItems and Vb7\VBProjectItems, respectively. It may seem a bit wasteful to have two copies, and although we could try and use long relative paths in the .vsdir files, the .vsz is so simple that it's not really worth the effort. The .vsz just needs to have the correct ProgID for the class:



Because this example does not use the wizard engine, the wizard files do not need to be installed in the VC#\VC#Wizards or VB7\VBWizards directories. However, the wizard will need to be installed somewhere on the system and registered with COM. (COM registration working as it does, however, it won't matter where you choose to install it.)