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

John McFadyens Windows Installer Blog

John McFadyen

April 11

Useful Custom Actions

I have been using these for sometime and only recently realised how often that was.
 
I thought they may be useful to some of you out there.
 
A simple little routine to write to the MSI log.
 
function WriteToMsiLog(strMessage)
   Const msiMessageTypeInfo = &H04000000
   Set objMessage = session.Installer.CreateRecord(1)
   objMessage.StringData(0) = "Log: [1]"
   objMessage.StringData(1) = strMessage
   if session.property("CUSTDEBUG") <> "" then msgbox "MESSAGE: " & strMessage
   Session.Message msiMessageTypeInfo, objMessage
end function
 
This is a nice easy way to get some output to your logs. As most of you know I spend more time in the repackaging area as opposed to SDLC solutions as such VBS CA's are more prominent in my current work.
 
Note: If you pass CUSTDEBUG property during installation you will get debugging feedback messages which are useful during testing of new vbs CustomActions.
 
How to Format Records such as [#FileKey], [$ComponentKey]
 
function FormatRecord(strRecordName)
  on error resume next
  set objInstaller = session.installer
  set objRecord = objInstaller.CreateRecord(1)
  objRecord.ClearData
  objRecord.StringData(0) = strRecordName
  strFormattedRecord = Session.FormatRecord(objRecord)
  WriteToMsiLog "MESSAGE: Formatting record key " & strRecordName & vbcr & "Result = " & strFormattedRecord
  FormatRecord = strFormattedRecord
  set objRecord = nothing
end function
 
This is a handy way to resolve those MSI variables. For those of you setting perms with subinacls or other third party tools (i.e. not the lock permissions table) then you you may well need to use this to ensure your packages stay dynamic. This is particularly common issue amongst the repackagers as path locations are more often than not a static path is acceptable. However in the unlikely chance someone changes and installation path the repackagers would fall victim to failed CA's as paths in the CA's would not exist.
 
Using a Formatted record and passing that Record value to the deferred phase via CustomActionData would ensure that your packages maintain support for dynamic installation (i.e. variable installation directories)
 
 
 
 
 
 
 
 
 
 
 
December 18

CustomActionData

This topic is one which is a little overdue, I started something and never finished it. The last couple of posts were a lead up to this.
 
I discussed a little about the client / server side of the Windows Installer service and the related security concepts around the Immediate / Deferred phases. I bring this up as time and time again I see Installers which blatently ignore these issues. I see packages which don't take into account the security concepts of the Installer service. More often than not this is due to a complete lack of clear and readable information about the topic.
 
I am going to attempt to resolve that issue here and now but bear with me there is alot happening and it can be a little difficult to grasp.
 
One of the key points I mentioned in the previous post was that you should only modify the system using a Deferred Custom Action. This is what I like to teach as the golden rule of packaging. If your going to write registry, edit copy delete files you need to do this during the deferred phase. Because this is the only time you have access to the elevated server side portion of the Windows Installer service. This is highly important if you even intend to deploy your packages to a locked down environment or a Windows Vista or later OS.
 
Lets run through a few examples of good and bad configuration.
 
1) Editing an XML that exists prior to installation.
 
Lets assume we already have a file installed on a machine such as:
 
c:\program files\testapplication\myXmlFile.xml
 
We now want to edit this xml file and for the sake of this demonstration we will have a simple vb script to do this job. (yes there are better ways but I want to keep this simple for the demonstration).
 
Our VB script my be something very simple such are editing an attribute in the xml such as a server name.
 
set objNode = objXmlDocument.SelectSingleNode("//Environment/ServerDetails")
set objAttribute = objNode.SetAttribute("ServerName", session.property("ServerName"))
 
We pop in a Windows Installer property of [ServerName]
 
Now as this file already exists on the machine some people may argue an Immediate CustomAction would suffice to edit this file. Interestingly enough this run as an immediate CustomAction may work in many cases. Here's why.
 
a) the file exists already so no issues with the file not being present during installation
b) vb script is ok to edit the file
c) ServerName property is present during immediate phase
 
Ok so looking at this chances are it could work ok. Running a test installation potentially works as well. So whats the issue you ask ?
 
Ok now lets throw a little complexity into this cycle. Now try to install this on a user account with limited access, in particular no access to edit c:\program files\*. We run this same successful installation under a locked down user account the result is. FAIL
 
The locked down user no longer has access to edit the file and as such security permissions on the machine deny access to the file causing the CustomAction to fail. Initiating a rollback of the installation and resulting in a failed installation.
 
So what do we do now ?
 
Ok lets try the same scenario in deferred phase using the elevated CustomAction.
 
a) the file exists already so no issues with the file not being present during installation
b) vb script is ok to edit the file
c) user has access to file as running in local system elevated context
d) ServerName property is not present during deferred phase
 
The result being the file is edited successfully dropping a blank value into the ServerName attribute of the xml.
 
So whats the go now ? Neither solutions work ?
 
Immediate phase fails due to access rights.
Deferred phase fails due to properties not being available.
 
So now we are in a catch 22 situation, how do we work around this. Remember earlier I stated the golden rule as editing the system must be done during the deferred phase ? Was I wrong about this or is there something missing from the equation ?
 
The solution is a concept referred to as CustomActionData. CustomActionData is a special property used to retain property values within the deferred phase. The idea being a simple throw and catch scenario. Where the data you need to use from the Immediate phase is thrown across into the Deferred phase. Initially this process seems a little complicated but once you get your head around it like everything it becomes very simple.
 
The requirements are as follows.
 
1) need to edit the system only during the deferred phase
2) need properties which are only available during the immediate phase but run during the deferred phase.
 
So how do we achieve this ???
 
The process is like this.
 
1) Collect  properties required during the immediate phase
2) Throw those properties across into the deferred phase
3) Catch the properties in the deferred phase
4) Run our CustomAction to edit XML using the catch results from previous steps
 
Seems pretty simple when you put it like that huh.
 
Technical Implementation
 
1 & 2)  Use a Set PROPERTY CustomAction to set a property that will be access during the deferred phase.
 
For example
 
Create a PROPERTY called SERVERNAME with a value of TEST
Set Property called DEFERREDVALUE = [SERVERNAME]
 
The above CA would create a new Property called DEFERREDVALUE with a value of [SERVERNAME] which has a value of TEST. The net result being
 
DEFERREDVALUE=TEST
 
Now we have completed the first portion of throwing values across into the deferred phase so how do we catch those values on the other side. The answer is.
 
We now need to access the CustomActionData property. This is a special property which gains its value from the name of the CustomAction in the deferred phase.
 
To access the CustomActionData property we do this.
 
strServerName = session.property("CustomActionData")
msgbox strServerName 
 
This took me a little while to come to grips with as the documentation in the SDK is pretty light. your probably wondering at this point how does CustomActionData contain the correct property. There are plenty of properties in default packages so how does CustomActionData access the correct one. The trick to this is the name of your CustomAction.
 
If the customAction we setup in the Deferred phase is called
 
DEFERREDVALUE then the CustomActionData property will contain the value of the property DEFERREDVALUE which in this case = TEST.
 
If the name of the CustomAction is called SERVERNAME then the CustomActionData property will = the value of SERVERNAME.
 
I know this seems like a multitude of additional steps when one could argue that an immediate CustomAction after InstallFinalize would suffice. But this is the only way to obtain access to the elevated context of the Windows Installer Server process. This in turn means it is the only way to successfully deploy to a locked down environment.
 
So to cut a long story short if your currently writing CA's to edit the system during the immediate phase you need to change your practice and implement the CustomActionData solution.
 
Yeah sure its alot more difficult, it involves a few more steps and is a little fiddly but the results are your installation should work in any state of lock down.
 
This is quite a bit to chew over so have a read let me know if this makes any sense at all and I will follow up with some real examples of how this can be implemented. (I dont have an editor with me atm to create some samples for you).
 
Please give me some feedback on this one as its hard to know if I covered it off clearly its a pretty muddy topic to cover without a whiteboard.
 
 
 
 
 
 
 
October 14

Sequencing PART II

Hi all, its been a while since my last post as I have been pretty busy with RL lately. A few people have been asking why I haven't been posting. Although there are many reasons I won't bore you with the details.

