Link to tool: CubMakr Online Tool
This is a quick demonstration of the new CubMakr Version 2 features. It covers custom ICE naming, integration with your application tracker and custom ICE routines.
Link to tool: CubMakr Online Tool
This is a quick demonstration of the new CubMakr Version 2 features. It covers custom ICE naming, integration with your application tracker and custom ICE routines.
Last week we released CubMakr V2, with a few new features. You can find a quick video here.
One of the new features was simply being able to specify custom ICE references as opposed to all the ICE messages appearing as ALKANEICE01. This was a request from Rory over at Rorymon.com.This can now be specified when you’re creating a new rule, at the very top:
If you’ve already created your rules you can edit each rule and change the name accordingly. This is useful so that you can refer to specific ICE messages like you can with normal MSI validation (ICE33, ICE03 etc). It also means you can set up a web page with references to your custom ICE messages and instructions on how they can be resolved. Unfortunately, the HelpURL part of the ICE message no longer appears to be supported by most products so we can’t include this in the CubMakr toolset (sad face).
The second new feature in Version 2 is Advanced Scripting. This is really interesting, as it enables advanced users to include their own custom VBScripts into the cub files. These scripts can be used to perform custom ICE routines (which cannot be handled by CubMakr natively, such as detecting empty components) and even integrate with your own application tracking tools for real-time validation!! The scripts can be sequenced to run before the CubMakr rules, or after the CubMakr rules. An example of advanced scripting can be found here.
I thought I’d give a quick example of how advanced users can utilise advanced scripting in CubMakr.
The first example I want to show is how we can integrate the CubMakr with your in-house tracking tool. Some places use Sharepoint to track applications, some places use a SQL Server back-end (ok…so does Sharepoint….but I believe the preferred approach for querying Sharepoint is to use CAML and you can find a VBScript example on my blog here).
I’ll let you read the commented code to see what the script does, but basically:
Because we want to set the ALKANEMANUFACTURER property before the CubMakr rules are run, we would obviously set this script to run before the CubMakr rules. Here’s the script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
Dim objConnection : Set objConnection = CreateObject("ADODB.Connection") Dim objRecordSet : Set objRecordSet = CreateObject("ADODB.Recordset") 'Connection string for our fictitious application tracker objConnection.Open _ "Provider=SQLOLEDB;Data Source=.\SQLExpress;" & _ "Trusted_Connection=Yes;Initial Catalog=alkanetracker;" & _ "User ID=alkanetest;Password=24rHj7Zz4OFNhYV;" Dim packagevendor : packagevendor = "" Dim packageapplication : packageapplication = "" Dim packageversion : packageversion = "" Const adVarChar = 200 Const adParamInput = 1 Dim sql : sql = "" Dim oView, oRecord 'First get the package reference. We use Session.ProductProperty here because the ALKANEREF property is part of our MSI, and 'not a property which has been created in the current in-memory session. alkaneRef = Session.ProductProperty("ALKANEREF") 'select all columns for our package with the specified package reference set cmd = CreateObject("ADODB.Command") with cmd .ActiveConnection = objConnection .CommandText = "SELECT * FROM packages WHERE packagereference = ?" .Parameters.Append .CreateParameter("packagereference",adVarChar,adParamInput,50,alkaneRef) end with set objRecordSet = cmd.execute Do While NOT objRecordSet.Eof packagevendor = objRecordSet("packagevendor") packageapplication = objRecordSet("packageapplication") packageversion = objRecordSet("packageversion") 'set ALKANEMANUFACTURER for our CubMakr rule. Remember that Session.Property is read/write, Session.ProductProperty is read only! Session.Property("ALKANEMANUFACTURER") = packagevendor objRecordSet.MoveNext Loop set cmd = nothing Set objConnection = Nothing Set objRecordSet = Nothing |
The second example I wanted to show you was writing your own custom ICE routines. CubMakr is used to provide a simplified way of checking for certain entries within the windows installer tables and summary information stream. It cannot handle more complex scenarios, such as checking for empty component, checking for duplicate registry entries etc. For these scenarios there is now the facility to add your own custom ICE routines. Here is an example of some custom ICE routines:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
'initialise variables and constants Const msiMessageTypeError = &H01000000 Const msiMessageTypeWarning = &H02000000 Const msiMessageTypeUser = &H03000000 Dim messageIceName, messageDescription, messageDescriptionAddition, messageType, messageTable, messageColumn 'Message Types '0 MessageFail '1 MessageError '2 MessageWarning '3 MessageInfo 'call Custom ICEs in this order Call checkEmptyComponents() Call checkNoKeypath() Call noDuplicateRegistry() 'This function checks for empty components Function checkEmptyComponents() 'Here we specify: 'The name of the ICE message messageIceName = "EXAMPLE01" 'The type of ICE message (see top of script) messageType = 2 'The description we want to show in the ICE message messageDescription = "'[1]' is an empty component. Please delete." 'The table that the ICE message relates to messageTable = "Component" 'The column where the issue resides messageColumn = "Component" Dim componentsView, componentsRec, tableView, tableRec, dataView, dataRec Dim emptyComponent : emptyComponent = True Dim tempComponent : tempComponent = "" If Session.Database.TablePersistent("Component") = 1 Then Set componentsView = Session.Database.OpenView("Select `Component` From `Component` ORDER BY `Component`") componentsView.Execute Set componentsRec = componentsView.Fetch Do While Not componentsRec is Nothing tempComponent = componentsRec.StringData(1) 'list the tables that have 'Component_' (foreign key) columns Set tableView = Session.Database.OpenView("SELECT `Table` FROM `_Columns` WHERE `Name`= 'Component_' AND `Table` <> 'FeatureComponents'") tableView.Execute Set tableRec = tableView.Fetch Do While Not tableRec is Nothing emptyComponent = True Set dataView = Session.Database.OpenView("SELECT `Component_` FROM `" & tableRec.StringData(1) & "` WHERE `Component_`='" & tempComponent & "'") dataView.Execute If Not dataView.Fetch is Nothing Then 'this table has a some data belonging to some component 'component contains data emptyComponent = False 'skip component and move to next Exit Do End If Set tableRec = tableView.Fetch Loop If emptyComponent Then componentsRec.StringData(0) = messageIceName & Chr(9) & messageType & Chr(9) & messageDescription & Chr(9) & "" & Chr(9) & messageTable & chr(9) & messageColumn & chr(9) & "[1]" Session.Message msiMessageTypeUser,componentsRec End If Set componentsRec = componentsView.Fetch Loop Set tableRec = Nothing Set tableView = Nothing Set componentsView = Nothing Set componentsRec = Nothing Set dataView = Nothing End If 'return success checkEmptyComponents = 1 End Function 'This function checks for components without a keypath, and if a keypath is available, suggests it. Function checkNoKeypath() Dim keypathView, keypathRec, blnKeypathSet, tempView, tempRec messageIceName = "EXAMPLE02" messageType = 2 messageDescription = "Component '[1]' does not have a keypath set." messageTable = "Component" messageColumn = "Component" If Session.Database.TablePersistent("Component") = 1 Then 'find all components which do not have Keypaths Set keypathView = Session.Database.OpenView("SELECT `Component`,`ComponentId`, `Attributes` FROM `Component` WHERE `KeyPath` IS Null") keypathView.Execute Set keypathRec = keypathView.Fetch Do Until keypathRec Is Nothing 'initiate this to false blnKeypathSet = False messageDescriptionAddition = " No suitable keypath entries were found." If Session.Database.TablePersistent("File") = 1 Then 'Check file table Set Tempview = Session.Database.OpenView("SELECT `File`,`Component_` FROM `File` WHERE `Component_`='" & keypathRec.StringData(1) & "'") Tempview.Execute Set tempRec = Tempview.Fetch If Not tempRec Is Nothing Then blnKeypathSet = True messageDescriptionAddition = " A suitable keypath may be '" & tempRec.StringData(1) & "' in the File table." End If Set Tempview = Nothing Set tempRec = Nothing End If If Not blnKeypathSet Then If Session.Database.TablePersistent("Registry") = 1 Then Set Tempview = Session.Database.OpenView("SELECT `Registry`, `Component_` FROM `Registry` WHERE `Component_`='" & keypathRec.StringData(1) & "'") Tempview.Execute Set tempRec = Tempview.fetch If Not tempRec is Nothing Then blnKeypathSet = True messageDescriptionAddition = " A suitable keypath may be '" & tempRec.StringData(1) & "' in the Registry table." end If Set Tempview = Nothing Set tempRec = Nothing End If End If If Not blnKeypathSet Then If Session.Database.TablePersistent("ODBCDataSource") = 1 Then 'check ODBCDataSource table Set Tempview = Session.Database.OpenView("SELECT `DataSource`, `Component_` FROM `ODBCDataSource` WHERE `Component_`='" & keypathRec.StringData(1) & "'") Tempview.Execute Set tempRec = Tempview.fetch If Not tempRec is Nothing Then blnKeypathSet = True messageDescriptionAddition = " A suitable keypath may be '" & tempRec.StringData(1) & "' in the ODBCDataSource table." end If Set Tempview = Nothing Set tempRec = Nothing End If End If keypathRec.StringData(0) = messageIceName & Chr(9) & messageType & Chr(9) & messageDescription & messageDescriptionAddition & Chr(9) & "" & Chr(9) & messageTable & chr(9) & messageColumn & chr(9) & "[1]" Session.Message msiMessageTypeUser,keypathRec Set keypathRec = keypathView.Fetch Loop End If Set keypathRec = Nothing Set keypathView = Nothing Set TempRec = Nothing Set Tempview = Nothing checkNoKeypath = 1 End Function 'This function checks that we do not have any duplicate registry. Duplicate registry items usually occur when users start to import 'registry and they're not being careful! Function noDuplicateRegistry() messageIceName = "EXAMPLE03" messageType = 2 messageTable = "Registry" messageColumn = "Registry" Dim registryView, registryRecord, duplicateView, duplicateRecord, tempRecord If Session.Database.TablePersistent("Registry") = 1 And Session.Database.TablePersistent("Component") = 1 Then Set registryView = Session.Database.OpenView("SELECT `Registry`,`Key`,`Name`,`Value`,`Component_` FROM `Registry`") registryView.Execute Set registryRecord = registryView.Fetch Do Until registryRecord Is Nothing Set duplicateView = Session.Database.OpenView("SELECT `Registry` FROM `Registry` WHERE `Key`=? AND `Name`=? AND `Value`=? AND `Registry` <> ?") Set tempRecord = Session.Installer.CreateRecord(4) tempRecord.StringData(1) = registryRecord.StringData(2) tempRecord.StringData(2) = registryRecord.StringData(3) tempRecord.StringData(3) = registryRecord.StringData(4) tempRecord.StringData(4) = registryRecord.StringData(1) duplicateView.Execute(tempRecord) Set tempRecord = Nothing Set duplicateRecord = duplicateView.Fetch While not duplicateRecord is Nothing if not isMSMData(registryRecord.StringData(1)) and not isMSMData(duplicateRecord.StringData(1)) Then messageDescription = "Registry entry '[1]' is a duplicate of registry entry '" & duplicateRecord.StringData(1) & "'. Please investigate." registryRecord.StringData(0) = messageIceName & Chr(9) & messageType & Chr(9) & messageDescription & Chr(9) & "" & Chr(9) & messageTable & chr(9) & messageColumn & chr(9) & "[1]" Session.Message msiMessageTypeUser,registryRecord end If Set duplicateRecord = duplicateView.Fetch Wend Set registryRecord = registryView.Fetch Loop End If Set registryView = Nothing Set registryRecord = Nothing Set duplicateView = Nothing Set duplicateRecord = Nothing noDuplicateRegistry = 1 End Function 'returns true if tempData contains MSM decoration Function isMSMData(tempData) isMSMData = False Dim Match Dim regEx : Set regEx = New RegExp regEx.MultiLine = vbTrue regEx.global = vbTrue regEx.Pattern = "[A-Za-z0-9]{8}_[A-Za-z0-9]{4}_[A-Za-z0-9]{4}_[A-Za-z0-9]{4}_[A-Za-z0-9]{12}" For Each Match in regEx.Execute(tempData) isMSMData = True Next Set regEx = Nothing End Function |
Link to tool: CubMakr Online Tool
This is a quick (kind of) demonstration of the CubMakr online tool. It goes through a few examples of how cub files can be generated easily without any scripting knowledge required.
This page takes us through some quick examples of CubMakr detection examples. It includes examples using different search methods (‘equals’,’starts with’,’contains’,’regular expression’), resolving proerties, performing case-sensitive searches and using conditions.
Example 1: Perform a case-sensitive search for a property name. If property/value is missing, display a warning message.
Check: Check that the ALLUSERS property is set to 1.
Example 2: Use the ‘contains’ comparison and flag for modification.
Check: Check the the Value column of the Registry table does not contain hard-coded paths.
Example 3: Use the ‘equals’ comparison and flag for removal.
Check: Check the TNSNames.ora is not in the package (Files such as this are usually managed outside the package).
Example 4: Use the ‘starts with’ comparison.
Check: Check that services use the ServiceInstall/ServiceControl tables, and not the registry.
Example 5: Use a case-sensitive regular expressions to check for the presence of multiple values.
Check: Use a regular expression to ensure that the COMPANYNAME property is one of our supported clients.
Tip: Use the following regular expression examples to construct your own searches.
Starts with: ^searchstringEnds with: searchstring$
Contains: searchstring
Equals: ^searchstring$
Example 6: Use the ‘Resolve properties’ setting to resolve a property value.
Check: Check that the Summary Information Stream Title value is the same as the ProductName property value.
Example 7: Use a condition for the validation rule.
Check: Check that a launch condition is set. Add a condition specifying that this rule is only valid when validating packages targeted at Windows 7 platforms.
Link to tool: CubMakr – Simplified and Centralised Online Cub File Creation
Link to video demonstration: CubMakr Video
Link to examples: CubMakr Examples
Link to V2 Updates: CubMakr V2 BETA Release
Link to video demonstration for V2: CubMakr V2 Video
When creating MSI packages we should always ensure that they conform to Windows logo standards and best practises. Adhering to these standards enables us to produce more reliable and well-authored MSI packages.
To perform this compliance check, we usually validate our MSIs using the Full MSI Validation Suite. What this validation process does is validate the MSI tables against certain rules which are specified in a .cub file called darice.cub. As a result of running this validation, we may be prompted with ICE warnings/errors which we should attempt (in most cases) to resolve.
This validation is great, but what if we need to do our own custom validation? For example, Alkane Solutions do work for multiple clients who all have different requirements:
Customer-specific naming conventions:
Some of our clients use specific naming conventions for things like the ProductName property. Some like to limit the number of periods in the ProductVersion.
In-house validation checks:
We like to check that our tables do not contain hard-coded paths, that we only append to (and not overwrite) the Path environment variable, that services use the ServiceInstall/ServiceControl tables and not the registry table, and that TNSNames.ora is not included with any package.
But how can we perform these validation checks when we have no scripting knowledge, and no knowledge of .cub files?
Easy – that’s where CubMakr comes in.
CubMakr is a custom toolset written using the Wix DTF (Deployment Tools Foundation) with the aim of generating custom CUB files to validate your MSI packages. No scripting knowledge is required!! CubMakr should be used to generate a CUB file which reflects the packaging standards that your company/client adheres to. Using it will:
Here are a list of features:
and here is an example of things that can be validated for:
Since the first release is just a BETA (to see if people find it useful), we have not included a way of saving cub files and retrieving them at a later date. Unfortunately whatever cubs you generate are only persistent for the current browsing session. The long term goal would be to enable people to save cubs for each client/project.
Example rules can be found here to get you started: CubMakr Detection Examples
Hopefully somebody will find this useful. If you do, please feel free to comment.