The (lack of) IDesignerHost in Visual Studio 2008 (Orcas) ASP.NET Web pages

My article HOWTO: Getting IDesignerHost and IHTMLDocument of WebForms from a Visual Studio .NET add-in explained how to do this with VS.NET 2002/2003/2005, but if you have tried it, it doesn’t work with VS 2008 (there is no IServiceProvider here). The underlying reason is that the new ASP.NET/HTML designer is based on the FrontPage editor, not in the old Internet Explorer editor (MSHTML, aka “Trident”). See:

http://blogs.msdn.com/mikhailarkhipov/archive/2007/01/20/sapphire-new-web-form-designer-in-visual-studio-orcas.aspx

FrontPage is now deprecated but Microsoft used its HTML editor in two new products: the SharePoint 2007 Designer and Expression Web:

http://office.microsoft.com/en-us/frontpage/default.aspx

The EnvDTE.HtmlWindow.CurrentTabObject property returns a “DispDesignerDocument” object that initially I thought belonged to the SharePoint Designer assemblies but the cast failed, and after some hours I discovered that it belongs to Expression Web, so you have to add a reference to the C:\Program Files\Microsoft Web Designer Tools\VWDFPEDITAX.DLL file (use the Browse tab) to generate two interop assemblies (Interop.Microsoft.VisualWebDeveloper.Interop.WebDeveloperInternal and Interop.Microsoft.VisualWebDeveloper.Interop.WebDeveloperPage). See:

http://blogs.msdn.com/mikhailarkhipov/archive/2007/08/20/what-is-microsoft-visual-studio-web-authoring-component.aspx

And while it seems that the Expression Web reference model is not documented, I guess it’s pretty much the same than the one of SharePoint Designer 2007, which is documented:

SharePoint Designer 2007 Developer Reference
http://msdn2.microsoft.com/en-us/library/bb149147.aspx

The interesting part is that the new HTML parser returns elements for both HTML and ASP.NET controls (in the previous versions you had to use MSHTML to get the HTML controls, and IDesignerHost to get the ASP.NET controls, because it seems that <asp> tags were alien to the IE-based editor). Also, as far as I know after a lot of research, there is no IDesignerHost in VS 2008 for ASP.NET Web pages (there wasn’t either in previous versions of VS for HTML pages, only for ASP.NET pages). Internally, Visual Studio uses classes to encapsulate each HTML element or web control and provides PropertyDescriptors, TypeConverters, and CustomTypeDescriptors to interact with the Properties window and other VS areas. So, for the most part, at design-time ASP.NET controls act as HTML controls (and not as IComponent-based components), and ASP.NET Web pages act as pure HTML pages (which as I said, did not have IDesignerHost in previous versions of VS either), therefore providing a much appreciated unified model at the cost of some losses for some people that started with VS 2005:

ASP.NET v2.0: is the non-visual components support finally back?
http://weblogs.asp.net/cazzu/ComponentsNotBack

Frustrations with command-line add-ins for Visual Studio

Here is another frustration when working with the automation model of Visual Studio: suppose that you want to make an add-in that is loaded before a command-line build (for example using devenv.exe mysolution.sln /build Debug) to perform some checkings that, if failed, should abort the build. Quite reasonable, isn’t it?. So, how do you approach this? Any programmer familiar with the automation model knows that the add-in wizard offers an option to create command-line add-ins and that the OnConnection method has a connectMode = ext_ConnectMode.ext_cm_CommandLine value, so that seems the way to go. But it happens that:

  • VS.NET 2002 does never receive that value, even when you launch a build from the command line. I reported this bug long time ago: BUG: ext_ConnectMode.ext_cm_CommandLine flag not passed to Visual Studio .NET 2002 add-ins.
  • VS.NET 2003 fixed that bug, at least apparently. Further experimentation has shown that if the solution to build is very small (for example, the default ClassLibrary project created by VS) then the build is performed without the add-in loaded !!! If the solution is bigger so that the compilation is not so immediate, then the add-in is loaded (you can test all this if you create a simple add-in with a messagebox in the OnConnection and OnDisconnection methods). Apparently it seems that add-ins are not loaded before the build begins but some time later, maybe in some parallel fashion. This, of course, ruins this approach.
  • VS 2005 has a somewhat different bug and the add-in is never loaded, no matter the size of the solution. I reported this here yesterday:

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=303803