Anyway this latest topic is something that so many people misunderstand, misuse, or simply don't care about. Which in my mind is all the more reason why I should write about it.

This is heavily related to my first post on installation sequences then you should probably have a quick look at that first. I left out some important bits of information I just assumed everyone knew. Interestingly enough the more packagers I have spoken to lately the more I realise the complete lack of understanding that is out there. Even some of the major players and training institutions are getting this wrong but I won't go into who, where and why you can work that out for yourselves. (I hope I don't goof the explanation and do the same).

I have updated the last diagram I drew to try to clarify these next explanations.

     InstallationSequences

So from the diagram above its a little difficult to see how this relates back to the tables. So bare with me while I try to explain it all. As we already know about standard installation conditions there are two tables which are processed. These tables are InstallUISequence and InstallExecuteSequence table. From the diagram above the green sections represented as the Acquisition phase consist of those two tables. During the processing of these two tables the installer service is Acquiring information about what actions need to be performed during installation. (hence the name).

The execution phase on the right shown in the purple has two sub-phases which are known as Deferred and Commit/Rollback. This is explained in more detail in my previous blog Installation Sequences. What I am going to cover in this blog is the smaller items at the bottom of this picture. These represent the actual Windows Installer Service and how things are processed during the Acquisition and Execution Phases.

There are two processes CLIENT and SERVER. The CLIENT process is protected and has limited access to the Windows Installer database (MSI) and the underlying Operating System. The CLIENT portion also runs programs which are only in memory for the duration of the task and then they are terminated.

The SERVER process usually consists of processes which don't terminate, one of the most important of those processes is the actual Windows Installer Service. The SERVER process needs full access to the Operating System to allow installation of the application and its resources. For this reason a portion of the installation needs to be elevated for the duration of the requirements needed to install the application. In order to optimise security and limit the time the installation has full rights the Execution phase was born.

I often hear many people say why does this need to be so difficult, the answer to that is that to successfully install on the myriad of platforms and user rights coupled with locked down environments the sequencing need to be complex to accommodate all of these requirements whilst maintaining optimum security.

So lets run through an installation again this time taking notice of the processes and how this affects the installation.

So lets assume we double click on an MSI running the standard installation. A CLIENT and SERVER process is launched. We first start to process the actions in the InstallUISequence, those actions are passed through to the CLIENT process. This means that during UI phases we have limited access to the underlying operating system. During this CLIENT side processing we do not have full access to the windows installer database.

We then start to process the InstallExecuteSequence during this part of the acquisition phase we have access to both the CLIENT and SERVER processes (this can be verified through check an installer log and looking for the (c) and (s) entries in the log). As such we have both read / write access to Windows Installer MSI however we still only have limited access to the underlying operating system. So according to my diagram above the yellow section has both access to the pink and purple section in the upper row of the process section of that diagram.

When we reach the execution phase (also commonly referred to as Deferred) a very important thing happens. Two additional processes are launched which are run in User / System context (or service account context). During the deferred phase actions were written to the installation script by the acquisition phase are then passed to one of the two newly created services. Because these two processes are launched here this is how we gain full access and elevation to the underlying operating system.

Now the most important part to understand here is this.

  1. We are now disconnected from the MSI and running as Either user or system.
  2. We now have full access to the underlying operating system

This is a very very important and often overlooked distinction. Because the two additional processes are launched at the start of the deferred phase it is the only location where we have true elevation capabilities and as such it is also the reason during this phase is the only place we have the capability to install an application on a locked down environment. It is also the reason why you cannot access the session object during the deferred phase.

This is why you hear so many people say when you modify the system you must use a deferred CA. Because the deferred phase is the only phase that has access to the Windows Installer Service accounts elevated context.

To summarise all of this

  1. InstallUISequence runs in CLIENT context
  2. InstallExecuteSequence runs in both CLIENT and SERVER context
  3. Deferred runs as USER or LOCAL SYSTEM
  4. Deferred is the only area which has FULL elevation context
  5. Immediate Custom actions have access to the session object
  6. Deferred Custom actions run a script which is disconnected from MSI meaning we lose access to the session.
  7. This is complex because it needs to cater for all scenarios whilst maintaining least privilege and maximum security

I was going to cover Custom Actions and there placement within these sequences but I think I will leave that for the next post as its getting late where I am.

Please feel free to ask any questions you have on this or post comments as this will inspire me to do this more often.

July 01

Advanced Windows Installer Training

So this is a little test to see just how much interest there is out there in pure Windows Installer training, I got virtually no feedback previous so either nobody read this or there is simply not an interest or requirement in the community. I have decided to run some FREE training courses. Yeah you heard me FREE introductory training. What's the catch you ask ?

Initially these classes will be held in Brisbane, however I am willing to travel to different states in the future should this produce enough level of interest. If you want to travel you can do so at your cost for travel / accommodation.

I'm guessing your sitting here saying watch the catch ? Ok well here it is.

  1. Training will need to be arranged by management from your company, applications will not be accepted from engineers.
  2. Training will be free for only 1 person per company.
  3. Training will not be supplied for application packaging companies.
  4. Training will not be supplied to persons whom have already attended one of my courses.
  5. A minimum of 8 people will be required before a course will commence.
  6. Course will be 3 days from 9am till 4pm.
  7. Schedules will be dictated based on interest level, if not enough people see benefit no courses will run.

Course Syllabus (Time permitting dependant on class skill levels)

    • Resilience
      • Features
      • Components
      • Keypaths
      • Entry points
      • Feature Level Healing
      • Component Level Healing
      • Installer caching
    • User Profile Fix up
      • HKCU / Profile related repair scenario's
      • Active Setup
      • Offline healing
    • Installation Methods / Processes
      • Admin Installs
      • Advertised Installs
      • Standard Installs
      • Installation Tables (InstallExecuteSequence, InstallUISequence etc)
        • UISequence
        • Acquisition Phase
        • Immediate
        • Execution Phase
        • Deferred
        • Commit / Rollback
      • Client / Server Processes
      • User Impersonation
      • System Process
      • Session Object
    • Custom Actions
      • When to use specific custom actions
      • Custom action rules
      • Using the session object
      • Using the session during deferred
      • CustomActionData during deferred
    • Validation ICE errors
      • Common validation errors
      • How to interpret validation errors
      • How to fix validation errors
    • Installer Logging
      • How to enable windows installer logging
      • How to interpret Windows Installer logging
    • Component Rules / Reference Counting Scenarios
      • Understanding component rules
      • Conflict management principals
      • Transitive components
      • Companion files
      • Shared dll reference count
      • Component reference count

This is intended for advanced users and it won't be light on technical information, it is intended to supply information on using Windows Installer and not targeted at selling a packaging product or even using a specific tool.

To register your interest drop me an email at john.mcfadyen@gmail.com use a subject of "FREE training". Hope to hear from you soon.

Windows Installer Upgrade process

So I finally got Internet connected at a record speed of 8 weeks (go iiNet, nothing like a bit of free publicity from another happy customer). Now I'm doing this one due to the massive number of requests we get on this through the multiple forums I frequent.

Windows Installer supports application upgrades. An upgrade is essentially the removal of an older product which is replaced by a newer product. The upgrade solution has two different upgrade methods.

Method 1:

  • Initiate installation of new product
  • Detect and Complete removal of an older product
  • Completion of installation for new product

This method is considerably easier and requires less knowledge of Windows Installer to achieve a successful result. Most application repackager's tend to use this method.

Method 2:

  • Initiate installation of new product
  • Check matching component codes, installation of new non matching component from new product.
  • Uninstallation of non matching components leaving matched components on the machine.

Method 2 is considered to be the better process as it improves installation speeds due as only small portions of an application need to be installed and uninstalled. This process is however considerably more complex to build into a package and requires in depth knowledge of Windows Installer to complete successfully. Its pretty common place for people to attempt method 2 first as this is generally how the default schemas for most packaging tools are setup.

This can easily be summarised in a quick table.

Description Pros Cons
Method 1
  • Simple to implement
  • Increased disk / network throughput required.
Method 2
  • Speed
  • Less intrusive to target platform as less is removed.
  • Potential to remove newly installed application during Uninstallation of older application.
  • Difficult to implement
  • Required conflict management / upgrade sync processes to be performed.

The different process types are controlled by two Windows Installer Standard actions. These are:

FindRelatedProducts

RemoveExistingProducts

The FindRelatedProducts uses upgrade codes and application versions to identify windows installer applications and then determines what should be done with the applications it finds. The action to be performed when a product is detected is determined by the value of the attribute column of the respective upgrade table row. In addition to this the FindRelatedProducts action adds the ProductCode of found application to the Property specified in the ActionProperty column of the upgrade table.

The RemoveExistingProducts action does the actual removal of products found by the FindRelatedProducts action. The list of features to be removed is determined from the Remove column of the upgrade table. An blank entry or a value of "ALL" deems the entire application feature set should be removed. The location of the FindRelatedProducts and RemoveExistingProducts actions in relation to each other viewed from the Installation sequences determines which of the above methods of upgrade are performed.

Method 1 action locations:

FindRelatedProducts

RemoveExistingProducts

All actions making up installation of new product

Method 2 action locations:

FindRelatedProducts

All actions making up installation of new product

RemoveExistingProducts

Note: Method 2 is considerably more complex and it is not recommended for inexperienced packagers. It requires an in depth knowledge of application sociability, conflict management, component rules, installation sequencing.

I will cover component rules in another post, for now to cut a rather long story short you need to match component GUID's on application which are intended to be upgraded with method 2 (that is if you want it to work properly). For those of you using Wise Package Studio there is a tool named UpgradeSync which is designed to aid with configuration of this type of upgrade method. To throw a little more complexity in the mix (such an unusual thing for Windows Installer) the MigrateFeatureStates action can be used to determine whether features should or shouldn't be removed, but wait there is more you didn't seriously think it was over there did you. There is another property called PreSelected which toggles whether the MigrateFeatureStates action runs.

There are 3 types of upgrade which are Small, Minor and Major updates. All updates require changing of the Package Code.

A small update is typically a small number of files, a small update cannot rearrange the feature component structure.

A minor update can add files and features however cannot manipulate the current feature/component structure. A minor update also requires the ProductVersion property to be changed. Minor updates can be shipped as full product installers or MSP patch files.

A major update can add files / features and also manipulate the feature component tree. The major upgrade requires that new ProductCode, PackageCode, and ProductVersion's are set.

The following table summarises the changes required for each update type.

Update Type Package Code ProductVersion ProductCode
Small x    
Minor x x  
Major x x x
       

June 06

Talking about Windows Installer Training

 

Quote

Windows Installer Training
So at the moment I am trying to convince some of the big vendors that their is a market for Windows Installer training as opposed to just tool based training.
 
So I have had a look at training offered by the two main players and I have to say I think its a little inadequate personally. What i see is a recurring theme of use our product. Here is how you use our product.
 
I use this analogy to compare it.
 
If you had a race car and wanted to get it tuned would you take it to a race engineer or a backyard mechanic that has proven his worth with a spanner.
My bet is the person who is trained to understand race engines and knows how to use the tools is better than a person whom is only familiar with using a set of tools.
 
Windows Installer is much the same, you have an engine and a bunch of tools that can manipulate that engine.
 
So where I am heading with this is should we push the vendors to create some more training ? Failing that perhaps I will do it myself. I see a market for this opportunity the only person I am aware of doing this is Darwin Sanoy no offence intended to Darwin but he cant be all over the world at once.
 
So if you agree there is room for better training, actually understanding the engine as opposed to the tools drop me some comments so I can take it to the big players ! I don't care if they are good or bad will open my eyes if no one elses.
 
The more ammunition I get the better chance we have of making this happen !
 
 
June 05

Windows Installer 4.5 released

fyi WI 4.5 is release.

 

 

I am pleased to announce that the final release of the Windows Installer 4.5 Redistributable and SDK are now available. There is also a KB Article published about the release.

 

New and improved features in Windows Installer 4.5

The following new and improved features have been implemented in Windows Installer 4.5.

 

Multiple package transaction

In a multiple package transaction, you can create a single transaction from multiple packages. In a multiple package transaction, a chainer is used to dynamically include packages in the transaction. If one or more of the packages do not install as expected, you can roll back the installation.

 

Embedded UI handler

You can embed a custom user interface (UI) handler in the Windows Installer package. This makes a custom UI easier to integrate. You can also invoke an embedded UI handler from the Add or Remove Programs item in Control Panel. Or, you can invoke an embedded UI handler during a Windows Installer repair process.

 

Embedded chainer

You can use the embedded chainer to add packages to a multiple package transaction. You can use an embedded chainer to enable installation events across multiple packages. For example, you can enable install-on-demand events, repair events, and uninstall events across multiple packages.

 

Update supersedence resiliency

This feature lets you correct for changes in the FeatureComponent table during supersedence.

 

Shared component patching resiliency during uninstall

This feature makes sure that the most recent version of a component is available to all products.

 

Custom action execution on update uninstall

This feature lets an update add or change a custom action so that the custom action is called when an update is uninstalled.

 

If you have any questions about the 4.5 release, please see our MSDN Documentation or other topics posted on this blog about 4.5. Additionally, we will be monitoring and responding to the comments on this post.

 

Thanks to everyone who helped us throughout the beta program of this release!

 

[Author: Tyler Robinson]
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.


View article...

June 04

Introduction to WiX

Hi all,

I know a bunch of people are waiting to get into this topic, so lets see how I go with the explanations. I haven't previously trained anyone with this at all so putting it into blog form could be somewhat interesting or confusing. There is a bunch of pre-requisite windows installer knowledge I will assume as well, so if I start to fast or too slow let me know. (I will try to cover this along the way if your new to Windows Installer some of my earlier blogs have covered that already)

So as you are all likely aware the WiX toolset is basically an MSI to XML and XML to MSI compiler / decompiler for want of better words. For those of you who can string more code together than I can its probably a very very useful tool to get your head around. The WiX toolset is more suited to people in development roles or closely associated to software development in some way or another.

One of the main benefits is it allows a great repeatable way to generate MSI's. If your in a development cycle that needs quick releases of new product source code this is an excellent way to achieve that as a result. My current role is doing just that compiling source code which is written all over the country into an MSI quickly and automated. Everyday I pull around a 1,000,000 lines of code together and compile it into assemblies then MSI's and have it deployed to large distributed server clusters inclusive of application configuration for multiple environments in a little over an hour.

There is a number of different technologies at work to make all this happen and WiX is a fairly substantial part of that process. So I will attempt to explain how you can utilise WiX to do similar compilation of large projects in a series of articles.

So enough of the pre amble lets get into WiX.

To start of there is a number of tools included in the WiX toolset. In order to start off with basic WiX we are only going to need to of them. These are:

Candle.exe and Light.exe (for some reason Rob Mensching had a fascination with lights of some sort when designing WiX most of the tools are names resembling light related names or objects)

Anyway the candle tool allows a pre compilation step which converts a WiX based file into a formatted wixobj file.

Light is then used to compiled the pre compiled file into an MSI file combining it with the source code to create your completed MSI.

In order for the two compilers to run you need to compile a WiX source file. WiX files come in a number of different file types, however for the most part I will be discussing wxs files.

So the wxs file format is simply and XML representation of your MSI database structure.

So before I dig too much into the internal formatting of the file I will outline the rough concept of the process.

  1. Create Source code
  2. Store source code in source repository
  3. Extract latest source code from repository to known area
  4. Generate a template wxs file referencing source code from 1 & 2 now located at 3
  5. Compile the wxs file using Candle.exe into a wixobj file
  6. Compile the wixobj file into an msi using Light.exe
  7. Collate compiled MSI and distribute to test servers

WXS File structure

All WiX files must start with this header as a bare minimum.

<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2003/01/wi'>
   . .
</Wix>

Before we move onto the next step you need to be aware of the following Windows Installer terms.

    • 32 bit hex GUID {00000000-0000-0000-0000-000000000000} or Globally Unique Identifier
    • ProductCode a product code acts like a serial number for an MSI. The product code is written as a 32 bit hex GUID
    • PackageCode is also a 32 bit hex GUID however its purposes is to identify Unique iterations of a specific product. Each new iteration should have a new package code.
    • UpgradeCode is also a 32 big hex GUID however its purpose is to identify a family of common applications. For example multiple versions of Adobe Acrobat Reader should all have the same upgrade code for family identification purposes

Each Windows Installer application must have some specific Product based meta data stored within the <Product> Node, as such the corresponding WiX scripts should also have those same attributes. The minimum attribute set and their respective WiX attributes are:

MSI Attribute (Property) Wix Attribute Description
ProductCode Id 32 bit hex GUID (or serial number for the product)
ProductName Name A descriptive name for the application
ProductVersion Version A version number
ProductLanguage Language A numeric language code i.e. 1033
Manufacturer Manufacturer A descriptive manufacturer name
UpgradeCode UpgradeCode 32 bit hex GUID for the family of related applications
  CodePage A 4 digit code page reference

Note: Specifying {????????-????-????-????-???????????} forces WiX to generate a new GUID each compilation, ALL GUID's should be in upper case.

Putting this all together you should have something like this.

<?xml version="1.0" encoding="UTF-8" ?>

- <Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">

<Product Id="{1BF1ACD8-7C1B-39BF-21E6-BB1C95FD2138}"

  Name="Minimal Windows Installer Sample"

  Version="1.0.0"

  Language="1033"

  Manufacturer="Installpac Pty Ltd"

  UpgradeCode="{B2F9543C-3C4D-12B3-A857-62E64F6FDEB0}"

  Codepage="1252">

</Product>

</Wix>

As with all Windows Installer packages you also need a package code. This is represented by the <Package> element. The package element also has a number of attributes which are required which are shown below.

         <Package

            Id="????????-????-????-????-????????????"

            InstallerVersion="200"

            Platforms="Intel"

            Languages="1033"

            Compressed="yes"

            SummaryCodepage="1252"

            Description="My Application"

            Comments="Hi there"

            Manufacturer="Installac Pty Ltd"

          />

Note: Using "????????-????-????-????-????????????" as the package code creates a new generated GUID each time the compiler runs. This ensures you are running with the rule of creating a new package code for each iteration of your package.

You need to insert the package element into the product element and then you are ready for your first compilation. Save the file as something like testproduct.wxs.

To test your compilation now you to run the first of the two compilers.

candle.exe <folder path to>testproduct.wxs

This will create a wixobj file which can then be used by the wix linker (light.exe)

light.exe <folder path to>testproduct.wxs

The result of this should be your first WiX msi.

June 02

General update

hi all just a quick note to let you know about the silence.
 
I have written a bunch of new content, but currently in the middle of moving house so im stuck without internet (which is very painful)
 
I don't feel right doing this at work so your going to have to bare with me for a bit. Should be online in a week or so. At which time I will be flooding the site with more info.
 
 
May 07

Understanding the Windows Installer Logs

When people are beginning to use Windows Installer the first look at the logs is often so daunting they tend to never look at them again. I find that the most common reason people avoid the logs is they don't understand the sequencing. Funnily enough the sequences and the logs tend to correspond with each other (I'm not sure why that is)

Anyway if you take Windows Installer and break it down you would know that the Installer basically consists of a service and a database that processes table's then sequences and actions. The installer service records everything that happens in sequential logic. The end result is a transactional record of the actions that are run during the installation. The beauty of this is as its transactional its quite simple to apply the reverse logic to handle the uninstall.

Now the trusty old log files record this process in its entirety, basically what I am getting at here is if you take the time to actually read the logs you would also understand the sequencing logic as well because they are one in the same. Alternatively if you work out the sequences you also workout the logs.

So first things first with the logs. How do we enable Windows Installer logging ?

Before we enable logging there is a number of available logging options which you need to know, there are:

Logging Option Option Description
v Verbose output
o Out of disk space messages
i status messages
c Initial UI Parameters
e All error messages
w Non fatal warnings
a start up of actions
r action specific records
m Out of memory or fatal exit information
u user requests
p terminal properties
+ Append to existing file
! Flush each line to the log
x Extra logging (Version 3.0 up, Windows 2003 + OS's)

Note: Each letter corresponds to a different setting. Adding a collection of all letters increases logging capabilities.

Logging via Registry

System Key: [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Installer]
Value Name: Logging
Data Type: REG_SZ (String Value)

image

Note: A common acronym of VOICEWARMUP is used as its a word that contains all available settings (prior to V3.0)

Debugging via Registry

System Key: [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\Installer]
Value Name: Debug
Data Type: REG_DWORD

image

By enabling the debug key you can monitor the installation using debugview

Install script logging

System Key: [HKEY_CURRENT_USER\Software\InstallShield\ISWI\3.0\SetupExeLog]
Value Name: VerboseLogFileName
Data Type: REG_SZ

Group Policy Logging

To enable logging from active directory or GPO you can find the settings here.

image

Windows Installer 4.0 +

So then we got Windows Installer 4.0 from which came two new Windows Installer properties.

MSILogging property hosts the same values listed in the above table.

MSILogFileLocation property has the path to where the logs should be created.

Command Line Logging

So last but not least command line logging, probably the one you will most often use.

msiexec.exe /i <path to msi> /l*v <path to logfile>

msiexec.exe /i <path to msi> /l*vx <path to logfile> (version 3.0 on Windows 2003 + OS's)

Reading the logs

Ok so now you have the logs how do we interpret the garbage that it generates. Well for those of you who always like to take the easy road. Get your hands on this little utility WILOGUTL.EXE it makes pretty it all a little more legible and takes the guess work out of things.

However for all of you hard core propeller heads here's how to really understand the seemingly difficult mess that arrive after setting some of the above options to log.

As you are aware the Installer Service on WindowsNT based platforms offers a client and server based process. The first section depicted in the following log excerpt in blue identifies the process is running client or server side.

MSI (s) (DC:D8) [22:18:04:544]: MainEngineThread is returning 1603
MSI (c) (34:30) [22:18:05:544]: Back from server. Return value: 1603

MSI (S) is server side

MSI (C) is client side

MSI (N) is a nested action

The next items depicted above in the red text is the ProcessID:ThreadID that generated the entry. Only the last 2 (hexadecimal) digits of the process and thread IDs are given. If the process ID was 2DC and thread ID 2D8 the Hex item show in the log would be (DC:D8).

The green section following is the date time stamp the action occurred.

Product State

The ProductState property can be any one of these values

Value Description
-1 The product is neither advertised nor installed.
1 The product is advertised but not installed
2 The product is installed for a different user
5 The product is installed for the current user

Feature Component State

So every feature and component is checked for state prior to it being marked as something that needs to be installed. So selecting different features "usually" changes the feature state and subsequently all the components within that feature (of course there are exceptions to every rule, the condition table and feature / component conditions all have effect here also.).

The first item here, show the feature name as Complete and it is currently Absent. So the requested action is to install it Local. So the components follow the same pattern. This one is a little tricky so more detail here

 

Value Value:        Description
Installed Local        
Source     
Advertise  
Absent      
already installed to run local
already installed to run from source
already installed as advertised
not installed
Request Local
Source     
Advertise  
Reinstall   
Absent     
requests to install the item to run local
requests to install the item to run from source
requests to advertise the item
requests to reinstall the item
should not be installed
Action Local        
Source     
Reinstall   
Absent     
Null         
actually performs install to run local
actually performs install to run from source
actually reinstalls the item
actually removes the item
do nothing

MSI (s) (94:28) [20:46:12:390]: Feature: Complete; Absent: Local;   Request: Local;   Action: Local
MSI (s) (94:28) [20:46:12:390]: Component: Registry; Installed: Absent;   Request: Local;   Action: Local
MSI (s) (94:28) [20:46:12:390]: Component: VersionRegistry; Installed: Local;   Request: Local;   Action: Local
MSI (s) (94:28) [20:46:12:390]: Component: slupExecutable; Installed: Absent;   Request: Local;   Action: Local
MSI (s) (94:28) [20:46:12:390]: Component: CoreLibrary; Installed: Absent;   Request: Local;   Action: Local
MSI (s) (94:28) [20:46:12:390]: Component: CtrlLibrary; Installed: Absent;   Request: Local;   Action: Local

Reading Actions and return codes

Action start 1:18:02: INSTALL.
MSI (c) (A1:6A): UI Sequence table 'InstallUISequence' is 
present and populated.
MSI (c) (A1:6A): Running UISequence
MSI (c) (A1:6A): Skipping action: ResumeInstall (condition is false)
MSI (c) (A1:6A): Doing action: CheckOSandSPAction
Action start 1:18:02: CheckOSandSPAction.
MSI (c) (A1:6A): Creating MSIHANDLE (1) of type 790542 for thread 110
Action ended 1:18:02: CheckOSandSPAction. Return value 1.
The valid return codes are: 
Value Description
0

Action not invoked; most likely does not exist.

1

Completed actions successfully.

2

User terminated prematurely.

3

Unrecoverable error occurred.

4

Sequence suspended, to be resumed later.

Shell32 API calls

Where you see one of these there is a good chance a directory is being resolved for the current target platform.

MSI (s) (94:28) [20:46:11:843]: SHELL32::SHGetFolderPath returned: C:\Documents and Settings\All Users\Templates

Old hands tips for log reading

1) Start at the bottom and work up

2) If your install fails with dialogs open. Leave dialogs on screen and goto log whilst error message is still visible

