Ramblings & Rants

Michael Sanford on Windows Installer, Software Development and life in general!

October 2004 - Posts

Provider Model WebCast Fun!

Wow!  Robert McLaws and Jeff Julian just wrapped up their WebCast: “Provider Model: The Flexible Design Pattern of ASP.NET 2.0”.

 

Not only was it a great presentation, but two really cool new things also were announced:

 

  1. Robert announced that InterScape is releasing a Provider Model framework for .NET 1.1.  This is awesome because it will allow us to start building Providers today.  When .NET 2.0 is released, all we’ll have to do is modify our classes to inherit from the 2.0 Provider Model instead of the Interscape framework.  Way Cool!
  2. Robert has a great vision for the Provider Model and really wants to see people adopt it.  To support this, he has announced the launch of http://www.providermodel.com.  This site is a community resource where you can collaborate with others, browse a gallery of providers, and read provider-related blogs.  Again – Way Cool!

 

Robert & Julian – Great Job!

 

P.S.  Watch the WebCast replay when it is available.  If you can figure out what I did to Robert to heckle him during the demo, I’ll send you a ThinkGeek gift certificate!  First person to post specific details in my comments wins!
MSN Music Customer Service

So – I was searching out some new music on MSN Music today with WMP10.  I did a quick search for Smile Empty Soul, and of course found the album right away.  The problem is that what I got was sort of like some search results.  It had two albums displayed near the bottom with identical images and I (incorrectly) assumed that it was a duplicate search result.  I quickly clicked the first one, skimmed over the album details to ensure it was what I wanted and clicked the “Buy Album” button.

 

The problem is that the one I bought is the edited version.  I realize now that the first one I saw which I thought was a duplicate was the one with the explicit lyrics.

 

While I was really irritated with myself for failing to notice the difference, I was also iriitated with MSN Music for not marking the album more clearly as being an edited version.  I’m not one who usually has great luck with customer service, but I figured it was at least worth filing my complaint.  On clicking around for a minute or two, I found an option to chat with the MSN Music support folks.  Feeling brave, I gave it a shot.

 

I filled out a short form explaining my problem and click the “Chat” button.  In about 4 seconds, I was connected with a support rep named Sha-Sha.  She echoed my beef back to me to be sure she understood.  Once I confirmed that she had it right, she had me wait while she updated the support ticket with my issue and sent to their content team.  To my utter amazement, she then told me she had credited my account the 8.91 the album had cost me so I could go buy the new one.

 

The whole process took less than 5 minutes!  Maybe I should not be so surprised.  Maybe all support and customer service should all be this good, but the fact of the matter is that it’s not.  In my opinion, MSN Music’s support is top notch!

 

Congrats on doing a fantastic job MSN!

 

BTW, Breaking Benjamin just showed up on MSN in the last few days.  If you like Good Charlotte and Three Days Grace, definitely check these guys out!

Posted: Oct 25 2004, 08:22 PM by Michael | with 5 comment(s)
Filed under: ,
Implementing a Windows Installer Validation Engine in VB6
 A recent topic in the msi newsgroups has focused around implementing a Windows Installer debugger or validation engine in VB6.  Conceptually, this isn't all that tough.  The concept focus around a successful implementation of the MsiSetExternalUI method and the companion callback.  Basically, the steps would go something like this:
  1. Figure out exactly which messages you want to get from Windows Installer and calculate the corresponding bitfield
  2. Implement the callback handler method
  3. Call MsiSetInternalUI to ensure that no UI will be displayed by Windows Installer
  4. Call MsiSetExternalUI passing a pointer to the callback method, and the bitfield describing which messages you want to recieve
  5. Invoke an Installer action by running msiexec with the appropriate options, or using the API/Automation methods
  6. Handle messages in the callback
  7. Call MsiSetExternalUI to restore the old handler.

Not so tough, right?  Well -- We have to bear in mind that the API's needed to do this were written for C++ programmers and not for VB Programmers.  While that is indeed a bummer, it does not stopping us from getting the job done.  Instead, it just creates an extra challenge of getting the API declares just right in VB.

To accomplish this, we first need to create a Module in which to implement the MsiCallback.

Option Explicit

Private m_Val As MSIValidator
Private m_OldHandler As Long
'
Const INSTALLLOGMODE_USER = 8
Private Declare Function MsiSetExternalUI Lib "msi.dll" Alias "MsiSetExternalUIA" (ByVal puiHandler As Long, ByVal dwMessageFilter As Long, ByVal pvContext As Long) As Long
Private Declare Function SysAllocStringByteLen Lib "oleaut32" (ByVal lpString As Long, ByVal lLen As Long) As String
Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As Long) As Long

Public Function SetExtUI(val As MSIValidator)
    Set m_Val = val
    m_OldHandler = MsiSetExternalUI(ByVal GetAddress(AddressOf MSICallBack), INSTALLLOGMODE_USER, 99999)
End Function

'Simple function to return a long pointer
Private Function GetAddress(ByVal Addr As Long) As Long
    GetAddress = Addr
End Function

Public Function UnSetExtUI() As Long
    UnSetExtUI = MsiSetExternalUI(m_OldHandler, 0, 0)
End Function

'This is the callback function.
'All wee do is call into the MSIValidator class and let it do what it wants
'NOTE:  This should always return 1
Public Function MSICallBack(ByVal pvContext As Long, ByVal iMessageType As Long, ByVal szMessage As Long) As Integer
    MSICallBack = m_Val.HandleMessage(ByVal iMessageType, ByVal PtrToVBString(szMessage))
End Function

'Convert a string pointer to a VB string
Public Function PtrToVBString(ByVal lPointer As Long) As String
    PtrToVBString = SysAllocStringByteLen(lPointer, lstrlen(lPointer))
