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

Blog


    April 16

    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 !