(UPDATE August 17, 2008: Microsoft released a hotfix for this problem after VS 2005 SP1: FIX: An command-line add-in does not load when you start the Visual Studio 2005 IDE)

  • VS 2008 (Beta 2) seems to fix all the problems of all predecessors and it loads the add-in even for builds of small solutions (I have not tested it very much, so I could be wrong)
  • Bets are open about the behavior in the future VS 2010 or whatever new version… 😉

Well, let’s assume that the behavior is correct and the add-in gets loaded. How do you write some results to the log file or the console output that Visual Studio uses (with the /out switch)? While I was able to write to the Output window when using the full IDE, I was unable to write to the log file. After some searching it seems that Console.Write should do the trick:

http://www.dotnet247.com/247reference/msgs/30/151588.aspx

But I was unable to succeed. Maybe someone from Microsoft can verify if this indeed works…

OK, the add-in can write to a different log file, no big deal. Now, how can the add-in cancel the build if it needs to? It happens that, in an apparent terrible oversight, the Build events lack a cancel parameter:

HOWTO: Canceling a build from a Visual Studio .NET add-in.
http://www.mztools.com/articles/2005/MZ2005010.aspx

No big deal because you can use DTE.ExecuteCommand(“Build.Cancel”)…. Alas, when the IDE is run for a command-line build, commands are not created.

Then you can try some ugly TerminateProcess API call, but it seems that it won’t work properly…

So, why on earth does the automation model provide a command-line connectMode that, when you are lucky enough that your VS version honors it, is basically useless? It would be nice if the Microsoft people could address all this in the automation model (I’m sure that an SDK package can handle all this OK)…

Well, after some relaxing time, I got a new idea. Forget the command-line add-in. Let’s try another approach. I don’t really care about the build. I just want some command-line utility to automate Visual Studio to perform a review before another command-line utility (devenv.exe) perform the build. If the first utility reports some error, my script does not execute the build. Sometime ago I wrote an article about how to automate Visual Studio from the outside:

HOWTO: Automating Visual Studio .NET from outside the IDE
http://www.mztools.com/articles/2005/MZ2005005.aspx

So, the idea is:

  • Create an add-in that offers a public method Sub Review(ByVal logFileName As String) in the Connect class.
  • Create a console application that receives a solution file name, examines its format (“7.00”, “8.00”, “9.00”, “10.00”) to guess the Visual Studio ProgID to use (VisualStudio.DTE.7, VisualStudio.DTE.7.1, VisualStudio.DTE.8.0, VisualStudio.DTE.9.0) (don’t ask me why the solution format numbers and the ProgID numbers don’t match), creates the DTE object as the article above explains, iterates its DTE.AddIns collection and locates the add-in by the AddIn.ProgId property, call its Connect = True property to load it, calls DTE.Solution.Open(solutionFileName) to load the solution, gets the AddIn.Object property to get the instance of the class that represents the add-in and then calls its Review(logFileName) method. Sounds feasible, right?

The first problem is that AddIn.Object returns a System.__ComObject that can not be converted to the .NET type of the Connect class of the add-in, even if your console application has a reference to the add-in dll. I am not a COM expert, but since this works with in-process DLLs, it can only be that such casts are not allowed from one process to another. Fortunately, you can call the Review method using late binding (Option Strict Off) in VB.NET, or using InvokeMethod of reflection using C# or VB.NET.

