Wednesday, 23 December 2009

C++ IS useful - Click Once workaround

cpp[Concrete/ Quite Interesting] Me and C++, we’re old mates – the sort you find on Friends Reunited. I was fortunate to work with Microsoft and Glockenspiel in the late 80’s, working in MFC 1.0 and beyond. Later I would teach C++ for QA Training and others. I really loved the language and managed to build some sizeable solutions in C++, MFC and ATL.
Then came .Net and C# and for me as with many others, we could now start to focus on the problem domain, design and patterns to solution rather than pointers, memory and language syntax.
In C# almost every line of code looks cleaner and addresses the issue at hand, something C++ never seemed to achieve. Look at this simple example taken randomly from the MSDN:
HTTPWebRequest in C++
#using <System.dll>

using namespace System;
using namespace System::Net;
using namespace System::Text;
using namespace System::IO;

// Specify the URL to receive the request.
int main()
{
   array<string^>^args = Environment::GetCommandLineArgs();
   HttpWebRequest^ request = dynamic_cast<httpwebrequest^>(WebRequest::Create( args[ 1 ] ));

   // Set some reasonable limits on resources used by this request
   request->MaximumAutomaticRedirections = 4;
   request->MaximumResponseHeadersLength = 4;

   // Set credentials to use for this request.
   request->Credentials = CredentialCache::DefaultCredentials;
   HttpWebResponse^ response = dynamic_cast<httpwebresponse^>(request->GetResponse());
   Console::WriteLine( "Content length is {0}", response->ContentLength );
   Console::WriteLine( "Content type is {0}", response->ContentType );

   // Get the stream associated with the response.
   Stream^ receiveStream = response->GetResponseStream();

   // Pipes the stream to a higher level stream reader with the required encoding format. 
   StreamReader^ readStream = gcnew StreamReader( receiveStream,Encoding::UTF8 );
   Console::WriteLine( "Response stream received." );
   Console::WriteLine( readStream->ReadToEnd() );
   response->Close();
   readStream->Close();
}
HTTPWebRequest in C#
using System;
using System.Net;
using System.Text;
using System.IO;

public class Test
{
    // Specify the URL to receive the request.
    public static void Main (string[] args)
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create (args[0]);

        // Set some reasonable limits on resources used by this request
        request.MaximumAutomaticRedirections = 4;
        request.MaximumResponseHeadersLength = 4;
        // Set credentials to use for this request.
        request.Credentials = CredentialCache.DefaultCredentials;
        HttpWebResponse response = (HttpWebResponse)request.GetResponse ();

        Console.WriteLine ("Content length is {0}", response.ContentLength);
        Console.WriteLine ("Content type is {0}", response.ContentType);

        // Get the stream associated with the response.
        Stream receiveStream = response.GetResponseStream ();

        // Pipes the stream to a higher level stream reader with the required encoding format. 
        StreamReader readStream = new StreamReader (receiveStream, Encoding.UTF8);

        Console.WriteLine ("Response stream received.");
        Console.WriteLine (readStream.ReadToEnd ());
        response.Close ();
        readStream.Close ();
    }
}
Ok, not a huge difference, but enough of a difference to make C# easier to write, easier to read and so we can focus better on the implementation. I really liked C++ but for me there is no reason to work so hard – C# makes coding more enjoyable and productive.
No reason until…

Click Once Deployment – sounds simple – NOT

I have been developing an Office Outlook Plug-in for www.far2muchmail.com. Far2MuchMail is a solution to help people better manage their emails in Outlook and it’s free. What I wanted was to make it easy for people to download and install the plug-in from the Internet so naturally I thought ‘Click Once Deployment – just the puppy’. WRONG.
I want to encourage people to use my plug-in. Did I say it was free. Being free, the last thing I wanted was loads of messages during installation telling the user that the software was from an unknown publisher, couldn’t be trusted, would probably cause a fire in the house, sell your wife and kids to criminals, cause global warming, and so on. I mean, you would never install software, free or not, with that level of scare mongering.
I understood why the warnings were there and knew that if I bought a certificate, my problems would be over. But somehow I felt like I was being conned. A certificate felt like one of those Microsoft Taxes – like MSDOS. There had to be another way.
I knew I could create a certificate using MAKECERT. but to stop Click Once raising alarming messages, this certificate needed to be installed prior to installing the app.
I came across an article that talked about programmatically adding your certificate into the Trusted Installer list on a local machine. Great, all I needed to do was implement this and have it called at the start of installation as a pre-action.
But Click Once is locked down so I had to look at an alternative installer. I decided upon a standard Deployment Project in Visual Studio. Finally I was able to install my certificate prior to installing the plug-in. But there was a problem…