End Function

As you can see the Module above is nothing fancy.  It holds a member variable which is a reference to our MSIValidator class.  When you call SetExtUI, you have to pass a reference to a MSIValidator class which is then stored for later use when messages are recieved.

Next, we need to create the MSIValidator class.  This class will be where most of the work is done. 

Option Explicit

Public Event OnInfo(iceNumber As Integer, Info As String)
Public Event OnWarning(iceNumber As Integer, Warning As String)
Public Event OnError(iceNumber As Integer, Error As String)
'
Const INSTALLUILEVEL_NONE = 2
'
Private Declare Function MsiSetInternalUI Lib "msi.dll" (ByVal dwUILevel As Long, hwnd As Long) As Long
Private Declare Function MsiOpenPackage Lib "msi.dll" Alias "MsiOpenPackageA" (ByVal szDatabasePath As String, phDatabase As Long) As Integer
Private Declare Function MsiSequence Lib "msi.dll" Alias "MsiSequenceA" (ByVal hDatabase As Long, ByVal szTable As String, ByVal iSequenceMode As Long) As Integer
Private Declare Function MsiCloseHandle Lib "msi.dll" (ByVal hAny As Long) As Integer

'
Public Sub ValidateMSI(MSIFilePath As String, CUBFilePath As String)
    '
    'Create two Installer instances (might be able to get by with one...)
    Dim oWI As Installer
    Set oWI = CreateObject("WindowsInstaller.Installer")
    Dim oWI2 As Installer
    Set oWI2 = CreateObject("WindowsInstaller.Installer")
    '
    'We need to make a temp copy of the msi to avoid mucking up the original
    Dim oFS As New Scripting.FileSystemObject
    oFS.CopyFile MSIFilePath, App.Path & "\temp.msi", True
    '
    'Open both databases
    Dim oDB As Database
    Set oDB = oWI.OpenDatabase(App.Path & "\temp.msi", 1)
    Dim oDB2 As Database
    Set oDB2 = oWI2.OpenDatabase(CUBFilePath, 1)
    '
    'Merge the cub file into our databse and save the changes
    oDB.Merge oDB2
    oDB.Commit
    '
    'Now we are going to switch to using the API, so let's clean up first
    Set oDB = Nothing
    Set oDB2 = Nothing
    Set oWI = Nothing
    Set oWI2 = Nothing
    '
    'Ok, let's set the external UI
    MSICallBack.SetExtUI Me
    '
   
    Dim lRet As Long
    Dim lHandle As Long
    'Make sure no visible UI appears
    lRet = MsiSetInternalUI(INSTALLUILEVEL_NONE, 0)
    'Open the package and get a handle
    lRet = MsiOpenPackage(App.Path & "\temp.msi", lHandle)
    'Run the _ICESequence Table
    '****All the magic will happen as soon as we call this method
    lRet = MsiSequence(lHandle, "_ICESequence", 0)
   
    'Ok, we are done now.  Let's clean up
    MsiCloseHandle lHandle
    MSICallBack.UnSetExtUI
End Sub

Public Function HandleMessage(ByVal lMsgType As Long, ByVal sMsg As String) As Long
    Debug.Print sMsg
    '
    Dim sData() As String 'Array to hold the tab delimited data
    Dim iType As Integer 'The type of message
    Dim iICE As Integer 'The ICE which is sending the message
    Dim sDesc As String 'The message text
    '
    Dim i As Long
    '
    'We really don't want any errors being raised here
    On Error Resume Next
    'Split the data on the tabs
    sData = Split(sMsg, vbTab)

    'Grab the data from the array
    iType = CInt(sData(1))
    iICE = Right(sData(0), 2)
    sDesc = sData(2)
    '
    'Raise our events
    Select Case iType
        Case 1
            RaiseEvent OnError(iICE, sDesc)
        Case 2
            RaiseEvent OnWarning(iICE, sDesc)
        Case 3
            RaiseEvent OnInfo(iICE, sDesc)
    End Select
    'Always return 1
    HandleMessage = 1
End Function

Hopefully, the code above is pretty self-explantory.  Basically, we make a temp copy of the msi file, merge the .cub file into it, then run the "_ICESequence" table.

Let me close this post with a word of caution.  Anytime you are mucking around with API calls from VB, or implementing callback functions, you are treading in dangerous territory.  Mistakes are common.  Crashes are common.  Please don't blame me if you screw up your system or lose some data as a result of toying with this code.

Congrats to New MVP: Dennis Bareis

Congratulations to Dennis Bareis!

 

Dennis has been named a MVP by Microsoft in recognition of his contributions to the community.

Announcing DeployNow.com!!!

I’m really happy to formally announce the launch of my new deployment related community site!

 

http://www.deploynow.com

 

Basically, the site is a place for me to post articles on deployment technologies, write about tools and technologies, and to host a forum in which I can help others in the community to achieve success with their deployment challenges!

 

There are some great sites out there already, run by some folks that I consider to be my friends (Stefan, Bob, Darwin…), and I am not launching this site in an effort to compete with them, but instead, to focus on a more specific goal of providing very specific advice on how to solve common challenges developers face when deploying sophisticated applications today.  I hope to provide information that user’s of all commercial tools like InstallAnywhere.NET, InstallShield, and Wise, as well as user’s of the Platform SDK will find of value.

 

Help me make this a valuable resource for the community by spreading the word!  Link to it!  Blog about it!  Post your questions!

 

Lastly, I have tons of ideas for articles, but hope that you’ll help me prioritize that list by letting me know what YOU want to read about!  Post a comment and let me know!