So, basically, I got this working…most of the time :-(. I noticed that from time to time, I get COMExceptions of the kind ‘Call was Rejected By Callee’ (that is, RPC_E_CALL_REJECTED). Ummmhh, more frustation. After some searching, it happens that that’s normal automating COM servers and fortunately the MSDN docs of Visual Studio report this problem and solution:

Fixing ‘Application is Busy’ and ‘Call was Rejected By Callee’ Errors
http://msdn2.microsoft.com/en-us/library/ms228772(VS.80).aspx

Kudos to the Microsoft guy that documented that. I didn’t know about that esoteric IMessageFilter::RetryRejectedCall stuff but it seems to work.

I am posting the code so other add-in developers facing the same problem can report problems or improvements:

The add-in (named CommandLineAddIn) VB.NET code is:

Imports System
Imports Microsoft.VisualStudio.CommandBars
Imports Extensibility
Imports EnvDTE
Imports EnvDTE80

Public Class Connect
   Implements IDTExtensibility2

   Private m_DTE As DTE

   Private Sub OnConnection(ByVal application As Object, ByVal connectMode As ext_ConnectMode, ByVal addInInst As Object, ByRef custom As Array) Implements IDTExtensibility2.OnConnection

      ' MessageBox.Show(connectMode.ToString)

      m_DTE = DirectCast(application, DTE)

   End Sub

   Private Sub OnDisconnection(ByVal disconnectMode As ext_DisconnectMode, ByRef custom As Array) Implements IDTExtensibility2.OnDisconnection

      ' MessageBox.Show(disconnectMode.ToString)

   End Sub

   Public Sub ExecuteReview(ByVal logFullFileName As String)

      Dim logStreamWriter As System.IO.StreamWriter = Nothing

      Try

         logStreamWriter = New System.IO.StreamWriter(logFullFileName, False, System.Text.Encoding.Default)

         If m_DTE.Solution.IsOpen = False Then
            logStreamWriter.WriteLine("No solution loaded!")
         Else
            logStreamWriter.WriteLine("Solution has " & m_DTE.Solution.Projects.Count.ToString & " projects.")
         End If

      Catch ex As Exception

         If Not (logStreamWriter Is Nothing) Then
            logStreamWriter.WriteLine(ex.ToString)
         End If

      Finally

         If Not (logStreamWriter Is Nothing) Then
            logStreamWriter.Close()
            logStreamWriter.Dispose()
         End If

      End Try

   End Sub

   Private Sub OnAddInsUpdate(ByRef custom As Array) Implements IDTExtensibility2.OnAddInsUpdate
   End Sub

   Private Sub OnStartupComplete(ByRef custom As Array) Implements IDTExtensibility2.OnStartupComplete
   End Sub

   Private Sub OnBeginShutdown(ByRef custom As Array) Implements IDTExtensibility2.OnBeginShutdown
   End Sub

End Class

And the command-line utility is:

'---------------------------------------------------------------------------------------------
' Module VSAutomator.vb
'---------------------------------------------------------------------------------------------

Module VSAutomator

   Private Enum VisualStudioVersion
      Unknown = 0
      VSNET2002 = 1
      VSNET2003 = 2
      VS2005 = 3
      VS2008 = 4
   End Enum

   <STAThread()> _
   Sub Main(ByVal args() As String)

      Const ADDIN_PROGID As String = "CommandLineAddIn.Connect"
      Const ADDIN_METHOD As String = "ExecuteReview"

      Dim dte As EnvDTE.DTE = Nothing
      Dim dteType As Type
      Dim commandLineAddIn As CommandLineAddIn.Connect = Nothing
      Dim solutionFullFileName As String
      Dim solutionFolder As String
      Dim solutionName As String
      Dim logFullFileName As String
      Dim connectObject As Object = Nothing
      Dim connectObjectType As Type
      Dim version As VisualStudioVersion
      Dim progID As String
      Dim executableName As String
      Dim addIn As EnvDTE.AddIn
      Dim msgFilter As MessageFilter = Nothing

      Try

         msgFilter = New MessageFilter

         If args.Length = 0 Then
            executableName = IO.Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly.Location)
            ReportError("Usage: " & executableName & " solution_file_name.sln")
         Else
            solutionFullFileName = args(0)

            If Not IO.File.Exists(solutionFullFileName) Then
               ReportError("Solution file '" & solutionFullFileName & "' does not exist.")
            Else

               solutionFolder = IO.Path.GetDirectoryName(solutionFullFileName)
               solutionName = IO.Path.GetFileNameWithoutExtension(solutionFullFileName)
               logFullFileName = IO.Path.Combine(solutionFolder, solutionName & ".log")

               If IO.File.Exists(logFullFileName) Then
                  IO.File.Delete(logFullFileName)
               End If

               version = GetSolutionVersion(solutionFullFileName)

               If version = VisualStudioVersion.Unknown Then
                  ReportError("The format version of the solution file is not supported.")
               Else

                  progID = GetVisualStudioProgID(version)

                  dteType = System.Type.GetTypeFromProgID(progID)

                  If dteType Is Nothing Then
                     ReportError("Could not find the ActiveX Server for ProgID '" & progID & "'. Likely the proper version of Visual Studio is not installed.")
                  Else

                     dte = DirectCast(System.Activator.CreateInstance(dteType), EnvDTE.DTE)
                     dte.SuppressUI = True
                     dte.UserControl = False

                     addIn = GetAddInByProgID(dte, ADDIN_PROGID)

                     If addIn Is Nothing Then
                        ReportError("The Add-in " & ADDIN_PROGID & " was not found in Visual Studio.")
                     Else

                        addIn.Connected = True

                        connectObject = addIn.Object

                        dte.Solution.Open(solutionFullFileName)

                        connectObjectType = connectObject.GetType

                        connectObjectType.InvokeMember(ADDIN_METHOD, Reflection.BindingFlags.InvokeMethod Or Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public, Nothing, connectObject, New String() {logFullFileName})

                     End If

                  End If

               End If

            End If

         End If

      Catch ex As Exception
         ReportError(ex.ToString)
      Finally

         If Not (dte Is Nothing) Then
            Try
               dte.Quit()
            Catch ex As Exception
            End Try
         End If

         If Not (msgFilter Is Nothing) Then
            msgFilter.Dispose()
         End If

      End Try

   End Sub

   Private Sub ReportError(ByVal msg As String)