VS2008 and the Deployment Project Bootstrap.

Unfortunately, in VS2008 Microsoft changed the bootstrap program (setup.exe) as part of their deployment package. The change was subtle but important. Previous versions of the program would remain in memory until the installation ended. This allowed you to use a program like WinZip or IExpress to package the setup and MSI into a single executable. Setup.exe would remain in memory until the setup had completed so you could reliably let WinZip or IExpress wait until the setup had finished then automatically clean up any temporary files. Everything worked. In VS2008, the bootstrap setup.exe was designed to leave memory as soon as it had kicked off the MSI package. The result was the single package created by WinZip or IExpress would think the setup has finished and would start to pull temporary files, causing the setup to fail. Very frustrating.
I was nearly there, all I now needed to do was keep the package from terminating while the MSI was installing. C++ to the rescue.

WaitForProcess - C++ application

image I decided to use IExpress to package my setup into a single executable deployable from my website. IExpress allowed me to name the components of the installation including the setup.exe bootstrap and MSI package. It also allows you to define post installation steps. It is here I named a program called WaitForProcess, written in C++.
WaitForProcess is very simple, it continues to run if a named process is still in memory. So calling WaitForProcess and naming msiexec.exe in the installation package is all I need to do to ensure the IExpress package remain in memory until the MSI package has completed installation.
The trick was to write WaitForProcess without any dependencies on .NET which may not be installed until after the setup has completed. Indeed, I needed to rely on as few components as possible. C++ without ATL/MFC only requires standard libraries like USER32 and KERNEL.

So here is the code to WaitForProcess:

// WaitForProcess.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "WaitForProcess.h"
#include <psapi.h>
#include <shlwapi.h>

bool IsRunning(LPCTSTR pProcessName) //findProcess should be 1,2 or 3 and tells us which of the predefined processes to look for (see searchString array below)
{
    DWORD Processes[1024]; //Buffer array where enumerated processes will go
    TCHAR ProcessPath[MAX_PATH] = _T(""); //Path of the examined process
    HANDLE hProcess = 0; //Handle of the examined process
    DWORD bytesNeeded; //bytes written into buffer array by EnumProcesses
    int nProcesses; //Number of actually enumerated processes calculated from bytesNeeded
    int i=0;
    bool found=false; //Stop examining further processes if found is true
 
    if(EnumProcesses(Processes, sizeof(Processes), &bytesNeeded))
    {
        nProcesses = bytesNeeded / sizeof(DWORD);
        while((i<nProcesses) && (!found))
        {
            if(Processes[i]!=0)
            {   
                hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, Processes[i]);
    
                if(hProcess)
                {
                    GetProcessImageFileName(hProcess, ProcessPath, sizeof(ProcessPath)/sizeof(TCHAR));
                    PathStripPath(ProcessPath);
                    found=!_tcsicmp(ProcessPath, pProcessName);
                }
            }
            i++;
        }
    }
    return found;
}

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);

    //TCHAR sMessage[1024] = _T("");
    //wsprintf(sMessage, _T("Process: %s"), lpCmdLine);
    //::MessageBox(0, sMessage, _T("Wait For Process"), MB_OK);
    ::Sleep(5000);

    if (IsRunning(lpCmdLine))
    {
        //::MessageBox(0, _T("Found"), _T("Wait For Process"), MB_OK);

        while(IsRunning(lpCmdLine))
            ::Sleep(500);
    }
    //::MessageBox(0, _T("Done"), _T("Wait For Process"), MB_OK);
    return 0;
}

Conclusion

Well, I still find C# a more productive language but it goes to show, when the going gets tough, the tough turn to C++.

1 comment: