How to Install the Updater Application Block into the SexyUpdateDancer Application

by Zac Hamilton

Based on Duncan Mackenzie Blog located at: http://weblogs.asp.net/duncanma/story/10221.aspx

 

1.  Download Updater Application Block at microsoft.com

2.  Install Application Block to default location.  (Gotcha:  do not install this elsewhere, since there are files that depend on this path.)

3.  Install QuickStarts.vbs from link in Start Menu at:  Start/Program Files/Microsoft Application Blocks for .Net/Updater/Install QuickStarts(script).  (Gotcha:  This requires Visual Studio 2003 be installed on the computer.  If the path to Visual Studio 2003 is anything other than C:\Program Files\Visual Studio .Net 2003\ (or whatever the default install is) then you will need to edit the QuickStarts.vbs file and change the path on line 174 to the correct path where Visual Studio 2003 is located.)

4.  Setup the directory structure:

First create a directory tree for the app on development machine, emulating the final configuration on a user's machine. The tree on the desktop looks like this:
C:\Program Files\
--- \SexyUpdateDancer_Client\
---------\1.0.0.0\

Second, copy the AppStart.exe program (provided with all of the samples, and the code is available if you wanted to customize it) into the SexyUpdateDancer_Client directory, along with its config file, and then copy all the files from the \bin directory to the 1.0.0.0 directory (which is the 1.0.0.0 .exe, the word .dots, and related DLLs for our app).

Third, modify the .config file for the AppStart.exe program as follows.

 
 <appStart>
  <ClientApplicationInfo>
  
   <appFolderName>C:\Program Files\SexyUpdateDancer_Client\1.0.0.0</appFolderName>
   
   <appExeName>SexyUpdateDancer_Client.exe</appExeName>
   
   <installedVersion>1.0.0.0</installedVersion>
   
   <lastUpdated>2003-07-16T10:50:36.7831101-07:00</lastUpdated>
   
  </ClientApplicationInfo>
 </appStart >
 
 


Change the applications foldername (maybe relative paths will work, maybe not), the .exe filename, and the currently installed version #.  Note that there are two places with the version # there, and they are both the same.  

5.  Modifying our application:

Next, it was time to modify the application's code and .config file, because it handles the actual update work.

Add a reference to the Application Block DLL and then add this section of code to the top of the BaseForm:

 
#Region "App Updater Declares"
    Private WithEvents _updater As _
        
ApplicationUpdateManager = Nothing
    Private _updaterThread As Thread = Nothing
    Private Const UPDATERTHREAD_JOIN_TIMEOUT _
        
As Integer = 3 * 1000
    Private WithEvents myDomain As _
        
AppDomain = AppDomain.CurrentDomain
#End Region
 


Not the best code around, and it doesn't match up with the naming conventions I use, but it does match up with the sample apps shipping with the application block, and that seemed safest. Next, add code to start up the updater on a background thread when the application starts:

 
Private Sub InitializeAutoUpdate()
    _updater = New ApplicationUpdateManager
    '  start the updater on a separate thread _
    'so that our UI remains responsive
    _updaterThread = New Thread( _
        
New ThreadStart(AddressOf _updater.StartUpdater))
    _updaterThread.Start()
End Sub
 


(Call InitializeAutoUpdate() as the first line of the constructor of my main form)

Then all that is left is handling the events, which involves a bit of thread marshalling as we can't interact with the UI on the updater's thread.  We'll use Invoke to jump over to the Form's thread.

 
Private Sub _updater_FilesValidated( _
         
ByVal sender As Object, _
         
ByVal e As UpdaterActionEventArgs) _
         
Handles _updater.FilesValidated
    Me.BeginInvoke(New MarshalEventDelegate( _
         
AddressOf Me.OnUpdaterFilesValidatedHandler), _
         
New Object() {sender, e})
End Sub
Private Sub OnUpdaterFilesValidatedHandler( _
         
ByVal sender As Object, _
          
ByVal e As UpdaterActionEventArgs)
    Dim dialog As DialogResult = _
      MessageBox.Show( _
"Would you like to stop this application and open the new version?", _
"Open New Version?", MessageBoxButtons.YesNo)
    If DialogResult.Yes = dialog Then
        StartNewVersion(e.ServerInformation)
    End If