#If DEBUG Then
      MsgBox(msg)
#End If

      Console.WriteLine(msg)

   End Sub

   Private Function GetAddInByProgID(ByVal dte As EnvDTE.DTE, ByVal addinProgID As String) As EnvDTE.AddIn

      Dim addinResult As EnvDTE.AddIn = Nothing
      Dim addin As EnvDTE.AddIn

      For Each addin In dte.AddIns

         If addin.ProgID = addinProgID Then

            addinResult = addin
            Exit For

         End If

      Next

      Return addinResult

   End Function

   Private Function GetSolutionVersion(ByVal solutionFullFileName As String) As VisualStudioVersion

      Dim version As VisualStudioVersion = VisualStudioVersion.Unknown
      Dim solutionStreamReader As IO.StreamReader = Nothing
      Dim firstLine As String
      Dim format As String

      Try

         solutionStreamReader = New IO.StreamReader(solutionFullFileName)
         firstLine = solutionStreamReader.ReadLine()

         format = firstLine.Substring(firstLine.LastIndexOf(" ")).Trim

         Select Case format
            Case "7.00"
               version = VisualStudioVersion.VSNET2002
            Case "8.00"
               version = VisualStudioVersion.VSNET2003
            Case "9.00"
               version = VisualStudioVersion.VS2005
            Case "10.00"
               version = VisualStudioVersion.VS2008
         End Select

      Finally

         If Not (solutionStreamReader Is Nothing) Then
            solutionStreamReader.Close()
         End If

      End Try

      Return version

   End Function

   Private Function GetVisualStudioProgID(ByVal version As VisualStudioVersion) As String

      Dim progID As String = ""

      Select Case version
         Case VisualStudioVersion.VSNET2002
            progID = "VisualStudio.DTE.7"
         Case VisualStudioVersion.VSNET2003
            progID = "VisualStudio.DTE.7.1"
         Case VisualStudioVersion.VS2005
            progID = "VisualStudio.DTE.8.0"
         Case VisualStudioVersion.VS2008
            progID = "VisualStudio.DTE.9.0"
      End Select

      Return progID

   End Function