3) Use WiLogUtl.exe

4) Don't forget the newest extended logging options /l*vx (most of you are still using /l*v)

5) Keep the sequences in mind when reading a logfile (strangely enough they coincide nicely)

6) Enable GPO logging or policy registry option if you need to log repairs

7) Now you have this information take a 30 minutes out of your day and read an entire log you'll be surprised what you learn.

Now all that being said, I'm not getting a lot of feedback about these blogs so its hard to gauge if anyone is really reading or even interested in this stuff or not.

Post some comments if you want more info otherwise I may just find other things to do with my spare time.

May 05

Custom XML Dom Class

Technorati Tags: ,,,

So lately a bunch of people are asking a little more about XML and XML DOM. Sometime ago when I was trying to learn how XML dom worked I threw this vbs class together to assist in reading and writing xml via the DOM. For those of you whom are not aware of the dom its the XML Document Object Model.

Using the DOM you can traverse xml nodes read and manipulate data. Looking at this entirely from a packaging perspective its a great way to store and read package data into a package.

So the attached script emulates the functionality in the NetFX classes, something I was not aware of prior to writing this code. So here is a bunch of vbs function (or methods for the dev's out there). Using these methods you can add / get / set and delete xml data from an XML structure. There is even a simple method to format the white space for readability purposes.

There is a bunch of sample code at the top of the script to aid in how this should be used. If anyone is after some more specific requests in how they can pull data from xml drop a few comments and I will endeavour to answer your questions.

The code is pretty old but its a great starting point for those of you whom are interested in learning xml.

'============================================================================
' LANG:            VBScript
' NAME:            IpXMLClass.vbs
' AUTHOR:        John McFadyen (john.mcfadyen@gmail.com)
' VERSION:        0.1
' DATE:            2006-07-30
' Description:        Generate / edit / query an XML file
'
' UPDATES:        http://www.installpac.com/scripting
'
' For Options:        This script has no options and is expected to be used as a class.
' Feedback:        Please send feedback to john.mcfadyen@gmail.com
'
' Notes:
' This has has only been tested with MSXML
' LICENSE:
' Copyright (c) 2004-2006, John McFadyen
' All rights reserved.
'
' Redistribution and use in source and binary forms, with or without
' modification, are permitted provided that the following conditions are met:
'
'  * Redistributions of source code must retain the above copyright notice,
'    this list of conditions and the following disclaimer.
'  * Redistributions in binary form must reproduce the above copyright notice,
'    this list of conditions and the following disclaimer in the documentation
'    and/or other materials provided with the distribution.
'  * Neither the name Installpac or the names of its contributors may be used
'    to endorse or promote products derived from this software without
'    specific prior written permission.
'
' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
' IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
' ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
' LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
' CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
' SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
' INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
' CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
' ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
' POSSIBILITY OF SUCH DAMAGE.
'==========================================================
'==========================================================

' Example 1 Generating a new xml document
' =======================================
' set xmlDoc = new XMLDom
' xmlDoc.filenameXML = "Johnny.xml"
' call xmldoc.CreateElement("Root")
'    call xmldoc.CreateElement("License")
'        call xmldoc.CreateElementString("starttime", now())
'        call xmldoc.CreateElementString("starttime", now())
'        call xmldoc.CreateElementString("starttime", now())
'        call xmldoc.CreateElementString("starttime", now())
'        call xmldoc.CreateElement("Hello")
'        call xmldoc.CreateEndElement
'        call xmldoc.CreateElement("Hello1")
' call xmldoc.close
'
' Example 2 Updating a current xml
' ================================
' set xmlDoc = new XMLDom
' xmlDoc.filenameXML = "Y:\Projects\TestApplication_1-0_R01\TestApplication_1-0_R011New.XML"
' xmlDoc.ValidateXML "Y:\Projects\TestApplication_1-0_R01\TestApplication_1-0_R011.XML"
' xmlDoc.XMLLoader "Y:\Projects\TestApplication_1-0_R01\TestApplication_1-0_R011.XML"
' Note: The following line using an external object
' set objNode = xmlDoc.GetNode("//PACKAGESRC")
' xmlDoc.SetNodeValue "Test", objNode, false
' set objNodeLocation = xmlDoc.GetNode("//LOCATION")
' xmlDoc.CreateElementString "NewNode", "test"
' xmlDoc.close

' Example 3 Using an actual stylesheet
' =========
' call xmldoc1.CreateElement("Root")
' call xmldoc1.CreateXmlProcess("stylesheet.xsl")
'     call xmldoc1.CreateElement("License")
'    call xmldoc1.CreateElementString("starttime", now())
'     call xmldoc1.CreateElementString("starttime", now())
'    call xmldoc1.CreateElementString("starttime", now())
'     call xmldoc1.CreateElementString("starttime", now())
'     call xmldoc1.CreateAttribute("test", "value1")
'    call xmldoc1.CreateAttribute("test1", "value1")
'     call xmldoc1.CreateAttribute("test2", "value1")
' call xmldoc1.CreateEndElement
' call xmldoc1.close
'
'=========================================================================================================
'=========================================================================================================
' Script version

Dim strScriptVersion
strScriptVersion = "0.1"

'=========================================================================================================

class XMLDom

    dim xmlfilename                        'stores the xml filename
    dim xslfilename                        'stores the xsl filename
    dim xmldom                          'stores a DOM Object
    dim xmlroot                            'stores the Root Node
    dim xmlcurrentnode                    'stores the Current Node
    dim xmlparent                        'stores the Parent Node of current node
    dim xmlelements
    dim intIndentation                    'stores the indentspacing (not required with XSL sheet applied)
    dim blnWhiteSpace

'=========================================================================================================

    private Sub Class_Initialize
        dim xmldeclaration
        Set xmlDom = CreateObject("Microsoft.XMLDOM")
        xmlDom.preserveWhiteSpace = False
        xmlDom.loadxml "<?xml version='1.0'?>"
        'xmlDom.createProcessingInstruction("<?xml version='1.0'?>")
        set xmlcurrentnode = xmlDom
    End Sub

    private Sub Class_Terminate
        'xmldom.LoadXML TransformXML
        'if xmlFilename <> nothing then xmldom.save(xmlFilename)
    End Sub

'=========================================================================================================

    public Property let FilenameXML(strFilename)   
        xmlFilename = strFilename
    End Property

    public Property let FilenameXSL(strFilename)   
        xslFilename = strFilename
    End Property

    public Property let Indentspacing(intIndentSpacing)
        ' Not required now we are using stylesheets
    End Property

    public Property let Whitespace(blnWhiteSpace)
        sprpWhiteSpace = blnWhiteSpace
    End Property

    public Property get Whitespace()
         sprpWhiteSpace = prpWhiteSpace
    End Property

    public Property let EmptyAttribute(blnAllowEmpty)
        prpAllowEmpty = blnWhiteSpace
    End Property

'=========================================================================================================

    public function CreateRootElement(strRootNode)

        set xmlroot = xmldom.CreateElement(strRootNode)
        xmldom.appendchild xmlroot
        set xmlcurrentnode = xmlroot
        Set objProcessing = xmlDom.CreateProcessingInstruction("xml","version='1.0'")
        xmlDom.insertBefore objProcessing ,xmlroot
        'xmlroot.appendChild xmlDom.CreateTextNode(vbcrlf & string(indentLevel, Chr(9)))   

    end function

    sub CreateElement(strElementName)
        dim xmlNewNode
        set xmlNewNode = xmlDom.createElement(strElementName)
        xmlcurrentnode.appendchild xmlNewNode
        set xmlcurrentnode = xmlNewNode
        'xmldom.save xmlFilename
        'xmlcurrentnode.appendChild xmlDom.CreateTextNode(vbcrlf & string(indentLevel, Chr(9)))   
    end sub

    sub CreateElementString(strElementName, strElementValue)
        dim xmlNewNode
        set xmlNewNode = xmlDom.createElement(strElementName)
        if isnull(strElementValue) then strElementValue = ""
        xmlNewNode.text = strElementValue
        xmlcurrentnode.appendchild(xmlNewNode)
        'xmlcurrentnode.appendChild xmlDom.CreateTextNode(vbcrlf & string(indentLevel, Chr(9)))   
    end sub

    sub CreateElementStringNew(strElementName, strElementValue)
        dim xmlNewNode
        set xmlNewNode = xmlDom.createElement(strElementName)
        if isnull(strElementValue) then strElementValue = ""
        xmlNewNode.text = strElementValue
        xmlcurrentnode.appendchild(xmlNewNode)
        'xmlcurrentnode.appendChild xmlDom.CreateTextNode(vbcrlf & string(indentLevel, Chr(9)))   
    end sub

    sub CreateEndElement
        set xmlcurrentnode = xmlcurrentnode.parentnode
    end sub

    sub DeleteEndElement
        dim xmlEndNode
        set xmlEndNode = xmlcurrentnode
        set xmlcurrentnode = xmlcurrentnode.parentnode
        xmlcurrentnode.removechild(xmlEndNode)
    end sub

    Function RemoveElement(strNodename, objNode)
        Dim objRemoveElement    ' As MSXML2.IXMLDOMNode   
        If Not objNode Is Nothing Then
          Set objRemoveElement = objNode.SelectSingleNode(strNodename)   
          If Not objRemoveElement Is Nothing Then
            objNode.removeChild objRemoveElement
          End If
        End If
    End Function

    Public Function RemoveElements(sXPath, objNode)
        Dim objNodes            ' As MSXML2.IXMLDOMNodeList
        Dim intNodeCount        ' As Long
        Set objRemoveElements = objNode.selectNodes(sXPath)
        if not objRemoveElements is nothing then
          For intNodeCount = 0 To objRemoveElements.length - 1
            objRemoveElements(intNodeCount).parentNode.removeChild objRemoveElements(intNodeCount)
          Next
        end if
    End Function

    Public Function GetElements

        set xmlElements = xmlDom.DocumentElement
        set GetElements = xmlElements
    end function

'=========================================================================================================

    sub CreateAttribute(strAttrName,strAttrValue)
        if isnull(strAttrValue) then strAttrValue = ""
        xmlcurrentnode.setAttribute strAttrName, strAttrValue
    end sub

    Public Function GetAttribute(strAttrName, objNode)     ' as string
        Dim objAttr                                            ' As MSXML2.IXMLDOMNode
        GetAttribute = ""
        If Not objNode Is Nothing Then
          Set objAttr = objNode.Attributes.getNamedItem(strAttrName)
          If Not objAttr Is Nothing Then
            GetAttribute = CStr(objAttr.Text)
          End If
        End If
    End Function
    Public Function SetAttribute(strAttrName, strAttrValue, objNode)                     
        Dim objAttr      ' As MSXML2.IXMLDOMNode
        If Not objNode Is Nothing Then
          If strAttrValue <> "" or AllowEmptyAttr Then
            Set objAttr = objNode.Attributes.getNamedItem(strAttrName)
            If objAttr Is Nothing Then
              Set objAttr = CreateAttribute(strAttrName, strAttrValue, objNode)
            End If 
            objAttr.Text = strAttrValue
          Else
            RemoveAttribute strAttrName, objNode
          End If
        End If       
    End Function

    Function RemoveAttribute(strAttrName, objNode)
        If Not objNode Is Nothing Then
          objNode.Attributes.removeNamedItem strAttrName
        End If
    End Function 
'=========================================================================================================

    Public Function HasChildNodes()
        HasChildNodes = False
        if xmlCurrentNode.HasChildNodes then HasChildNodes = True

    end function

    Public Function NodeExists(strNodeName)
        Dim objNode
        msgbox "node test"
        NodeExists = false
'        if xmlCurrentNode.HasChildNodes then
        For each objNode in xmlCurrentNode.ChildNodes
                msgbox objNode.Nodename
                if objNode.Nodename = strNodename then
                    NodeExists = True
                    exit for
                end if
            next
'        end if
    end function

    Public Function GetChildNodes()

        'On Error Resume Next

        Dim arrNodes()
        redim preserve arrNodes(1,0)
        y = 0
        For each objNode in xmlCurrentNode.ChildNodes
            if y >= ubound(arrNodes,2) then redim preserve arrNodes(1,y)
            arrNodes(0,y) = objNode.Nodename
            arrNodes(1,y) = objNode.Text
            y = y + 1
        Next
        GetChildNodes = arrNodes

    End Function

    Public Function GetNode(sXpath)
        on error resume next
        'Note: requires GetElements to be run first
        '    : preffered to get to external object as well.
        '    : I.E. set objNode = GetNode("//Nodename")

        if not xmlelements then
            GetElements
        end if
        set xmlcurrentnode = xmlElements.selectSingleNode(sXPath)
        Set GetNode = xmlcurrentnode

    end function

    Public Function GetNodeValue(objNode, blnCData)    ' As String
        on error resume next
        if not xmlelements then
            GetElements
        end if

        set xmlcurrentnode = xmlElements.selectSingleNode(sXPath)

        If Not xmlcurrentnode Is Nothing Then
          If blnCData Then
            If xmlcurrentnode.childNodes.length > 0 Then
              GetNodeValue = CStr(xmlcurrentnode.childNodes(0).nodeTypedValue)
            End If
          Else
            GetNodeValue = CStr(xmlcurrentnode.Text)
          End If
        End If
    End Function

    Public Function SetNodeValue(strNodevalue, objNode, blnCData)    ' As String
        on error resume next
        If Not objNode Is Nothing Then
          If blnCData Then
            If objNode.childNodes.length > 0 Then
              objNode.childNodes(0).nodeTypedValue = sValue
            Else
              objNode.appendChild objNode.ownerDocument.createCDATASection(strNodevalue)
            End If
          Else
            objNode.Text = strNodevalue
          End If
        End If
    End Function
'=========================================================================================================

    sub CreateXmlProcess (strProcessInstruction)
        dim xmlProcessInstruction
        Set xmlProcessInstruction = xmlDom.createProcessingInstruction(strProcessInstruction)
        xmlDom.appendChild(xmlProcessInstruction)
    end sub

    sub CreateXSlProcess (strProcessInstruction)
        dim xmlProcessInstruction
        Set xmlProcessInstruction = xmlDom.createProcessingInstruction("xml-stylesheet", "type=""text/xsl"" href="""& strProcessInstruction & """")
        xmlDom.appendChild(xmlProcessInstruction)
    end sub

    Public Sub StartInsertAt(objParentNode)
        Set xmlcurrentnode = objParentNode
    End Sub

    function LoadXML(strxml)
        xmldom.loadXML(strxml)
    end function

    public function XMLLoader(strXML)
        xmldom.load(strXML)
    end function

    Public Function validateXML(strXML)

        Dim sReturn
        xmldom.async = 0
        xmldom.resolveExternals = 1
        xmldom.validateOnParse = 1
        If xmldom.load(strXML) Then
        Else
            If xmldom.parseError.errorCode <> 0 Then
                strReturn = xmldom.parseError.reason & vbcrlf & _
                    xmldom.parseError.line & vbcrlf & _
                    xmldom.parseError.linepos & vbcrlf & _
                    xmldom.parseError.srcText
            End If
        End IF
        ValidateXML = strReturn

    End Function

    Public Function transformXML1(strXML,strXSL)

        Dim xslDoc
        Set xslDoc = New MSXML2.DOMDocument40

        If xmlDom.parseError.errorCode = 0 Then
            xslDom.Load strXSL
            If xslDoc.parseError.reason = "" Then
                return = xmlDom.transformNode(xslDoc)
            Else
                return = "Error: Stylesheet.xsl did not load. " & _
                xslDoc.parseError.reason
            End If
        Else
            return = "Error: XML did not load. " & _
            xmlDoc.parseError.reason
        End If
        transformXML1 = return
        Set xslDoc = Nothing

    End Function

    function TransformXML
        dim objStylesheet
        set objStylesheet = CreateObject("Microsoft.XMLDOM")
        objStylesheet.async = False
        objStylesheet.loadXML ("<?xml version=""1.0"" encoding=""UTF-8""?>" & vbcrlf & _
                        "<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">" & vbcrlf & _
                        "<xsl:output method=""xml"" indent=""yes"" encoding=""UTF-8"" />" & vbcrlf & _
                        "<xsl:template match=""@* | node()"">" & vbcrlf & _
                        "   <xsl:copy>" & vbcrlf & _
                        "        <xsl:apply-templates select=""@* | node()"" />" & vbcrlf & _
                        "   </xsl:copy>" & vbcrlf & _
                        "</xsl:template>" & vbcrlf & _
                        "</xsl:stylesheet>")
        'objStyleSheet.Save "c:\test.xsl"
        TransformXML = xmlDOM.transformNode(objStylesheet)
    end function

'=========================================================================================================

    sub close()
        xmldom.LoadXML TransformXML
        xmldom.save(xmlfilename)
    end sub
end class

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..

March 31

Packed GUID's, Darwin Descriptors and Windows Installer Reference counting

Packed GUID's

All the Windows Installer registry keys and settings are held under these locations:

[HKEY_CLASSES_ROOT\Installer]
[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Installer]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer] a Per-User / Per-Machine installation affects the content that is written under these keys.

ALLUSER Value Install Type Registry Location
0 Per User HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-21-xxxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxx
1 Per Machine HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18
2 Per user or machine, depends on users rights of installing user HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-21-xxxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxx or HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18

Under these keys, you will find details of all the Windows Installer products and components that have been installed onto the workstation. These Product Codes and Component IDs are stored as packed GUIDS, the packing is done as it takes up less space and allows faster access. The curly braces and hyphens are discarded, saving 6 bytes per GUID, and then related back to original data in the following way:

Original Product Code:
{12345678-ABCD-WXYZ-1234-ABCDEFGHIJKL}

String manipulation steps:

Action Original GUID section Packed GUID Section
Reverse first 8 characters 12345678 87654321
Reverse next 4 characters  ABCD DCBA
Reverse next 4 characters WXYZ ZYXW
Reverse next 2 characters 12 21
Reverse next 2 characters 34 43
Reverse next 2 characters AB BA
Reverse next 2 characters CD DC
Reverse next 2 characters EF FE
Reverse next 2 characters GH HG
Reverse next 2 characters IJ JI
Reverse next 2 characters KL LK

Recombine characters without any curly braces or hyphens to see your packed GUID:
87654321DCBAZYXW2134BADCFEHGJILK

In summary:

To use Microsoft's term on the different "globally unique identifier (GUID)" the following would apply:
Uncompressed GUID (standard): {12345678-ABCD-WXYZ-1234-ABCDEFGHIJKL}
Compressed GUID: 87654321DCBAZYXW2134BADCFEHGJILK

Reference Counting

Often when I refer to reference counting I have noticed that a number of people confuse Windows Installer reference counting with Windows reference counting (more commonly referred to as Shared DLL reference counting by application packagers). So here make the distinction we are talking about "Windows Installer reference counts".  

Another common item I hear is that reference counting is a count of the number of times a component is installed, however I think it is more appropriate to refer to it as the number of products which have installed a particular component.

The information is written to the registry by the ProcessComponents action. Under the components subkey (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components) you will find a list of Per-Machine Windows Installer components. The components will be identified by another subkey which is the Compressed Guid of the respective component. (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\<CompressedComponentGuid>. Per-User entries are written to HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\<UserSID>\Components\<CompressedComponentGuid>).

Under each of these <CompressedComponentGuid> subkey's will be an undetermined list of REG_SZ registry strings. These REG_SZ strings are actually packed GUID's of the ProductCode of the application which installed the component. Such as:

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\<CompressedComponentGuid>\<CompressedProductGuid> = <ComponentKeypath>

For those of you who are interested the <ComponentKeyPath> entry is enumerated by a function call to MsiGetComponentPath.

Based on this installing two applications which share the same Component you will find something similar to the following in the registry.

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\<CompressedComponentGuidA>\<CompressedProductGuidA> = <ComponentKeypathA>

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\<CompressedComponentGuidA>\<CompressedProductGuidB> = <ComponentKeypathA>

If you find a REG_SZ string which is all 0's. Then the respective component is set not to be uninstalled during uninstall. This means that the component attribute contains a binary bitmask value of 16 or msidbComponentAttributesPermanent.

Some of you may at this point be asking so what does all this mean ? Well to make a very long story short it allows multiple applications to play nicely together particularly during uninstall. At some point I am sure many of you have tried to uninstall an older style application and been prompted with the infamous "This application is using shared DLL's do you want to uninstall these files" or you have uninstalled an application only to realise your machine is now completely non functional. Well reference counting is an attempt to make this a thing of the past. Enter Conflict Management (I will cover this in another blog)

Darwin Descriptors

I'm sure most of you have heard the tales not to repackage an MSI, well these little babies are one of the main reasons why NOT to repackage an MSI. "Darwin Descriptor" is actually an encrypted representation of a specific product, component, and feature. If these values exist, Windows Installer will decrypt the data, and use it to perform checks against the respective product and component. Refer to the previous blog on self healing for more detail about this action.

Darwin 

The problem encountered when capturing an MSI is this. When you install PRODUCT-A on your capture machine you write 'x' number of Darwin descriptors to the registry. Your snapshot tool will capture these darwin descriptors as registry information and incorporate them into PRODUCT-B. Remember these captured "darwin's" will have the PRODUCTCODE of PRODUCT-A encrypted into them.

As such if we install the captured PRODUCT-B onto a fresh machine you will still have "darwin's" pointing at PRODUCT-A which is no longer installed. It doesn't take a brain surgeon to work out the issue here.

If you want to get into the registry to see where all of this is stored go check out this location.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\<CompressedProductCode>\Features

Well that about wraps it up for another blog, as I am a little new to this if you guys see any room for improvement feel free to speak up. I may take the whole "darwin descriptor" feature even deeper in a future blog. 

March 30

Windows Installer Self healing

One of the main features of Microsoft's Windows Installer technology is self healing. Self healing leverages the windows installer database to allow a full or partial reinstallation of a managed product under certain conditions.

For example lets assume you install a custom MSI of Winzip, and then proceeded to delete the files that made up Winzip. You could then run an advertised Windows Installer shortcut which acts as a proxy back to the Installer database and while you watch Winzip would be reinstalled. This feature is known as self healing, not to be confused with Self repair.

So in order to understand self healing you need to get a grasp on a few basics of Windows Installer.

Windows Installer Term Description
Feature A feature acts in a similar way to a folder or container. Within a feature there are however limitations to what can be stored within it. A feature can contain other feature and components. It cannot directly contain files or registry etc.
Component

A component is also a container, which is uniquely identifiable by a standard 32-bit hex GUID. Components typically contain one or more of the following items:

Registry, File, ODBC,Service,Shortcut,INI files,Extensions

A component can have a number of different attributes associated to it which determine different behaviours during installation and more importantly reinstallation.

Primary Key The primary key of a relational table uniquely identifies each record in the table
Advertised Entry point

Portions of the application which, are used for triggering self healing checking routines. Entry points are commonly shortcuts however can also be file extensions and COM objects and binaries. Entry points are usually classified within the following MSI tables:

Shortcuts,Class,ProgId,Verb,Extensions,Typelib,Mime

Windows Installer cache Default behaviour of the windows installer service is to cache a copy of the installer database on the workstation at %windir%\installer. This copy is not always a full MSI it is just the content contained within the database schema. As such binary files are not included within the cache. This has significant impact on the behaviour of self healing.
SOURCELIST The SOURCELIST is an MSI public property which can be used to specify alternate application source files.
Feature Level healing An entire feature is marked for healing.
Component Level healing A single component is marked for healing.

The activation of the self healing process is triggered by the user accessing an advertised entry point when attempting to start an application. It is very important to understand that an application can consist of multiple entry points, as such it is vital to know the location of the entry point within the MSI feature structure. For example if there is 3 features in your application called FEATURE1 FEATURE2 and FEATURE3 if your entry point is located in FEATURE1 then self healing will start in FEATURE1. If your entry point is located in FEATURE3 then healing will start respectively in FEATURE3.

It is common with self healing to use the shortcut as an entry point, this is not a hard and fast rule, it simply stands to reason that the user is likely to run a shortcut to activate an application. Therefore a shortcut is a likely place to initiate the healing process. Shortcuts can be both advertised and non advertised. The target column of the shortcut table dictates whether a shortcut is advertised or not. If you wish to utilise self healing then the use of advertised shortcuts is advised.

Once an entry point has been accessed the installer services queries the entry point to find which feature it belongs to. Once obtained it then queries that feature for all of the components it contains. There are a number of important component attributes which are gathered during this process. These items are: Component Name, Component Install State, Component code and Component Keypath. Most of this information can be viewed via the Component Table of any Windows Installer MSI.

The Keypath being one of the more important attributes will reference one of the items contained within component (note a component can contain multiple items only one of which can be a keypath); The keypath will represent a file or registry key which should be present on the machine if the Component State is set to be installed. If Component state is set to be installed a check will be made on the target machine to determine if that file or registry key is present on the target. Where a keypath is not present the installer service initiates self repair in one of the two modes explained in the next paragraph.

There are two types of healing modes "Feature Level healing" and "Component Level healing". Feature level healing is performed when the broken keypath is located in the same feature as the entry point. A feature level heal will heal all items contained within a feature, this means that when a single component is broken in a feature which contains multiple components all components are healed regardless of whether they are broken or not whilst taking into consideration each components install state. A component level heal will only heal an individual component other components within that feature will not be affected.

Now something which always catches people out when this is explained is what happens after checking the first feature. This is probably the most misunderstood part of self healing. So bear with me, please make a distinction between checking and healing in the next paragraph. (added for you Bruce.)

If the feature containing the accessed entry point does not have any broken components identified by missing keypath references, then a check will be made for any parent features of that initial feature. If a parent feature is found the healing mode is switched from Feature level healing to Component level healing and process begins again on the new feature. If there are no parents then self healing stops the checking functionality at that point. (Note: Child or sibling features are NOT checked.)

Now if the entry point feature did have broken components identified by missing keypath references that feature would be healed at Feature level and the remaining MSI feature set would then be enumerated and subsequently checked. Where a feature did have a parent feature and that parent contained broken components all remaining features would be checked regardless of location within the MSI feature structure. (Note: this means all features are checked regardless of location when any broken component is found)

Another common misconception is when attempting to disable self healing within a component, as self healing is triggered when Keypath are missing assumptions are made to delete the keypath to stop self healing. In actual fact if you wish to stop self healing on a single component the correct method is to remove the components component GUID from the component table. (this will be explained in more detail in the section on packed GUID's)

It is also useful to note that when running add/remove programs and selecting the repair option a full heal is performed on the application in a similar manner to that of the initial installation.

Taking this a step further a feature commonly referred to as "user profile fixup" leverages the self healing functionality to repair items deployed into a users profile. An example being when you install applications logged in as USERA then log off and log back in as USERB items that were deployed to the user profile of USERA may not be accessible for USERB. By marking items within the users profile as a keypath each user will get the profile specific data deployed when launching the advertised shortcut (entry point) of the application through standard self healing techniques. I have written a post on how to exploit this functionality a few years back on http://www.myitforum.com labelled as:

Current User healing and Current User healing II.

Both documents are very old and need to be rewritten as they contain a few minor errors (I will endeavour to do this ASAP), however the technique is still very valid and widely practiced around the world.

Windows Installer Caching

Each time a Windows Installer MSI is installed a partial copy of the Windows Installer MSI is copied to the target machine usually in this location %windir%\installer. Binary files are included within the cache, however embedded cabinets are removed (from the _Streams table) before the package is cached. Generally they are smaller than the original source MSI.

When a broken application is found and self healing occurs a determination is made where the corresponding item will be repaired from. If it is just registry or ini file data then this can be done by the locally cached MSI. If the item is a file it will not be contained within the cache and as such an attempt will be made to access the original install media. I'm sure many of you have seen office prompting for a CD on a few occasions.

(Note: For this reason it is not considered smart to install an MSI from a CD, as attempts will be made to access that media for the remainder of the time the application is installed on the target)

As described above a way to counter this is to populate the SOURCELIST property prior to installation. A semi colon delimited list of servers can be added to the sourcelist to allow alternate heal points. Such as:

\\MySourceServer\Apps;\\%AppServer%Apps;x:\apps;\\DFS\Apps

If however you have made the mistake of deploying from CD and need to change a SOURCELIST after deployment. Darwin Sanoy of www.desktopengineer.com has written a script to change the sourcelist of pre Windows Installer 3.0 systems. When 3.0 is installed this can be used.

Dim objInstaller

Set objInstaller = CreateObject("WindowsInstaller.Installer")

objInstaller.AddSource "{00000ABC-ABE1-11D2-B60F-006097C998E7}", "", "\\MySourceServer\apps"

The next few topics of Darwin Descriptors and Packed Guids will cover this in even more detail.

First blog entry and introduction

Hi all,
 
Some of you may know me from my heavy support in the Wise forums http://www.wise.com/newsgroups.asp, some of you probably don't even care. Anyway bunch of people keep pestering me about starting a blog, some of them are just that "pests" but many of which I hold in very high regard. I have been messing with IT technology for 25 years or so. I have only taken it to a professional level for 10 of those years. I have a strong interest in training and enjoy helping some of the less fortunate people to get somewhere in life.
 
So at the risk of boring the absolute crap out of people here it is.. my first blog. Don't worry most of the content will be purely technical so you don't need to hear about who i went to dinner with last night or what I wil be cooking tomorrow. Oh and as many of my colleagues tell me so often my grammar isn't the best so bear with me on that. (Im talking about you Ben)
 
So there is a bunch of items which people have asked me to cover off in detail. So for a few months I expect I will be writing alot of technical content before resorting to some mindless dribble you dont want to hear of.
 
So for the immediate future you can expect to see these topics covered off quickly.
 
1) Windows Installer self healing in heavy detail
2) Windows Installer darwin descriptors and packed guids.
3) Windows Installer sequencing in heavy detail
4) Windows Installer automation object for beginners
5) Windows Installer automation object for advanced users
6) Wise Macro code
7) Understanding the windows registry from an application packagers view
8) Understanding dlls from a application packagers view
9) Understanding windows installer custom actions
10) Using XML based transforms for Enterprise System admins
11) Windows Installer Distribution points
12) Introduction to WiX
13) Automating WiX generation using C#
14) Introduction to MSBuild
15) Introduction to TeamBuild
16) Introduction to TFS
17) Combining TFS, WiX and MSbuild / TeamBuild
18) Dynamic packaging.
19) Dynamic WiX packaging
20) Dynamic Enterprise scale deployment
 
So that should keep me busy over the immediate future. Hopefully a few of you tune in for the upcoming items. If there is anything you guys would like to hear about related to Windows Installer or some form of SDLC items let me know.
 
John
 
 
 
 
 
 
 
Thanks for visiting!
Please wait...
Sorry, the comment you entered is too long. Please shorten it.
You didn't enter anything. Please try again.
Sorry, we can't add your comment right now. Please try again later.
To add a comment, you need permission from your parent. Ask for permission
Your parent has turned off comments.
Sorry, we can't delete your comment right now. Please try again later.
You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
Complete the security check below to finish leaving your comment.
The characters you type in the security check must match the characters in the picture or audio.
Jan. 4
Picture of Anonymous
Al wrote:
How about an example of using the embedded chainer in 4.5?
July 9
Brucewrote:
Johnny,
 
Two more topics... I know we already covered one of these, SMDB and CMDB (again!!)
Apr. 16
If anyone was wanting some basic training videos on packaging
appdeploy have some free Video training clips


http://www.appdeploy.com/video/
Mar. 31
Thanks for the Info Johnny. good call on making the BLOG!

cheers ~
Mar. 31