End Sub
Private Sub _updater_UpdateAvailable( _
        
ByVal sender As Object, _
        
ByVal e As UpdaterActionEventArgs) _
        
Handles _updater.UpdateAvailable
    Me.Invoke(New MarshalEventDelegate( _
       
AddressOf Me.OnUpdateAvailableHandler), _
       
New Object() {sender, e})
End Sub

Private Sub OnUpdateAvailableHandler( _
        
ByVal sender As Object, _
        
ByVal e As UpdaterActionEventArgs)
    Debug.WriteLine(("Thread: " + _
       Thread.CurrentThread.GetHashCode().ToString()))
    Dim message As String = _
       
String.Format("Update available: 
The new version on the server is {0} and current version is {1}
would you like to upgrade?", _
         e.ServerInformation.AvailableVersion, _
         ConfigurationSettings.AppSettings("version"))
    Dim dialog As DialogResult = _
         MessageBox.Show(message, _
         "Update Available", MessageBoxButtons.YesNo)
    '  for update available we actually WANT to block
    'the downloading thread so we can refuse an update
    '  and reset until next polling cycle;
    '  NOTE that we don't block the thread _in the UI_, 
    'we have it blocked at the marshalling dispatcher
    '"OnUpdaterUpdateAvailable"
    If DialogResult.No = dialog Then
        '  if no, stop the updater for this app
        _updater.StopUpdater(e.ApplicationName)
    End If
End Sub
Private Sub myDomain_ProcessExit(ByVal sender As Object, _
      
ByVal e As System.EventArgs) _
      
Handles myDomain.ProcessExit
    StopUpdater()
End Sub
Private Sub frmMain_Closed( _
     
ByVal sender As Object, _
     
ByVal e As System.EventArgs) Handles MyBase.Closed
    StopUpdater()
End Sub
Delegate Sub MarshalEventDelegate( _
     
ByVal sender As Object, _
      
ByVal e As UpdaterActionEventArgs)
 
Private Sub StartNewVersion( _
     
ByVal server As ServerApplicationInfo)
    '  NOTE:  this pathing trick will ONLY work when 
    '  this app is run from the expected "1.0.0.0"
    '  sub-dir of the demo;
    '  it expects to have AppStart.exe in dir directly 
    '  above it, as would be case in demo.
    Dim doc As System.Xml.XmlDocument = New System.Xml.XmlDocument
    Dim basePath As String
    doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile)
    basePath = doc.SelectSingleNode( _
"configuration/appUpdater/UpdaterConfiguration/
application/client/baseDir").InnerText
    Dim newDir As String = Path.Combine(basePath, "AppStart.exe")
    Dim newProcess As New ProcessStartInfo(newDir)
    newProcess.WorkingDirectory = newDir + server.AvailableVersion
    '  launch new version (actually, launch AppStart.exe which 
    '  HAS pointer to new version )
    Process.Start(newProcess)
    '  tell updater to stop
    myDomain_ProcessExit(Nothing, Nothing)
    '  leave this app
    Environment.Exit(0)
End Sub
 
Private Sub StopUpdater()
    '  tell updater to stop
    _updater.StopUpdater()
    If Not (_updaterThread Is Nothing) Then
        '  join the updater thread with a suitable timeout
        Dim isThreadJoined As Boolean = _
           _updaterThread.Join(UPDATERTHREAD_JOIN_TIMEOUT)
        '  check if we joined, if we didn't interrupt the thread
        If Not isThreadJoined Then
            _updaterThread.Interrupt()
        End If
        _updaterThread = Nothing
    End If
End Sub
 


That was it for code, no other line anywhere in the app was changed.  

6.  Modifying the app.config or SexyUpdateDancer_Client.exe.config:

The changes made to the app.config file were copied from the SelfUpdating example.

First, there is yet another place where you have to enter the same version #, and keep it in sync...

 
 <appSettings>
  <add key="version" value="1.0.0.0" />
 </appSettings>
 
 


Next, the log listener needs to point at a good local path.  Relative paths don't seem to work here either so the  final destination is c:\Program Files\SexyUpdateDancer_Client\ for the application on the user's machines (cringing at the idea of forcing and hardcoding an install location, but doing it anyway), and modify all of the paths accordingly:

 
<logListener logPath="C:\Program Files\SexyUpdateDancer_Client\UpdaterLog.txt" />
 
 

 

<application name="SexyUpdateDancer_Client" useValidation="false">

      <client>    

            <baseDir>C:\Program Files\SexyUpdateDancer_Client\</baseDir>

            <xmlFile>C:\Program Files\SexyUpdateDancer_Client\AppStart.exe.config</xmlFile>

            <tempDir>C:\Program Files\SexyUpdateDancer_Client\newFiles</tempDir>

      </client>

      <server>

            <xmlFile>http://SexyUpdateDancersupport.ballzac.com/updates/Manifest.xml</xmlFile>

            <xmlFileDest>C:\Program Files\SexyUpdateDancer_Client\Manifest.xml</xmlFileDest>

            <maxWaitXmlFile>60000</maxWaitXmlFile>

      </server>

</application>

 
 


7.  Working with the RSA key and the Manifest Utility:

There is also an RSA key that needed to be replaced.

 

The Manifest Utility can generate the key pairs for you. Open the Manifest Utility and go under the file menu and generate keys.  Note where they are saved. Then select the button for the key field in the manifest utility and go get the private key file that was just generated.

Then install the public key into the updater app.config file: Go to where the public key file is and copy its contents right into the config file between the <validator><key>PASTE TO HERE</key></validator> XML.

And follow these instructions also, as the Manifest Utility is a little quirky:
1) Select the Validator assembly and don't assume the default will work. If your update files folder points to a release version of your app, then select a release version of the validator assembly. Even if it is a debug version, reselect the debug version of the validator assembly.
2) After you select the validator assembly in step 1 the validator class combo box will refresh...then select the RSAValidator since you are using the keys.