End Module

And the message filter is:

'---------------------------------------------------------------------------------------------
' Module MessageFilter.vb
'---------------------------------------------------------------------------------------------

Imports System.Runtime.InteropServices

' See: Fixing 'Application is Busy' and 'Call was Rejected By Callee' Errors
' http://msdn2.microsoft.com/en-us/library/ms228772(VS.80).aspx

<ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
Interface IOleMessageFilter
   <PreserveSig()> _
   Function HandleInComingCall(ByVal dwCallType As Integer, ByVal hTaskCaller As IntPtr, ByVal dwTickCount As Integer, ByVal lpInterfaceInfo As IntPtr) As Integer

   <PreserveSig()> _
   Function RetryRejectedCall(ByVal hTaskCallee As IntPtr, ByVal dwTickCount As Integer, ByVal dwRejectType As Integer) As Integer

   <PreserveSig()> _
   Function MessagePending(ByVal hTaskCallee As IntPtr, ByVal dwTickCount As Integer, ByVal dwPendingType As Integer) As Integer
End Interface

Public Class MessageFilter
   Implements IOleMessageFilter, IDisposable

   <DllImport("Ole32.dll")> _
   Private Shared Function CoRegisterMessageFilter(ByVal newFilter As IOleMessageFilter, ByRef oldFilter As IOleMessageFilter) As Integer
   End Function

   ' Class containing the IOleMessageFilter thread error-handling functions.

   Private Enum SERVERCALL
      SERVERCALL_ISHANDLED = 0
      SERVERCALL_REJECTED = 1
      SERVERCALL_RETRYLATER = 2
   End Enum

   Private Enum PENDINGMSG
      PENDINGMSG_CANCELCALL = 0
      PENDINGMSG_WAITNOPROCESS = 1
      PENDINGMSG_WAITDEFPROCESS = 2
   End Enum

   Private m_oldFilter As IOleMessageFilter
   Private m_disposedValue As Boolean = False

   Public Sub New()

      Dim hr As Integer

      m_oldFilter = Nothing

      hr = CoRegisterMessageFilter(Me, m_oldFilter)
      System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(hr)

   End Sub

   Private Function HandleInComingCall(ByVal dwCallType As Integer, ByVal hTaskCaller As System.IntPtr, ByVal dwTickCount As Integer, ByVal lpInterfaceInfo As System.IntPtr) As Integer Implements IOleMessageFilter.HandleInComingCall

      ' Return the ole default (don't let the call through)
      Return SERVERCALL.SERVERCALL_ISHANDLED

   End Function

   Private Function MessagePending(ByVal hTaskCallee As System.IntPtr, ByVal dwTickCount As Integer, ByVal dwPendingType As Integer) As Integer Implements IOleMessageFilter.MessagePending

      Return PENDINGMSG.PENDINGMSG_WAITDEFPROCESS

   End Function

   Private Function RetryRejectedCall(ByVal hTaskCallee As System.IntPtr, ByVal dwTickCount As Integer, ByVal dwRejectType As Integer) As Integer Implements IOleMessageFilter.RetryRejectedCall

      Dim iResult As Integer

      ' See: IMessageFilter::RetryRejectedCall
      ' http://msdn2.microsoft.com/en-us/library/ms680739.aspx

      ' Return values:
      ' -1: The call should be canceled. COM then returns RPC_E_CALL_REJECTED from the original method call.
      ' Value >= 0 and <100: The call is to be retried immediately.
      ' Value >= 100: COM will wait for this many milliseconds and then retry the call.

      If dwRejectType = SERVERCALL.SERVERCALL_RETRYLATER Then ' Thread call was rejected, so try again.
         iResult = 99 ' Retry immediately
      Else
         ' Too busy; cancel call.
         iResult = -1
      End If

      Return iResult

   End Function

   Protected Overridable Sub Dispose(ByVal disposing As Boolean)

      Dim dummyFilter As IOleMessageFilter
      Dim hr As Integer

      dummyFilter = Nothing

      If Not m_disposedValue Then

         hr = CoRegisterMessageFilter(m_oldFilter, dummyFilter)
         System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(hr)

         m_disposedValue = True

      End If

   End Sub

   Public Sub Dispose() Implements IDisposable.Dispose

      ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
      Dispose(True)
      GC.SuppressFinalize(Me)

   End Sub

