John McFadyen's profileJohn McFadyens Windows I...PhotosBlogGuestbookMore Tools Help

Blog


    April 28

    Using Custom XML Based transforms

    Ok so a lot of people wonder why I bother with doing this. Well the simple facts are this is an easy way to make a package more dynamic without needing repackaging to modify the package. A rather beneficial side affect is that allows updates to be made to properties without having to uninstall a package and reinstall it. (this is a really nice feature if you don't want to uninstall but perhaps have a server name update to perform on a registry key or something similar.)

    The reason I am posting this is that Kim has linked to it on my previous blog so I guess I may as well explain what it is all about.

    Lets take a simple scenario into account. Perhaps you have an application which writes the path to a database into the registry can't think of one ? Probably most of you use one each day. Wise is one application I can think of off the top of my head that does this.

    Anyway back to the point lets say you have written a database path to the registry in a location such as.

    HKLM\Software\MyApplication\DatabasePath = MyServer\SQL01

    A smart packager would do something like this instead.

    HKLM\Software\MyApplication\DatabasePath = [SQLSERVERPATH]

    In the MSI Property table add the property

    SQLSERVERPATH = MyServer\SQL01

    This of course has the effect that you can now with a TRANSFORM and or COMMANDLINE change the path to that SQL Server. Using a Transform is often the way ahead. But this can lead to other issues down the track. Lets assume in 6 months time the SQL Server gets upgraded and with that upgrade it gets a new Server\Instance path. Using a TRANSFORM you would have to perform the following steps.

    1) uninstall the app

    2) create a new transform with new property

    3) reinstall the app.

    Using my custom xml solution you could simply do this.

    Create the Registry in a dynamic sense as shown above.

    HKLM\Software\MyApplication\DatabasePath = [SQLSERVERPATH]

    Then create an XML File such as.

    <Properties>

      <SQLSERVERPATH>MyServer\SQL01</SQLSERVERPATH>

    </Properties>

    Using this code each item under the Properties node gets enumerated and converted to a property name and value. This is really old code and could be written considerably easier (I wouldn't write it like this any longer this was well before I understood XML Dom to the degree I do now)

    Now why would you do this, well the benefits are defined by who, how and why your doing your packaging. In most instances this leaves my clients in a situation where they do not need a packager to update the package. A simple edit of the text node in the XML is all that's needed to repackage this app. The unplanned side effect of this was that instead of needing to uninstall your app and reinstall it a simple forced heal would update the path.

    As I mentioned earlier this is really old code I'm just posting this to explain the code Kim linked to. There's plenty of nicer ways to code the same result.

    '******************************************************************************
    ' Filename: XMLCONFIG.VBS
    '
    ' Description: MSI XML configuration script.
    '
    ' Function: Reads XML Configuration and modifies MSI's to suit
    '
    ' Modification History:
    ' Author Date Version Changes
    ' ----------------- ----------- ------- ---------------------------------------
    ' Installpac Pty Ltd
    ' John McFadyen 20/04/2006 1.00 Created Script
    '
    '
    '******************************************************************************
    strSourcePath = Session.Property("SourceDir") 'reads msi filename and path
    strSourcePath = Mid(strSourcePath, 1, InstrRev(strSourcePath, "\")) 'strips filename leaving msipath
    Set objWshShell = CreateObject("WScript.Shell")
    set objNetwork = CreateObject("Wscript.Network") 'Create network object
    Set objXMLDocument = CreateObject("Msxml2.DOMDocument") 'Creates XML document
    Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
    Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_ComputerSystem", "WQL")
    objXMLDocument.Async = False
    objXMLDocument.resolveExternals = False
    objXMLDocument.Load(strSourcePath & "scripts\Environment.xml") 'Loads XML file
    strUserDomain = objNetwork.UserDomain 'Determines users domain
    For Each objItem In colItems
    sstrFQDN = objItem.Domain
    Next
    strItem = GetXMLChildNodes(objXMLDocument, "PROPERTIES")
    '********************************************************************************
    ' Function: GetXMLAttribute (Reads attributes from XML)
    '
    ' John McFadyen
    Function GetXMLAttribute (byref objXMLDocument, strNode, strAttributeName)
    'On Error Resume Next
    Dim objDocumentElements, objNode, strAttributeResult
    Set objDocumentElements = objXMLDocument.documentElement
    Set objNode = objDocumentElements.selectSingleNode(strNode)
    GetXMLAttribute = objNode.GetAttribute(strAttributeName)
    Session.Property(strAttributeName) = GetXMLAttribute
    ' msgbox strAttributeName & " " & session.Property(strAttributeName)
    End Function
    '********************************************************************************
    ' Function: GetXMLChildNodes (Enumerates child nodes from XML parent)
    '
    ' John McFadyen
    Function GetXMLChildNodes (byref objXMLDocument, strNode)
    'On Error Resume Next
    Dim objDocumentElements, objNode, strAttributeResult
    Set objDocumentElements = objXMLDocument.documentElement
    Set objParentNode = objDocumentElements.selectSingleNode(strNode)
    For Each objNode in objParentNode.childNodes
    ' msgbox "NodeName: " & objNode.NodeName & " " & "NodeText: " & objNode.Text
    Session.Property(objNode.NodeName) = objNode.Text
    ' msgbox objNode.NodeName & " value " & session.Property(objNode.Nodename)
    Next
    End Function

    April 25

    Basic Windows Installer Automation Object (session object)

    As always I will start a little slow with this and progressively get harder as the posts go on. And if anyone wants to know about anything specific and I have the knowledge to help drops some comments on what you want to know about and I will endeavour to assist.

    So I guess the first items of interest are the main objects to deal with. Looking at this from a packagers perspective the first part of the automation object you should get your head across is the session object. A session is automatically created when executing an MSI for installation / repair or uninstallation.

    The session object is very powerful and is one of the key concepts I use in order to create dynamic packages. The simplest yet I believe one of the most important thing to learn as a re/packager is how to set properties using the session object.

    Although a fairly simple process there are a few caveats around its use.

    If you haven't already read my posts on Windows Installer sequencing you should read that first as that directly correlates to this post. I usually access the session object using vbscript as a custom action. There are other ways but I find this to be quick and easy. Now taking into account the deferred phase is actually running a script that was generated during the Acquisition / Immediate phase it stands to reason that the session will not be available during the deferred phase. Where the immediate phase is directly communicating with the MSI database we have the access to read / write to that same database.

    Based on this information we can access the session object during the immediate phases which allows us to access and manipulate table data. (you can even write data back into the msi tables during installation) The most common function we are likely to perform is simply updating / retrieving a property value.

    So to retrieve a property value its quite simple we just use.

    strMyValue = session.property("MYPROPERTY")

    Using the above line the vbscript variable strMyValue will contain the property MYPROPERTY's value.

    We can also set the properties value by the following line of code.

    session.property("MYPROPERTY") = "myNewValue"

    Now its also important to be aware that you must use a vbscript custom action to access the session object. If you use an EXE custom action that calls a vbscript you will not immediately have access to the session. For example if we called

    cmd /c cscript.exe [SourceDir]MyVbScript.vbs ' Would not allow access to the session object.

    There is so many reasons how this can be useful. Here is a sample on how I use this functionality to handle applications like AutoCAD which use a different serial number for every machine.

    In this scenario

    MACHINEA uses a serial number of ABCD-ABCD-ABCD-ABCD where

    MACHINEB uses a serial number of DCBA-DCBA-DCBA-DCBA

    So in order to avoid creating a new transform for every machine that uses AutoCAD I whip up a simple bit of XML (I'm a real fan of XML) and a script that interprets the content of the XML and uses the session object to change the MSI Public Property SERIALNUM to whatever it should be for the appropriate machine.

    So lets assume our XML looks something like this.

    <ComputerList>

       <Computer Name="MACHINEA" SerialNum="ABCD-ABCD-ABCD-ABCD" />

       <Computer Name="MACHINEB" SerialNum="DCBA-DCBA-DCBA-DCBA" />

    </ComputerList>

    So now we just whip up a little vbscript to get the name of the current machine during installation. Then we do an XML lookup for that machine and retrieve the appropriate Serial number. Lets assume in this example we are on MACHINEA. Our vbscript looks up that machine name and retrieves the serial ABCD-ABCD-ABCD-ABCD.

    We then use the following session object syntax to set that property.

    session.property("SERIALNUM") = "ABCD-ABCD-ABCD-ABCD"

    Based on this principal we now have a single MSI with a simple custom action that allows us to switch the SERIALNUM property for every machine that installs AutoCAD. So what you may say, well now 3 months down the track we get another 5 machines that want to install AutoCAD. Instead of creating new transforms for each machine we simply update the XML by adding new machines and serial numbers. We don't even have to open an MSI editor to handle this. It can all be done with the simplicity of notepad.

    So as you see a pretty simple principal with very powerful capabilities.

    I hope this is useful to some of you! Don't forget requests for anything a little more difficult will keep me blogging. !!!

    April 21

    Wise & Software Mangler Distribution

    Software Manager

    The distribute function ties in heavily with the use of conflict management. This is a very complex phase to understand and has some definite advantages and disadvantages. The advantages are only recognised if and when you are running a full conflict management solution. The main reason for the distribution phase is to allow Conflict Manager to copy files to and from other packages already in the repository.

    The output of this step is that all files from the MSI / MST combinations are copied from the current package to the Wise Sharepoint under the following location.

    [WiseSharePoint]000 ‘ From now I will refer to this as [000]

    In order to demonstrate some important processes lets assume we start with the following items in our sample package

    Application Name = APPLICATIONA

    File Name            = FILE1.DLL (Version 1.0)

    WiseSourcePath   = .\Program files\TestApplication\FILE1.DLL

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONA

    clip_image002

    When package distribution is run files within the package are enumerated each file is then compared against the (wamdb.idx). If the file does not previously exist it will be copied into [000]001\FILE1.DLL. At this point our applications sample information will now look like this.

    Application Name = APPLICATIONA

    File Name            = FILE1.DLL (Version 1.0)

    WiseSourcePath   = ..\..\000\001\FILE1.DLL

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONA

    clip_image004

    Note: The change in the WiseSourcePath table.

    The change in the WiseSourcePath table has a potential negative impact but it is done for a very specific reason. In my opinion the WiseSourcePath table should not be modified and instead Wise should of created a new table such as WiseSoftwareSourcePath or something similar. (however that is not the case so we have to cater for the negative impact of this step.)

    If we then run the same distribution on another similar package “APPLICATIONB” and assume this application also contains the same FILE1.DLL (version 1.0) the distribution function will check the Repository Database and find that the file already exists.

    Application Name = APPLICATIONB

    File Name            = FILE1.DLL (Version 1.0) and FILE1.DLL (Version 2.0)

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONB

    WiseSourcePath   = .\Program files\TestApplication\FILE1.DLL and .\Program Files\TestApplication\Test\FILE1.DLL

    If the file does exist the files version would be checked, where the files version was the same no files would be copied to the repository, however the WiseSourcePath entry for “APPLICATIONB” would be updated to show the same after distribution path as APPLICATIONA in the above example.  

    Application Name = APPLICATIONB

    File Name            = FILE1.DLL (Version 1.0)

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONB

    WiseSourcePath   = ..\..\000\001\FILE1.DLL

    If the file does exist and the file version is different from files already stored in the repository. Then a new folder will be created and its number incremented from the folder that last contained a copy of the file with the same name.

    Application Name = APPLICATIONB

    File Name            = FILE1.DLL (Version 1.0)

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONB

    WiseSourcePath   = ..\..\000\002\FILE1.DLL

    Note: The WiseSourcePath has now incremented from ..\..\000\001\FILE1.DLL to ..\..\000\002\FILE1.DLL.

    The intention here was to ensure that all APPLICATION FILES for each application in the Software Manager will be available and relative to all packages currently imported in the software Manager from the WiseSharePoint. However each file will only ever be referenced once regardless of how many applications contain the file name. However if more than one version of the same filename exists you will find multiple folders housing each of the individual copies of the respective files. Therefore the folders with the lower numbers are more likely to contain more files from the various applications which have already been through the Wise Distribution phase.

    At this point I expect most of you are reading this quite confused or even wondering why would Wise bother with such a step. The answer to that question is as complex as the question itself. If you consider what conflict manager is trying to do, it is basically trying to ensure all applications are sociable with all other applications already imported into the Software Manager. In order to do this there is likely going to be a time where you would like to switch files from one package to another. To cut a very long and complex story short this is why the distribution phase is run. It basically allows functionality within Conflict Manager to be enabled, this functionality allows the user to copy files from one package to another package.

    Folders

    Prior to distribution the WiseSourcePath table has entries which are relative to the current working file. For example APPLICATIONA.WSI. All files relating to APPLICATIONA are present in the folder for APPLICATIONA. (no rocket science here) However after distribution each file entry in the WiseSourcePath table will look similar to this.

    ..\..\000\001\FILE1.DLL.

    Note: ..\ means that the package will step up a folder from its current location.

    So if we are working on APPLICATIONA which is in the WORKING FOLDER APPLICATIONA in this example. If we step up 2 directories (I.e. ..\..\) we will be at [SharePoint] from there the remaining path of 000\001 is easily found from the SharePoint location.

    If we are working on APPLICATIONB which is in the WORKING FOLDER APPLICATIONB in the same example. If we step up 2 directories (i.e. ..\..\) we will also be at [SharePoint] from there the remaining path of 000\001 is easily found from the SharePoint location.

    Basically what I am trying to say is, if we are working in a package directly under the projects folder no matter what the package location is 2 folders up is [SharePoint] in effect this means that each package that has had distribution run on is is now relative to all other packages within Software Manager. This is a very important part of understanding conflict management in its entirety.

     
    Conflict Management

    Using the same example applications mentioned above, if we now look at this from the perspective of the Wise conflict management tool we will see a different picture of why this is done.

    For example:

    If we assume now we have imported both applications into conflict manager. Remember that both packages had FILE1.DLL present as such we would see that each of these applications would conflict against each other as they both had the same file name existing in various locations.

    The following picture is taken from an actual conflicting application. The top window is the currently selected package. The bottom window is all other packages in the Software Manager that conflict withe package in the top Window.

    clip_image008

    The circled item is the files in the currently selected application which is conflicting with other applications.

    clip_image010

    The items circled below are the other applications in conflict manager which are actually conflicting with the item which is selected in the screen at the top section.

    clip_image012

    The two buttons in the middle are used to swap files between other packages.

    Note: This item is only available if the distribution option is used, the view depicted here is from a package which has not run the distribution phase

    clip_image014

    Selecting a package from the view on the bottom will allow the grayed out middle items to be selected. Then selecting either button will allow you to move dll’s from one package to another package. Note this heavily integrates with the items listed in the WiseSourcePath tables earlier in this post.

    clip_image016

    Pushing this "Copy Up" or "Copy Down" presents the above item. The two checkboxes perform the following.

    • Copies the complete file from other package to the current package
    • Copies the files component GUID only from other package to the current

    So for the purpose of explaining Conflict Management processes lets assume that the TOP window is APPLICATIONA and the BOTTOM window is APPLICATIONB. If we select the "Copy UP" option we are effectively saying lets copy the file from the application selected in the BOTTOM window, in this case APPLICATIONB into the application selected in the TOP window. In this case APPLICATIONA.

    Prior to distribution you will recall we have this information.

    Application Name = APPLICATIONA

    File Name            = FILE1.DLL (Version 1.0)

    WiseSourcePath   = .\Program files\TestApplication\FILE1.DLL

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONA

    and

    Application Name = APPLICATIONB

    File Name            = FILE1.DLL (Version 1.0) and FILE1.DLL (Version 2.0)

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONB

    WiseSourcePath   = .\Program files\TestApplication\FILE1.DLL and .\Program Files\TestApplication\Test\FILE1.DLL

    After distribution we had this information.

    Application Name = APPLICATIONA

    File Name            = FILE1.DLL (Version 1.0)

    WiseSourcePath   = ..\..\000\001\FILE1.DLL

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONA

    and

    Application Name = APPLICATIONB

    File Name            = FILE1.DLL (Version 1.0)

    Working Folder    = [WiseSharePoint]Projects\APPLICATIONB

    WiseSourcePath   = ..\..\000\001\FILE1.DLL

    Based on this if we are Managing Conflict management for APPLICATIONA you will note our Working Directory is

    [WiseSharePoint]Projects\APPLICATIONA

    We are however interested in copying file from APPLICATIONB into APPLICATIONA. So in order to achieve the copy APPLICATIONA reads the WiseSourcePath table of APPLICATIONB. Prior to distribution the WiseSourcePath table for APPLICATIONB reads as:

    WiseSourcePath   = .\Program files\TestApplication\FILE1.DLL and .\Program Files\TestApplication\Test\FILE1.DLL

    The current working directory is [WiseSharePoint]Projects\APPLICATIONA as such Wise will look into

    [WiseSharePoint]Projects\APPLICATIONA\Program Files\APPLICATIONB which does not exist in APPLICATIONA's working folder.

    After the distribution phase the WiseSourcePath table reads as WiseSourcePath   = ..\..\000\001\FILE1.DLL.

    So with a working directory of [WiseSharePoint]Projects\APPLICATIONA\..\..\000\001\FILE1.DLL we will find the path to the source files for APPLICATIONB. Regardless of the application selected if the distribution phase is run all applications can find files from all other applications.

    Well that about wraps the distribution phase up, its very complex to put into words I would be interested in any feedback about this document. Its hard to understand implement and even harder to write about it.

    This is some pretty smart work on behalf of Wise. I hope this can help somebody struggling with the principals of Software Manager. 

    April 16

    Automating Software mangler imports

    Ok so someone on the Wise / App deploy forums was after this stuff. Here's how I automated my software mangler imports using Wise.

    The code is pretty self explanatory so I won't go into detail unless someone asks for more info.

    ' ****************************************************************************
    ' Name              :   GenerateIniMSI.vbs
    ' Description       :   Create Import scripts for Wise Software Manager
    ' Usage             :
    ' Modifications     :
    '
    '
    ' Date                  Version     User                Description
    ' ****************************************************************************
    ' 25/01/2005            V1.0        J. McFadyen         Created initial script
    '
    ' ****************************************************************************

    option Explicit

    const ForReading = 1
    const ForWriting = 2
    const ForAppending = 8

    Dim sobjArgs, sobjShell, sobjFSO
    Dim sstrMSI, sstrMST, sstrProject, sstrProjectDir, sstrGroupID, sstrServerName,
    sstrDSN

    set sobjArgs  = Wscript.Arguments
    set sobjShell = CreateObject("WScript.Shell")
    set sobjFSO   = CreateObject("Scripting.FileSystemObject")

    sstrMSI = sobjArgs(0)
    sstrProject = sobjArgs(1)
    sstrProjectDir = sobjArgs(2)
    sstrGroupID = sobjArgs(3)
    sstrDSN = sobjArgs(4)

    sstrServerName = "WISESERVER"

    set sobjFSO= sobjFSO.CreateTextFile(sstrProjectDir & "\" & sstrProject & "MSI.in
    i", ForWriting ,1)

    sobjFSO.Writeline "[Database Info]"
    sobjFSO.Writeline "DSN=" & sstrDSN
    sobjFSO.Writeline "Application=" & sstrProject
    sobjFSO.Writeline "Package=" & sstrProject
    sobjFSO.Writeline "Operation='Import'"
    sobjFSO.Writeline "Script=" & sstrProjectDir & "\" & sstrMSI & ".msi"
    sobjFSO.Writeline "BaseMSI=" & sstrProjectDir & "\" & sstrMSI & ".msi"
    sobjFSO.Writeline "Transact='Yes'"
    sobjFSO.Writeline "Delete='No'"
    sobjFSO.Writeline "Silent='No'"
    sobjFSO.Writeline "Overwrite='Yes'"
    sobjFSO.Writeline "NoTouch='Yes'"
    sobjFSO.Writeline "RCSAdd='Yes'"
    sobjFSO.Writeline "RCSCheckIn='Yes'"
    sobjFSO.Writeline "RCSComment='Initial Check in for " & sstrProject & "'"
    sobjFSO.Writeline "GroupID0=" & sstrGroupID

    Output result is something along the lines of this.

    [Database Info]
    DSN=" & sstrDSN
    Application=" & sstrProject
    Package=" & sstrProject
    Operation='Import'"
    Script="<path to msi>"
    BaseMSI=" & sstrProjectDir & "\" & sstrMSI & ".msi"
    Transact='Yes'"
    Delete='No'"
    Silent='No'"
    Overwrite='Yes'"
    NoTouch='Yes'"
    RCSAdd='Yes'"
    RCSCheckIn='Yes'"
    RCSComment='Initial Check in for " & sstrProject & "'"
    GroupID0=" & sstrGroupID

    Wise Macro's for the average Joe...

    Ok this is a follow on from the last blog. So I'm going stick with the Hungarian notation due to the use of Wise Editor and Wise .wbs file formats. For the real dev's out there .. bite me this is only a scripting language so its ok.

    First up I should go into the difference between functions and subs within Wise macro code. These act in a similar way to standard vbs code. Where a sub is a procedural piece of code that does not return a result. A function performs practically the same function however it allows a return value to be passed out of the function to the calling code.

    Enumerate Tables into HTA

    So I am going to throw out a few simple techniques I use on a regular basis with my macro's. So this first item shows how to generate an HTA to provide visual input to a user in the form of a drop down menu populated with Database table data. For all of the following examples I will remove all the declarations for readability.

    Function EnumFeatures
            'Initialising Internet Explorer HTA page
            Set objExplorer = CreateObject("InternetExplorer.Application")
            objExplorer.Navigate "about:Hi there dudes"
            objExplorer.ToolBar = 0
            objExplorer.StatusBar = 0
            objExplorer.Width=400
            objExplorer.Height = 200
            objExplorer.Left = 0
            objExplorer.Top = 0
            Do While (objExplorer.Busy)
            Loop
            objExplorer.Visible = 1
            objExplorer.Document.writeln "Select a feature to add your stuff to"

           'Connecting to Feature Table

            Set tblFeature = WTables("Feature")

            Redim preserve  arrFeature(0)       
            arrFeature(0) = "Default"
            i = 1

            'Loop to add each row of the feature table to an array.
            For Each rowFeature In tblFeature.WRows
                Redim preserve arrFeature(i)
                arrFeature(i) = RowFeature("Feature")
                i= i + 1
            Next

            'Adding each feature name to HTA dropdown
            objExplorer.Document.WriteLn ("<select id=""DD_Feature"" size=""1"" style=""background-color:WhiteSmoke"">")
            i = 0
            For i = 0 To UBound(arrFeature)
                objExplorer.Document.WriteLn "<option value=" & arrFeature(i) & ">" & arrFeature(i) & "</option>"
            Next
            objExplorer.Document.WriteLn "</select>"

           'Waits for user to select a feature

            Do until objExplorer.Document.All.DD_Feature.Value <> "Default"
             i = 0
             i = i + 1
            Loop

           'Returns selected drop down entry to calling code
            EnumFeatures = objExplorer.Document.All.DD_Feature.Value
        End Function

    Using this technique you can add custom entries into 'undefined' feature names. For example if you are editing a vendor MSI you may find naming conventions a little unusual which means its difficult to anticipate what feature names you can add your custom items to. Using this you can present the packager with options that a specific to the current package allowing them a GUI view to add with selection of features from the current package.

    Note: You do not have to use the Feature table this is just the example I chose to use for this demonstration.

    Backup Macro

    One of my favourite little macros is this little gem. This when added to the Save_Event allows backup of WSI and MST files after each compile. This has saved me and others hours of time when they accidentally delete something they should of kept.

    Sub BackupProject
        If sstrExtension = "WSI" Or sstrExtension = "MST" Then
            If sobjFSO.FileExists(sstrFilePath & sstrFilename) Then

                'Creating folder within project folder (relative to wsi / msi / mst)
                CreateSourceFolder(sstrExtension & "Backup")
                strDateTimeStamp = Now
                strNewFileName = sstrFilePath & sstrExtension & "Backup\" & sstrFilename1 & "_" & Year(strDateTimeStamp) & "-            " & Month(strDateTimeStamp) & "-" & _
                    Day(strDateTimeStamp) & "-" & Hour(strDateTimeStamp) & "-" & Minute(strDateTimeStamp) & "-" &                   Second(strDateTimeStamp) & "." & sstrExtension
                sobjFSO.CopyFile sstrFilePath & sstrFilename, strNewFileName
            Else
            End If
        Elseif sstrExtension = "MSI" And sobjFSO.FileExists(sstrFilePath & sstrFilename1 & ".WSI") Then
            MsgBox "Warning: You are not currently working the WSI even though it exists"
        End If
    End Sub

    'Sub to create folder in app path

    Sub CreateSourceFolder(strFolder)
       
        If Not sobjFSO.Folderexists(sstrFilepath & strFolder) Then
            sobjFSO.createfolder(sstrFilepath & strFolder)
        End If   
    End Sub

    Using a macro with user input

    Ok so this is again very powerful and similar types of routines can greatly assist the speed and development of packaging. I'm sure many of you have falling victim to spending hours trying to get a silly custom action to work only to realise a week later you need the same action and can't remember what you did to make it work. Or someone else on the team tries to reinvent what you have already done.

    So for the repetitive task that's slightly different on each package something like this works a treat. Gather the information from the packager then automate custom actions based on input. Simple and effective.

    Sub CreateSubKeyRegPerms
        strSubKeyRegMsg = "Please Enter the Registry SubKeys " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "that needs to have permissions set " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "e.g. \HKLM\Software\<Client>\*.* " & vbCrlf
        strSubKeyRegName = inputbox (strSubKeyRegMsg, "SubKeyRegName", "\HKLM\Software\<Client>\*.*")
        strSubKeyRegMsg = "Please enter the user access to be granted " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & " " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "Users " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "Power Users " & vbCrlf
        strUserAccess = inputbox (strSubKeyRegMsg, "User group that needs access", "Users")
        strSubKeyRegMsg = "Please Enter the user access to be granted " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & " " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "F = Full Control " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "R = Read " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "A = ReAd Control " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "Q = Query Value " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "S = Set Value " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "C = Create SubKey " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "E = Enumerate Subkeys " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "Y = NotifY " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "L = Create Link " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "D = Delete " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "W = Write DAC " & vbCrlf
        strSubKeyRegMsg = strSubKeyRegMsg & "O = Write Owner " & vbCrlf
        strUserPermission = inputbox (strSubKeyRegMsg, "Access Level", "SC")
        strCmd = "cmd /c ""[SourceDir]scripts\subinacl.exe"" /subkeyreg " & strSubKeyRegName & " /grant=" & strUserAccess & "=" & strUserPermission
        Call CreateCustomAction ("CUSTSetSubKeyRegPerms", "1058", "SystemFolder", strCmd)    ' refer to previous blog this
        Call CreateInstallExecuteSequence ("CUSTSetSubKeyRegPerms", "NOT Installed", "6006")
    End Sub

    April 13

    Wise Macro's for beginners

    Ok so one of the avid Wise followers call's me "old" the other day as he was requesting some tips. For the record when someone is old well its best not to tell them that particularly if you want something. So anyway without naming any names "Owen G" here's what you have been waiting for.

    So first I am going to go over a very heavily debated topic of Hungarian notation. I have come to my own opinion it has its place in languages that are not strongly typed by design. So where we are talking vbscript / wise macro and such like languages I think hungarian notation is worthy of use. However if we are talking languages which are strongly typed such as VB / C# and many other OO type languages the use of hungarian notation become less useful and perhaps even painful in some cases.

    So the debate for good or bad I will leave up to you guys, for those of you whom don't know what hungarian notation is. Well its the use of prefixes on variable names to identify the content of the variable prior to knowing what the variable is. 

    For example an object named WindowsInstaller in hungarian notation might be objWindowsInstaller. So for the purpose of this document I am going to code with the use of hungarian notation simply because the Wise Macro code is not strongly typed and nor is the editor particularly user friendly in showing object hierarchy.

    So here's a brief table of what I am going to use.

    Prefix Description Content
    str string a text string
    obj object a class with its own methods and properties
    tbl table a windows installer table
    row row a windows installer table row
    col collection a collection of objects
    int integer a number
    bln boolean a true false value
    sub sub (routine) a block of code that does not return a value
    func function (routine) a block of code that does return a value
    arr array a list of items identified by an index

    Now I am assuming you all have some scripting knowledge so I am not going to cover basic scripting technique but rather how to use basic scripting to obtain access with Windows Installer tables through Wise Package Studio function known as the Macro Editor. (this is actually one of the bigger reasons I prefer Wise over Installshield)

    The Wise Macro Editor allows code to be run across the tables at packaging time. This means that repetitive tasks can be automated for each application that is packaged. For those of you trying to speed up your packaging processes this is surely one of the fastest methods to do so.

    Connecting to a Table

    So connecting to a Windows Installer table. This is a pretty and a single line of code and get you there.

    set tblInstallExecuteSequence = WTables("InstallExecuteSequence")     ' Note put your table name in the quotes.

    This means that tblInstallExecuteSequence is now an object of all of the rows within this table.

    Iteration of table content

    If you wanted to iterate through each of the rows within that table you can do this easily with

    For Each rowInstallExecuteSequence In tblInstallExecuteSequence .WRows
      msgbox rowInstallExecuteSequence("Action")     'where action is the name of the column you wish to display

    next

    Selecting a specific Row of a table based on a known columns value

    If you want to select a specific row this code will do the trick

    set rowSpecificItem = tblInstallExecuteSequence .WRows("Action")

    Selecting a specific column of a specifically selected Row

    strAction = rowSpecificItem("Action")

    strCondtion = rowSpecificItem("Condition")

    strSequence = rowSpecificItem("Sequence")

    Writing values to a cell

    rowSpecificItem("Action") = "my new value"

    Writing rows to a table

    Set NewRow = WTables.Item("Directory").NewWRow    
    NewRow("Directory") = "Flag"
    NewRow("Directory_Parent") = "WindowsFolder"
    NewRow("DefaultDir") = "Flag"

    Dynamically writing new rows to a table.

    Ok so the above example does the job but takes a considerable amount of code if you were to write hundreds of rows of data to a table. In order to increase efficiency and write cleaner code the use of subs and functions is heavily advised.

    Sub CreateDirectoryTableRow(strDirectory, strDirectory_Parent, strDefaultdir)

      Set NewRow = WTables.Item("Directory").NewWRow    
      NewRow("Directory") = strDirectory
      NewRow("Directory_Parent") = strDirectory_Parent
      NewRow("DefaultDir") = strDefaultDir

    end sub

    This method is an empty subroutine with no row data. Its more dynamic as it can create any row with any data.

    Take this table for an example

    Directory Directory_Parent DefaultDir
    Directory1   Directory1
    Directory1-A Directory1 Directory1-A
    Directory1-B Directory1 Directory1-B

    In order to recreate that structure using the above subroutine this would suffice.

    CreateDirectoryTableRow("Directory", "" , "Directory1")

    CreateDirectoryTableRow("Directory1-A", "Directory1" , "Directory1-A")

    CreateDirectoryTableRow("Directory1-B", "Directory1" , "Directory1-B")

    If you wanted to get really smart something a single function to write to any table would be the best option here.

    Sub CreateAnyTableRow(strTableName, arrTableCells)

    for i = 0 to ubound(arrTableCells)

         'add smarter table handling code here.

    next

    end sub

     

    Ok well that about wraps up the easy lesson. I will take requests on code that people want from here or just kick into some advanced stuff on the next blog. So Owen if you have any ideas you would like to see come to reality now is the time and place !

    April 08

    Windows Installer Sequencing

    Ok well this is probably one of the harder topics to try and explain on paper. Its considerably easier to do with a whiteboard and a room full of people. But I will have a shot at it no doubt Kim (AngelD) will assist with any problem areas. Throughout this document try to keep this diagram in your head.

    sequences

    So in order to understand windows installer you really need to get your head around the installation sequences sooner rather than later. I often hear people throwing around the terms of Immediate and Deferred custom actions and completely misunderstanding the context of what this really means. So here I will try to explain how and why this is so important.

    Windows Installer has 6 tables which control the order and flow of when and where all other tables are read (as above). Of these 6 installation sequence tables only 2 of them are executed during a typical execution. Tables are to be executed are determined by the installation command line. The tables and their respective command lines are :

    Command Line Exec Table Name UI Table Name Top Level Action
    msiexec.exe /i InstallExecuteSequence InstallUISequence INSTALL
    msiexec.exe /a AdminExecuteSequence AdminUISequence ADMIN
    msiexec.exe /j AdvtExecuteSequence AdvtUISequence(usually hidden) ADVERTISE

    What this means is that when you run msiexec.exe /i <Path to msi> which is a typical installation command line you will be executing the two tables of InstallExecuteSequence and InstallUISequence.

    Additional to each pair of tables a User Interface Level (UILEVEL) is thrown into the mix by additional switches to the previous command line.

    Windows Installer supports four user interface levels:

    Command Line Description
    /qf Full: All authored modal and modeless and built-in modal error message dialogs are displayed.  This is the default user interface level.
    /qr Reduced: All authored modeless and built-in modal error message dialogs are displayed.
    /qb Basic: Only built-in modal error message dialogs are displayed.
    /qn None: Completely silent installation

    By utilising these extra command line switches items within the UI Tables as shown in the table above are conditionally loaded.

    For example:

    When user interface level is Full or Reduced, Windows Installer engine will start with processing actions from the UI sequence table and continue with Execute sequence table's actions.

    When user interface level is Basic or None, UI sequence table will be skipped and installation will be started with executing actions from the Execute sequence table.

    Note: Actions can be in both tables and may or may not be executed in both of the respective tables. When custom actions are involved this can be toggled by custom action attributes (as specified from the type column of the custom action table)

    Installation Phases

    Each installation is broken down into a series of phases those phases each have different attributes and more importantly occur at staged times throughout the installation. The first of the phases is the Acquisition phase.

    Acquisition Phase (Immediate)

    The acquisition phase consists of all actions from the appropriate UI Sequence table (as controlled from the selected command line options) and all actions from the Install Sequence table (as controlled from the selected command line options). Generally during the acquisition phase items should not be installed to the target platform it should be used purely as a data gathering phase. The data gathered is then used to conditionally control the actions which happen in the later phases.

    During this phase some of the main items performed are:

    • Checking prerequisites (optional):

      • Installation is started up on the correct version of operating system, platform, etc (Launch Conditions).

      • Competitive upgrade is being installed on the system with competing product installed (Application Search).

      • License for licensed product is valid (Product Validation)

    • Selecting features to install (either through user interface or properties in the command line).

    • File Costing - making sure that target system has enough of available hard disk space to install the product.

    • Generating the installation script for the next phase.

    To gain some real appreciation of what is happening during this phase and in particular from when the action InstallInitialize is reached. The InstallInitialize action is important as this action signifies that start of the Installation script generation. As I have tried to depict with the main diagram at the top of this page the first parse of the actions between InstallInitialize and InstallFinalize are parsed and their conditions are evaluated, where a condition is met the action is written into the Installation script. (Note: this script does not exist prior to installation). This process is repeated until the InstallFinalize action is reached.

    Now to throw a little confusion into the mix where an action is a custom action the custom actions type is evaluated and if the custom action has an Immediate attribute and the condition is evaluated as true then that custom action is executed Immediately hence the name. Where a custom action has an attribute of Deferred and the condition is evaluated as true that custom action is written into the installation script.

    Note: Immediate phase when it comes to custom actions.  All Immediate custom actions are running in the context of the user performing the installation.  Immediate custom actions must not change the system.

     

    Execution Phase

    The execution phase is split into two separate phases known as Deferred and Commit/Rollback.

     

    Deferred Phase

    This phase is performed by InstallFinalize action in the Execute sequence table.  During this phase the installation script, generated during acquisition phase, is passed to a process which runs with elevated privileges.  This process executes all actions from the script.  This is where the actual installation takes place, files are copied registry is written and the application is installed. When an action is a custom action the custom actions attributes are evaluated and if it is deemed to be a Deferred - Commit that action is moved across into the 2nd installation script. Where a custom action is a standard deferred custom action is it simply executed at that point.

    Depending on success or failure of the running installation script, the Execution phase will be completed by:

    • Rollback installation:  If installation is unsuccessful, the installer restores the original state of the system.

    • Commit phase:  If installation is successful, installer will run all Commit custom actions.

    Some important items to note here are as we switch from the immediate phase into a generated installation script, it is at this point that we lose contact with the Windows Installer Session which in turn means that we lose access to a number of properties that were available during the immediate phase.

    Note: Deferred custom actions usually run with elevated privileges unless they are not impersonated.  Impersonated deferred custom actions are running in the context of the user performing the installation.

     

    Commit Phase

    Once the Deferred phase reaches the InstallFinalize action and all actions are successfully run the Commit phase runs. During this phase only Deferred custom actions with a Commit / Rollback attribute will be present in the 2nd installation script. As such this is where Deferred Commit / Rollback actions are run. Actions in this phase a custom actions which deploy a item which needs an opposing uninstall action to perform removal. (a poor example as I can't think of one quickly is if you installed a service using a custom action call as opposed to the service tables)

    Once the commit phase reaches InstallFinalize it hands back to the immediate phase which is generally where typical cleanup of application installation data will occur. Note at this point you again have access to all the session which means properties are again accessible.

    Ok so that wraps up the Beginners guide to Installation sequences from here I will move onto an Advanced guide which will target all of the MSI Function calls and actual attributes etc. (Might need your help here AngelD)

    April 02

    Windows Installer technical chat

    Windows Installer (MSI) 4.5 Technical Chat

    http://blogs.msdn.com/windows_installer_team/archive/2008/04/01/windows-installer-msi-4-5-technical-chat.aspx

    Windows Installer 4.5 chat is due in another two days. The MSDN Technical Chat is scheduled on April 3, 2008 at 10:00 AM (Pacific). Click here to add the chat to your calendar so you don't miss it!

    [Author: Hemchander  Sannidhanam]
    This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm.

    TCP/IP

     

    Ok so this is a little left field but it was a request from a colleague as he knew I had already written it for some my team. (this is not my field of expertise just something I dabble with from time to time). 

    Over the weekend I will attempt to cover Windows Installer sequencing, I get so many questions relating to this it drives me crazy. I think this is due to the complete lack of legible information about this topic. Hopefully I can address that problem a little.

    Network Protocol – TCP/IP

    TCP/IP has been adopted as the worldwide protocol for Internet based communications. There are a number of other networking protocols available however these are not widely used or implemented specifically when making reference to internet based communications. TCP/IP has a few revisions which are currently being utilized throughout the world which are:

    The revision of TCP/IP which will be used through this document is IPv4.

    Understanding IPv4

    What makes up an IPv4 address.

    IPv4 networking consists of an IP address and a Subnet mask. These two items are written with the following structure.

    Decimal Representation

    IP Address: 192.168.0.1

    Subnet Mask: 255.255.255.0

    The numbers displayed are a decimal representation of what is actually a binary numbering system. Each decimal place represents an Octet. The reason it is called an octet is because it actually represents and 8 bit binary number.

    For example the subnet mask in the above example would actually be written as:

    Binary Representation

    Subnet Mask: 11111111.11111111.11111111.00000000

    Each decimal place represents a group of 8 binary digits. Therefore both the IP address and subnet mask are 4 octets of 8 bit binary.

    In my experience I have found that many IT professionals actually have very little understanding of TCP/IP and many have absolutely no understanding that the decimal numbers they are typing into network card settings are really in fact binary numbers. It is this reason that I cannot stress the importance of understanding what is really going on when configuring a network device with IPv4 TCP/IP.

    So the first step to understanding TCP/IP is that each number you are viewing is really a decimal view of what is actually binary notation. The only reason it is in decimal format is that we humans are prone to error. As such it is far easier for us to write a number correctly in decimal form than it is to write the same number in binary form.

    To alleviate this issue we convert the binary numbers to decimal for human readability, however a computer prefers to read this information in binary format. So in order to support the best of both worlds we let the computer read the numbers in binary but convert them to decimal solely for us humans.

    Its usually about here, we humans are often caught making the mistake of thinking of the numbers as decimal but they most definitely are NOT. They are treated as binary numbers (a similar thing happens with Custom Actions which I will go into later).


    Converting Decimal to Binary

    So to make our first step into understanding TCP/IP we need to know how to convert a decimal number to a binary number. Now I am going to skip the math lesson here and jump straight to the Windows Calculator.

    Lets assume we are using the same address we quoted earlier in this section.

    Decimal Representation

    IP Address: 192.168.0.1

    Subnet Mask: 255.255.255.0

    If you open the Windows Calculator and switch the calculator into Scientific mode you should see something similar to this.

    clip_image002

    Ensure that your calculator has the “DEC” radio button selected. Now enter the first octet of the IP Address which is: 192

    clip_image004

    Now switch your calculator radio button to “Bin”. You should see an 8 bit binary reference like this. Showing “11000000”.

    clip_image006

    Repeat this step for all octets of both the IP Address and Subnet Mask.

    Now once this step has been completed you should have the following information about your IP Address and Subnet Mask.

    IP Address: 192.168.0.1

    IP Address: 11000000.10101000.00000000.00000001

    Subnet Mask: 255.255.255.0

    Subnet Mask: 11111111.11111111.11111111.00000000

    Now the next step in understanding TCP/IP is that you need to understand the minimum requirements for two TCP/IP devices to successfully communicate with each other are:

    1) IP Address

    2) Subnet Mask

    If you have one without the other you cannot communicate using TCP/IP.


    Network ID

    TCP/IP consists of Networks and Hosts each of which are labelled with a Network ID and a Host ID. Unless you have both an IP Address and a Subnet Mask you cannot determine the Network ID or Host ID. This is why you need both the IP and Mask to successfully transmit TCP/IP. It is about here where you will likely realize why a computer uses binary in preference to our decimal system.

    One of the steps a computer performs is called Subnet ANDing. ANDing binary numbers causes the following to happen.

    When you and the following two binary numbers together you get the results shown in the table below.

     

    Binary Number 1 Binary Number 2 Binary AND result
    0 0 0
    0 1 0
    1 0 0
    1 1 1

    Using the table above you can see that when two 1’s are ANDed you receive a result of 1. Now lets apply this to each of the 4 octets of binary for both the IP address and the Subnet Mask.

     

    Item Octet 1 Octet 2 Octet 3 Octet 4
    IP Address 11000000 10101000 00000000 00000001
    Subnet Mask 11111111 11111111 11111111 00000000
    AND Result 11000000 10101000 00000000 00000000

    Converting the AND Result back to decimal will show the Network ID.

    Binary Notation

    IP Address:     11000000.10101000.00000000.00000001

    Subnet Mask:  11111111.11111111.11111111.00000000

    AND Result:    11000000.10101000.00000000.00000000

    Network ID:    11000000.10101000.00000000.00000000

    Decimal Notation

    IP Address: 192.168.0.1

    Subnet Mask: 255.255.255.0

    AND Result: 192.168.0.0

    Network ID: 192.168.0.0

    Subnet Mask

    Now that we have the Network ID we need to understand the importance of the subnet mask. One thing to note about the Subnet Mask is that we cannot use all decimal numbers in the Subnet mask. The subnet mask can only use numbers which resolve to Binary octets that either are all 0’s or start with a 1 and finish with a 0's. Just to be a little bit tricky when the binary number has its first 0 it the remaining binary digits must all be 0 for all remaining octets.

    For example the following items show valid and subnet Mask octets.

    Valid items are:

    Binary Notation Decimal Notation Short Notation
    Class A    
    11111111.00000000.00000000.00000000 255.0.0.0 IP/8
    11111111.10000000.00000000.00000000 255.128.0.0 IP/9
    11111111.11000000.00000000.00000000 255.192.0.0 IP/10
    11111111.11100000.00000000.00000000 255.224.0.0 IP/11
    11111111.11110000.00000000.00000000 255.240.0.0 IP/12
    11111111.11111000.00000000.00000000 255.248.0.0 IP/13
    11111111.11111100.00000000.00000000 255.252.0.0 IP/14
    11111111.11111110.00000000.00000000 255.254.0.0 IP/15
    Class B    
    11111111.11111111.00000000.00000000 255.255.0.0 IP/16
    11111111.11111111.10000000.00000000 255.255.128.0 IP/17
    11111111.11111111.11000000.00000000 255.255.192.0 IP/18
    11111111.11111111.11100000.00000000 255.255.224.0 IP/19
    11111111.11111111.11110000.00000000 255.255.240.0 IP/20
    11111111.11111111.11111000.00000000 255.255.248.0 IP/21
    11111111.11111111.11111100.00000000 255.255.252.0 IP/22
    11111111.11111111.11111110.00000000 255.255.254.0 IP/23
    Class C    
    11111111.11111111.11111111.00000000 255.255.255.0 IP/24
    11111111.11111111.11111111.10000000 255.255.255.128 IP/25
    11111111.11111111.11111111.11000000 255.255.255.192 IP/26
    11111111.11111111.11111111.11100000 255.255.255.224 IP/27
    11111111.11111111.11111111.11110000 255.255.255.240 IP/28
    11111111.11111111.11111111.11111000 255.255.255.248 IP/29
    11111111.11111111.11111111.11111100 255.255.255.252 IP/30
    11111111.11111111.11111111.11111110 255.255.255.254 IP/31

    A few invalid items as examples are:

    Binary Notation Decimal Notation
    11001100 204
    10000001 129

    As shown earlier the Subnet Mask is used to determine the Network ID it is also used however to determine how big or how many hosts will be contained in each network.

    The following table will give an indication of how many networks vs how many hosts in each network. Remember there is 32 bits in total for an entire IP Address or Subnet Mask. As such 8 bits of Network ID leaves 24 bits allocated to the Hosts.

     

    Network Bits Networks Host Bits Hosts
    8 (255.0.0.0) 256 24 (255.0.0.0) 16777216 – 2
    9 512 23 8388608 – 2
    10 1024 22 4194304 – 2
    11 2048 21 2097152 – 2
    12 4096 20 1048576 – 2
    13 8192 19 524288 – 2
    14 16384 18 262144 – 2
    15 32768 17 131072- 2
    16 (255.255.0.0) 65536 16 (255.255.0.0) 65536 – 2
    17 131072 15 32768 – 2
    18 262144 14 16384 – 2
    19 524288 13 8192 – 2
    20 1048576 12 4096 – 2
    21 2097152 11 2048 – 2
    22 4194304 10 1024 – 2
    23 8388608 9 512 – 2
    24 (255.255.255.0) 16777216 8 (255.255.255.0) 256 – 2
    25 33554432 7 128 – 2
    26 67108864 6 64 – 2
    27 134217728 5 32 – 2
    28 268435456 4 16 – 2
    29 536870912 3 8 – 2
    30 1073741824 2 4 – 2

    So in effect the Subnet Mask does two things of considerable importance which are:

    1) Determines the Network ID (via AND the IP and Mask)

    2) Determines the Size of the network (i.e. Network / Hosts)

    The reasons behind changing the size of the subnet are in relation to reducing network traffic and or increasing security on a network.


    Configuring Network Interface Cards

    Single Segment

    For a machine to connect to a network it will need a minimum of;

    a) An IP Address

    b) A Subnet Mask

    In this configuration a machine will be able to communicate with machines on the same segment using broadcasts.

    To prove this scenario create two machines which are physically cabled together and put them on the same network segment.

    For example:

    Machine A

    IP Address: 192.168.0.1 or 192.168.0.1/24

    Subnet Mask: 255.255.255.0

    Machine B: 192.168.0.2

    Subnet Mask: 255.255.255.0 or 192.168.0.2/24


    Multiple Segment

    For a machine to connect to a network with multiple segments it will need a minimum of;

    a) An IP Address

    b) A Subnet Mask

    c) A default gateway

    When trying to contact machines which are on a different segment it will then pass that request onto the device which is configured in the default gateway setting. This device should have a route table which is aware of the other segments on the network. The default gateway can be considered a little like an information desk in a large shopping mall. When you don’t know where you want to go you head to the information desk. A network device will look to its default gateway when it does not know where to find a network device on an unknown segment.

    To prove this scenario create two machines which are physically cabled together and put them on the same network segment. On one of the two machines Install 2 NIC cards. One of these can be virtual if you are using VMWare

    For example:

    Machine A

    IP Address: 192.168.0.1 or 192.168.0.1/24

    Subnet Mask: 255.255.255.0

    IP Address: 192.168.1.1 or 192.168.1.1/24

    Subnet Mask: 255.255.255.0

    Machine B: 192.168.0.2

    Subnet Mask: 255.255.255.0 or 192.168.0.2/24

    From Machine B ping 192.168.0.1, you should get a reply from this machine. Now ping 192.168.1.1 at this point you will not get a reply as Machine B is only aware of machines on the same Segment.

    Now change the configuration of Machine B to the following;

    Machine B: 192.168.0.2

    Subnet Mask: 255.255.255.0 or 192.168.0.2/24

    Default GW: 192.168.0.1

    Ping 192.168.0.1 as before you will get a reply. Now ping 192.168.1.1 which previously failed. Now however even though Machine B is not aware the other segment on Machine A. It will pass its request to its default gateway which is aware of the addresses on other segments of Machine A.

    April 01

    A packagers guide to the Windows Registry and COM

     

    Basic understanding of the com DLL

    A COM class is code which is capable of instantiating or creating an object, or multiple objects for that matter. These objects can have methods and properties which allow manipulation of the object and or its contents. An example COM object which could be used with everyday use is the “FileSystemObject”. Instantiation of a “FileSystemObject” will allow you to manipulate the file system using methods and properties of the “FileSystemObject”.

    The following example is how you could use an VBS API call to a COM class to instantiate the “FileSystemObject”

    Set objFSO = CreateObject(“Scripting.FileSystemObject”)

    Method

    A method is a function or piece of code which performs an action against an object created from a COM class. A common example of a method which is easy to relate to an everyday task is creating a folder.

    Using the same “FileSystemObject” we can manipulate the file system or perform an action on that file system object using one of its methods.

    Based on this using the object previous created objFSO we can now utilize a method to perform the required action in this case creating a folder. The method name is CreateFolder.

    objFSO.CreateFolder(“Your folder path would go here”)

    i.e.

    objFSO.CreateFolder(“c:\test”)


    Property

    A property is a little simpler and acts simply like a variable would in any other coding language. A property can be set or retrieved using a syntax similar to the following.

    To set a property a syntax similar to the following would be used.

    objTest.Property = “Your New Value”

    To retrieve a property a syntax similar to the following would be used.

    strPropertyValue = objTest.Property


    What is an API

    An Application Programming Interface (API). The API made it easier for applications to integrate with the Operating System and also allowed for a scriptable interface to the application. Windows Installer has a very comprehensive API or COM class which allows accessibility to the entire Windows Installer Service and its applications via a scriptable interface. This will be covered heavily in a future blog or (my advanced courses).

    To understand how an API works it is necessary to break down more of the components of a COM class and how they are written to the registry.

    Programmatic Identifiers

    What is a Programmatic Identifier (ProgID). A ProgID acts in a very similar way to that which DNS acts with an IP address. Where DNS will resolve a DNS name to an IP address and vice versa. The ProgID can be likened to that of the DNS name.

    The ProgID is a friendly name which relates to a ClassID (a value which is significantly harder to remember than a ProgID)

    The ProgID is stored in the Windows Registry in the HKEY_CLASSES_ROOT hive.

    An example of such a key is. [HKEY_CLASSES_ROOT\WindowsInstaller.Installer]

    clip_image002

    As mentioned previously the ProgID relates directly to a ClassID in the same way as DNS does an IP address. The diagram above shows a child key with the name CLSID. Selecting this item as shown in the following diagram will expose the CLSID entry which is related to the WindowsInstaller.Installer ProgID.

       clip_image004

    Class Identifier

    A ClassID is a unique descriptor which identifies a COM class. These descriptors use a common naming structure known as a GUID in the case of a COM dll the GUID is a 32 bit Hexadecimal GUID.. A 32 bit GUID looks similar to the following item:

    {000C1090-0000-0000-C000-000000000046}

    It is thought to be extremely rare if not impossible to randomly create two GUID’s with the same details. The ClassID is stored in the Windows Registry in the hive HKEY_CLASSES_ROOT.

    The following example shows the subkey's present for this particular ClassID.

       clip_image006

    A COM class has a few subkey's of particular interest to a packager and his associated support teams. One of these attributes in particular is: InProcServer32

     

    InprocServer32

    These items are of particular interest as they are core in understanding a common problem often referred to as DLL Hell. The InProcServer32 key contains the path to the actual DLL itself, or to confuse things a little more can also contain a Windows Installer Darwin Descriptor.

       clip_image008

    As such the ProgID, ClassID and InprocServer32 keys are all heavily related. In this example we have learnt the following items about this particular COM class.

    The COM class has a ProgID of:

    WindowsInstaller.Installer.

    That ProgID is related to this ClassID.

    {000C1090-0000-0000-C000-000000000046}

    This ClassID has an InprocServer32 value of:

    C:\WindowsSystem32\msi.dll  as shown in the following table.

    Item Description
    ProgID WindowsInstaller.Installer
    ClassID {000C1090-0000-0000-C000-000000000046}
    InprocServer32 C:\Windows\System32\msi.dll

    As such when an API call is made the following items occur.

    Where an application or script uses an API call such as:

    Set objWindowsInstaller = CreateObject(“WindowsInstaller.Installer”)

    The operating system will first lookup the ProgID in the Windows registry, it will then cross reference the ProgID with its associated ClassID which will in turn look for an InProcServer32 value which will contain the path to the actual DLL which contains the COM class.

    A simpler model of this is depicted in the following diagram.

    ProgCycle

    TypeLibs

    A typelib file is used to assist with understanding the content within an unknown DLL. For example if a vendor write a COM class but doesn't want to disclose the source code they can generate a typelib file. The typelib identifies the content of the DLL without exposing the source. In lamens terms it sort of documents the content of DLL. Tools such as Visual Studios, Microsoft Basic editor leverage functionality in the typelib to expose methods and properties within the script editors.

    image

    How does this information get into the registry

    One may ask how does this registry information get into the registry and what is its importance. Most of you are likely to of heard of a tool called REGSVR32.EXE maybe some of you have even used it yet not understood what it was actually doing behind the scenes.

    REGSVR32.EXE

    REGSVR32.EXE is relatively simple to use, its purpose is to populate the HEY_CLASSES_ROOT registry hive with the information shown in the previous descriptions. In actual fact it can input considerably more information into the registry however that is best left for another lesson.

    To run REGSVR32 you can use one of the following two command lines.

    To register a DLL

    REGSVR32.EXE /R <path to your dll that requires registration>

    To unregister a DLL

    REGSVR32.EXE /U <path to your dll that requires unregistered>

    The only downside to the REGSVR32.EXE tool is it give very little information away as to what it is actually doing. There is however a solution to this mystery and that solution is WISECOMCAPTURE.EXE (only available to licensed Wise users)

    WISECOMCAPTURE.EXE

    WISECOMCAPTURE.EXE is a tool developed by Altiris / Wise which mimics that of REGSVR32.EXE and in addition it also creates a .REG file which can be used for the purpose of examining what happened during your registration process. This tool is really useful when troubleshooting specific DLL hell issues.

    To run WISECOMCAPTURE.EXE you can use one of the following two command lines.

    To register a DLL

    WISECOMCAPTURE.EXE /R <path to dll> <path to output file .REG>

    To unregister a DLL

    WISECOMCAPTURE.EXE /U <path to dll> <path to output file .REG>

    Why is all of this information important.

    The importance of this information is heavily dependant on your role. For an application packager it is very important to know when a dll should be registered and even more importantly when it should not be registered.

    A specific example of when not to register a DLL is when a DLL is present in System or System32 folder and another copy of the same DLL for some reason is delivered to the application directory. Such as “c:\program files\Your application”

    Using this example lets trace back a few steps, registering a dll will add the following entries.

    When registered in System32

    Item Description
    ProgID WindowsInstaller.Installer
    ClassID {000C1090-0000-0000-C000-000000000046}
    InprocServer32 C:\Windows\System32\msi.dll

    If you registered the same dll into "c:\program files\myApplication\msi.dll"

    Item Description
    ProgID WindowsInstaller.Installer
    ClassID {000C1090-0000-0000-C000-000000000046}
    InprocServer32 C:\program files\MyApplication\Msi.dll

    Did anyone spot the similarities ? The only change in both instances the InProcServer32 entry has changed. This becomes problematic as the last registration of a DLL will rewrite the location of the InProcServer32 keys. Although initial inspection one would think this is not an issue. As both the file and its registration will be present. So what's the big deal ?

    The big deal actually doesn’t rare its very ugly head until you uninstall the application which last registered the dll. Uninstalling the application is likely to remove the files from the program files location as its is highly unlikely a reference count exists. If it removes the file and any other application was reliant on it being present you now have a broken COM object. Or worse still if it removes the file and its registration you have even less information about that COM object.

    So from a packagers perspective its very important to know when you should and shouldn’t register a DLL.

    If the file remained in System32 it is far less likely to be removed from a machine for a few reasons such as:

    MSI Component Reference counting, Shared dll Reference counting.

    What else should a packager be aware of ? Upgrading and downgrading DLL’s is particularly problematic also. With old installation technologies such as SETUP.EXE an installer would simply drop a DLL with little or no regard to the version of such a DLL. These installers could cause al kinds of havoc particularly if a DLL was downgraded. As this would mean the DLL may have methods and properties which were simply no longer present within the dll. The upgrade on the other hand should support backwards compatibility particularly if the DLL is made by the same vendor, as such upgrade are often less problematic than downgrades.

    With the advent of Windows Installer the downgrade of DLL’s is considerably more controllable. As Windows Installer can be told to only allow upgrades of files and never allow a downgrade. (check out the REINSTALLMODE property in the SDK)

    So what does all of this mean for a support person. A support person needs to know that older installation technologies can downgrade DLL’s without warning. Where Windows Installer is concerned it is important a support person know how to control and suspend downgrading of a DLL.

    Knowing how a DLL lookup is performed can allow reverse engineering and tracking of problematic and hidden DLL issues.

    Ok im getting tired so moving on from here I will cover Isolation topics etc..