3) See Section 9 for more details

 

8.  Creating the Initial Install:

The application needs to be installed onto the client machine at least once.   So I dragged my directory structure into a new setup file and hardcoded an install location of c:\Program Files\SexyUpdateDancer_Client.  The directory structure consists of the following subdirectory:   1.0.0.0 which contains the initial install of the SexyUpdateDancer applications /bin directory.  Then I built it into an .MSI.

9.  Setting up the web server:

First create a virtual directory under the support website named Updates.  Point updates to a directory on that machine.

Second, set the properties on the Virtual directory as shown in the following illustration:

Third, choose the Configuration... button to view the following illustration:

Fourth (this is VERY IMPORTANT), highlight the .config entry and choose Remove.  You will be asked:  "Remove the selected script mapping?".  Choose Yes to proceed.  If this is not done then the update process will fail every time.

Then choose Ok to return to the Properties screen.

Fifth, configure security so that users of the SexyUpdateDancer app have access to this Updates directory.

 

10.  Creating the Manifest.xml and putting files on the web server:

Creating the manifest file by hand would really suck, but luckily there is a "Manifest Utility" included with the Application Block that handles most of the work for you. You enter a directory path containing your app's .exe (usually the bin directory) and all required files (other assemblies, config files, etc.), a private key (which the tool can also create for you, as described in Section 7), the version number, again and the target web server that will be the source of these updated files as shown in the following screen shot:



When the user chooses to "Create Manifest", they will be asked to save the Manifest.xml file.  Save this file to the webserver's Updates directory (http://SexyUpdateDancersupport.ballzac.com/Updates/ in our case). 

Next copy all the files in the \bin directory to the 1.0.0.0 directory.  Make sure the Word Document templates are in there, since it is possible that changes may happen to these we need to make sure the user gets the most recent copy.  The Create Manifest will create a manifest of all the files that need to be downloaded by the client based on the bin directory. 

11.  Process to build and propagate a new version:

1.  Update SexyUpdateDancer Application

2. Change the assemblyinfo.vb version and the version key in the app.config file in the SexyUpdateDancer_Client project to the same (higher) version #.

<appSettings>
  <add key="version" value="1.0.0.1" />
 </appSettings>
 

3. Build and test the application locally.

4. Build in Release Mode.

5. Copy the files from the \bin directory to \Updates directory on the web server.

5. Create a new manifest file by pointing the manifest utility at the \bin directory, setting the Update location as  http://SexyUpdateDancersupport.ballzac.com/updates/1.0.0.1, updating the version # to match the values from step 2, and loading PrivateKey.xml from the location where they are stored as shown in the following illustration:

 

6. Save the Manifest.xml file to the \Updates directory on the web server (\Updates\Manifest.xml)

7. Client Applications should start updating within 120 seconds if they are currently running.