End Class

Automation model documentation for Visual Studio 2008 available

Just in case you haven’t notice it yet, there is documentation available about the new automation assemblies specific to Visual Studio 2008, such as EnvDTE90 or VSLangProj90:

http://msdn2.microsoft.com/en-us/library/1xt0ezx9(VS.90).aspx

No much new automation for add-ins or macros in Visual Studio 2008, I think. It seems that the Visual Studio team is much more focused on the Visual Studio SDK and the automation model is quite forgotten which it is a pity, because it is by far the easiest and cleanest way of extending Visual Studio, and for most people approaching the Visual Studio extensibility is the way to go. Packages are much more complex and overkill for most purposes and their history is not the best: unmanaged COM-based interfaces, Interop assemblies, Managed Package Framework, etc. which seem patch after patch…

There is still a lot of room for improvement in the automation model such as:

  • Providing mandatory automation model support in all Visual Studio projects, such as setup projects and others. New VSTS database projects support automation, but that was not the case in VS.NET 2002/2003 database projects.
  • Providing better source code control support.
  • Providing better commandbar support, for example to create dropdown menus with image, such as the Add New Item button on the Standard toolbar.
  • Providing better text editing capabilities, such as markers, colorizers, etc.

In general, for each interface of the SDK there should be an automation model where applicable. I know that you can call the interfaces of the SDK from an add-in, and I have written several articles about that in the past, but it is quite painful to read the SDK docs to figure out how things work or how to call them. For the most part, Reflector for .NET is the best tool to understand things if you are lucky enough to be calling a managed Visual Studio package…

MZ-Tools Articles Series: PRB: PEVerify.exe causes problem with EnvDTE.dll verifying Visual Studio 2005 add-ins

It happens that VSLangProj.dll references EnvDTE.dll version 7.0.3300.0 while Visual Studio 2005 add-ins use EnvDTE.dll version 8.0.0.0, so this causes problems verifying assemblies with peverify.exe. See here how to solve this problem:

PRB: PEVerify.exe causes problem with EnvDTE.dll verifying Visual Studio 2005 add-ins
http://www.mztools.com/Articles/2007/MZ2007032.aspx

MZ-Tools Articles Series: BUG: ProjectItem.Collection returns a wrong collection for folders of SQL Projects of Visual Studio Team Edition for Database Professionals

While adding support for SQL Server database projects of VS Team Edition for Database Professionals in my MZ-Tools add-in, I found a nasty bug in the implementation of the ProjectItem.Collection property when the item is a folder:

BUG: ProjectItem.Collection returns a wrong collection for folders of SQL Projects of Visual Studio Team Edition for Database Professionals
http://www.mztools.com/articles/2007/MZ2007031.aspx

I reported it to Microsoft through Microsoft Connect but they closed it without even acknowledging the bug:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=295434

I used Reflector for .NET to examine the internals of the package and I discovered the code that is wrong, so I opened a new one with a more detailed explanation:

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=296758

Fortunately once you know the internals of the class that is wrong you can use Reflection to make the correct calls and get the expected result, so the next build of MZ-Tools will support database projects, but I hate when I spend time reporting a bug that will not be fixed or even acknowledged. BTW, they introduced a new bug in VS 2008 that did not exist in VS 2005:

EditPoint.FindPattern causes InvalidCastException on SQL editor of VSTS for DB Professional projects
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=295427

I wish they fix that one…

MZ-Tools Articles Series: HOWTO: Use the system font in dialogs and toolwindows of a Visual Studio add-in

