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

Blog


    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.