How can I perform an update without triggering the UAC?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts
  • Ulrich
    Indigo Rose Staff Member
    • Apr 2005
    • 5131

    How can I perform an update without triggering the UAC?

    The solution presented below uses a commercial third party plugin, developed independently and currently not officially endorsed by Indigo Rose.

    Introduction

    On newer operating systems, like Windows Vista, Windows Server 2008 and Windows 7, it is not obvious how you could perform silent update checks. Since by default you would configure your update client to be run "as administrator", so it would be able to replace files in folders that are being monitored by the User Account Control (UAC). However, when starting an update client with this setting, the end user will be asked to allow the program to run - and there may be no new update available, so this security prompt is nothing but an annoyance.

    One solution would be running the update client as invoker (so it would not trigger the UAC), and use patch packages, like those created by Visual Patch, set to run as administrator. The end user would still be prompted before the installed software can be updated, but at least this prompt would appear only when an update is actually available.

    Another solution, avoiding security prompts completely, would be adding the UpdateHelper action plugin and Windows service to your project, and invoking it directly in your TrueUpdate client script.

    Deploying the files into proper folders

    You should always use session variables in the destination of your application files. The %AppFolder% is the obvious choice for all static files that are part of your distribution, and do not change during the normal execution of your product.

    Some sub folder of %ApplicationDataFolder% should be used for user-specific configuration files of your application, so the product can actually write to these files in runtime. Please note that since 2006, when Vista was officially published, you cannot and should not keep configuration files in the application folder.

    The %ApplicationDataFolderCommon% would be the best choice for placing the TrueUpdate client executable and data file, because here the application can update itself without causing a security alert of the UAC, no matter what user started the update check.

    Resuming, this screen capture shows a suggestion how a really simple application could be laid out on the target system:



    Configuring the update client

    By default, the TrueUpdate is executed with administrative privileges ("Require administrator"). If we wish to run the update client as silently as possible, we need to change this setting. See Project > Settings > Advanced > Requested User Privileges. Set the "As invoker" radio button here.

    Adjusting the folder access rights

    When you deploy a file after an elevation, the file that gets installed does not feature the same access rights as a file that was copied by a regular user. For example, the files that are deployed into %ApplicationDataFolderCommon%\UpdateDemo in the example above, cannot be deleted or changed by a regular user. Even placing these files into an area where the UAC does not monitor for changes, they were not given the same security settings as shown by a file that you copy into that folder manually afterwards. So we have to correct this setting, caused by the elevation.

    Setting access rights to files in a certain folder is now very easy, thanks to a small script I developed earlier and published here. If you don't have it already, fetch the script, install it into your Setup Factory environment, and include the files into your installer as shown in the documentation. A single line of code, best placed in the On Post Install event of your installer, will fix the folder's access rights:
    Code:
    Folder[COLOR="#FF0000"].[/COLOR]SetPermissions(SessionVar[COLOR="#FF0000"].[/COLOR]Expand([COLOR="#800080"]"%ApplicationDataFolderCommon%[COLOR="#800080"]\\[/COLOR]UpdateDemo"[/COLOR])[COLOR="#FF0000"],[/COLOR] [COLOR="#800080"]"Everybody"[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#800080"]"Grant"[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#800080"]"Full"[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]true[/COLOR]);
    This action will grant full control to the user group "Everybody", so now any regular user will be able to start the update client, and it can replace the data file or executable in this folder, when new versions are available. Neat, isn't it?

    Updating the installed product

    Now, how can you silently replace the files that were installed in the ProgramFiles tree, when the update client is running "as invoker"? You need the UpdateHelper software bundle. The software is composed by a plugin that you can add to your TrueUpdate toolbox, and a Windows service. You will use the plugin to inform the service about tasks that you wish it to get done. The service does receive the command over an encrypted channel, parses the data, and performs the requested action, allowing your script to resume right afterwards.

    To keep this explanation as simple as possible, let us assume that your update client downloads a zip file containing the new versions of those files that need to be replaced in the product's installation folder. So, you would normally download the zip file into the temporary folder, and then decompress the files directly in the target folder. We will need to make a minor change in this procedure.

    Instead of decompressing the files directly into the application folder, we will decompress the contents into a new, temporary folder. Then we will instruct the UpdateHelper service to copy these files to the target folder. This can be done, again, with a single line of code.

    First, we set the destination where we wish to expand the contents of the zip to:

    Code:
    [COLOR="#008000"]-- Where do you want to download the zip file to?[/COLOR]
    g_ZipDownloadDest [COLOR="#FF0000"]=[/COLOR] SessionVar[COLOR="#FF0000"].[/COLOR]Expand([COLOR="#800080"]"%TempFolder%[COLOR="#800080"]\\[/COLOR]%ProductName%[COLOR="#800080"]\\[/COLOR]patch.zip"[/COLOR]);
    [COLOR="#008000"]-- Where do you want to extract the files to?[/COLOR]
    g_ZipExtractDest [COLOR="#FF0000"]=[/COLOR] SessionVar[COLOR="#FF0000"].[/COLOR]Expand([COLOR="#800080"]"%TempFolder%[COLOR="#800080"]\\[/COLOR]%ProductName%[COLOR="#800080"]\\[/COLOR]patch"[/COLOR]);
    We simply are creating a sub folder, named "patch", and decompress the contents of the zip there. No changes are required to the scripts in the screens - just set the global variables as needed.

    Now that we have decompressed the content of the zip files, we need to tell the UpdateHelper service that we wish to have these files copied or moved into the application folder. Assuming that you somehow know where the application was installed, and have the folder path stored in a session variable named %AppFolder%, this would be the single command you need to update the application:
    Code:
    res [COLOR="#FF0000"]=[/COLOR] UpdateHelper[COLOR="#FF0000"].[/COLOR]FileCopy(SessionVar[COLOR="#FF0000"].[/COLOR]Expand([COLOR="#800080"]"%TempFolder%[COLOR="#800080"]\\[/COLOR]%ProductName%[COLOR="#800080"]\\[/COLOR]patch[COLOR="#800080"]\\[/COLOR]*.*"[/COLOR])[COLOR="#FF0000"],[/COLOR] SessionVar[COLOR="#FF0000"].[/COLOR]Expand([COLOR="#800080"]"%AppFolder%"[/COLOR])[COLOR="#FF0000"],[/COLOR][COLOR="#0000FF"]true[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]true[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]true[/COLOR]);
    This line of code will instruct the UpdateHelper service to copy all files found at the specified location and copy recursively the sub folders, overwriting old files as needed.



    This is, of course, only one of the functions offered by this plugin. An almost complete update script, missing only the obvious part of closing down the running application before the files can be overwritten, could look similar to this example:
    Code:
    [COLOR="#008000"]------------------------------------------[/COLOR]
    [COLOR="#008000"]-- Perform the Update[/COLOR]
    [COLOR="#008000"]------------------------------------------[/COLOR]
    
    [COLOR="#008000"]-- Tell the user an update is available[/COLOR]
    [COLOR="#0000FF"]if[/COLOR] (Screen[COLOR="#FF0000"].[/COLOR]Show([COLOR="#800080"]"Update Required"[/COLOR]) [COLOR="#FF0000"]==[/COLOR] SR_LISTCOMPLETE) [COLOR="#0000FF"]then[/COLOR]
        [COLOR="#008000"]-- Download the zip file[/COLOR]
        [COLOR="#0000FF"]if[/COLOR](Screen[COLOR="#FF0000"].[/COLOR]Show([COLOR="#800080"]"Download Zip"[/COLOR]) [COLOR="#FF0000"]==[/COLOR] SR_SUCCESS) [COLOR="#0000FF"]then[/COLOR]
            [COLOR="#008000"]-- Extract the contents of the zip file[/COLOR]
            [COLOR="#0000FF"]if[/COLOR] (Screen[COLOR="#FF0000"].[/COLOR]Show([COLOR="#800080"]"Unzip File"[/COLOR]) [COLOR="#FF0000"]==[/COLOR] SR_SUCCESS) [COLOR="#0000FF"]then[/COLOR]
                [COLOR="#008000"]-- The zip extraction succeeded, so delete the zip file.[/COLOR]
                File[COLOR="#FF0000"].[/COLOR]Delete(g_ZipDownloadDest[COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]false[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]false[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]false[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]nil[/COLOR]);
                [COLOR="#008000"]-- Copy the extracted files to the application folder[/COLOR]
                res [COLOR="#FF0000"]=[/COLOR] UpdateHelper[COLOR="#FF0000"].[/COLOR]FileCopy(SessionVar[COLOR="#FF0000"].[/COLOR]Expand([COLOR="#800080"]"%TempFolder%[COLOR="#800080"]\\[/COLOR]%ProductName%[COLOR="#800080"]\\[/COLOR]patch[COLOR="#800080"]\\[/COLOR]*.*"[/COLOR])[COLOR="#FF0000"],[/COLOR]  SessionVar[COLOR="#FF0000"].[/COLOR]Expand([COLOR="#800080"]"%AppFolder%"[/COLOR])[COLOR="#FF0000"],[/COLOR]  [COLOR="#0000FF"]true[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]true[/COLOR][COLOR="#FF0000"],[/COLOR] [COLOR="#0000FF"]true[/COLOR]);
                [COLOR="#008000"]-- Perform cleanup[/COLOR]
                Folder[COLOR="#FF0000"].[/COLOR]DeleteTree(SessionVar[COLOR="#FF0000"].[/COLOR]Expand([COLOR="#800080"]"%TempFolder%[COLOR="#800080"]\\[/COLOR]%ProductName%"[/COLOR]));
                [COLOR="#0000FF"]if[/COLOR] (res [COLOR="#FF0000"]>[/COLOR] [COLOR="#000000"]0[/COLOR]) [COLOR="#0000FF"]then[/COLOR]
                    [COLOR="#008000"]-- The zip extraction succeeded, so tell the user[/COLOR]
                    [COLOR="#008000"]-- that the update process was successful and is complete.[/COLOR]
                    Screen[COLOR="#FF0000"].[/COLOR]Show([COLOR="#800080"]"Update Successful"[/COLOR]);
                [COLOR="#0000FF"]else[/COLOR]
                    [COLOR="#008000"]-- Something went wrong while copying the files[/COLOR]
                    Screen[COLOR="#FF0000"].[/COLOR]Show([COLOR="#800080"]"Update Failed"[/COLOR]);        
                [COLOR="#0000FF"]end[/COLOR]
            [COLOR="#0000FF"]else[/COLOR]
                [COLOR="#008000"]-- The zip extraction failed[/COLOR]
                Screen[COLOR="#FF0000"].[/COLOR]Show([COLOR="#800080"]"Update Failed"[/COLOR]);
            [COLOR="#0000FF"]end[/COLOR]
        [COLOR="#0000FF"]end[/COLOR]
    [COLOR="#0000FF"]end[/COLOR]
    If you believe that this approach could help you updating your product silently, you can find the UpdateHelper software bundle on my server.

    Ulrich
    Last edited by Ulrich; 10-01-2010, 12:40 PM. Reason: Lua code highlighted, link updated
Working...
X