While Visual Studio .NET 2002/2003 use their own font setting for dialogs and toolwindows, Visual Studio 2005 and higher honor the system font, which in Windows Vista is no longer the Tahoma 8.25pt of Windows XP or Windows 2000. See how to do it in your own add-in with my latest article: 

HOWTO: Use the system font in dialogs and toolwindows of a Visual Studio add-in
http://www.mztools.com/articles/2007/MZ2007030.aspx

Announcing MZ-Tools 6.0 for VS.NET

Wow, it’s has been more than a month since I wrote my last post! No, no vacations yet 🙁 There was a good reason: I have spent the last weeks doing heavy work around my MZ-Tools add-in:

  • Using the nothing short of wonderful Help and Manual product I have given up HTML Help Workshop and now I am using a professional tool to make the HTML Help (.chm) files of MZ-Tools.
  • As a plus, that tool allows me to generate online help that is now on the web site.
  • I got the help reviewed by Omer Kircher, an American customer of mine, since English is not my native language. Omer has done a wonderful job pinpointing even the smallest errors. He is the author of http://www.convert-units.net/, likely the most powerful unit converter that you’ll ever see. Now I can know how many square meters is a “Spanish (area) fanega” 🙂 (my grandfathers used that unit to measure the fields in their village).
  • I captured more modern (that is, Windows Vista) screenshoots for the help files.
  • I have converted the MZ-Tools web site from pure HTML to ASP.NET 2.0 (XHTML) with Visual Studio 2005 (bye bye FrontPage). I also ensured that the old links still work, specially for the MZ-Tools articles, very popular.
  • I am now using Perforce as my source control tool.
  • I created a tool to perform automatic MZ-Tools builds (bye bye .bat script file). I know that there are visual tools out there to do this but I am much more satisfied with my custom tool.
  • I created a new customer database much more flexible to add new releases and upgrades.
  • And finally, I created a new MZ-Tools 6.0 version that adds no new features (that will be in the future MZ-Tools 7.0) but targets all VS.NET IDEs from 2002, including Visual Studio 2008 (aka “Orcas”)!. So, the new setup (I had to create a new setup, of course) detects your installed VS.NET IDEs and allows you to select which ones to install MZ-Tools for. So, there is no more “MZ-Tools 4.0 for VS.NET 2002/2003” and “MZ-Tools 2005 for VS 2005”, just “MZ-Tools 6.0 for VS.NET”. As a good side effect now MZ-Tools uses the same XML configuration files for all the IDEs. Best of all, this upgrade is free for all MZ-Tools customers.

I have still work to do on the web site during the next weeks (animated pictures to show features, etc.) but hopefully I will have time to write two new articles about add-ins with things that I have learned these weeks. Stay tuned!

Did I mention that I will take soon some days off for a well deserved vacation? 🙂

MZ-Tools Articles Series: HOWTO: Performing some action before or after a build from a Visual Studio macro or add-in

Another question that appear frequently in the forums is how to perform some action before or after a build, how to build programmatically or how to retrieve build configurations. My latest article is about that: 

HOWTO: Performing some action before or after a build from a Visual Studio macro or add-in
http://www.mztools.com/articles/2007/MZ029.htm

Another tool in my developer toolset

Previously I talked here about the tools that I use when developing my MZ-Tools add-in. I would like to mention an awesome tool named Help and Manual 4 that I will use to create help files. I previously used the free HTML Help Workshop, that, if you have used it, you know that is far from perfect, to say the less.

There are many things remarkable about Help and Manual, from a very nice user interface and ease of use to two things that I have found very valuable:

  • You can generate several output formats from a single source: this allows you to generate a HTML Help file along with online help to integrate on a web site; or a downloadable PDF file.
  • You can create builds with conditional inclusion of topics or text. This allows you to use a single source to generate the help file of several versions or editions of the same product. For example, the help file of my free MZ-Tools 3.0 for VBA add-in is a subset of the help file of MZ-Tools 3.0 for VB6 and now I can create them easily. Very cool indeed.

VS SDK, packages, add-ins, macros and more…