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.
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 SubPrivate 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 SubPrivate 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 SubPrivate Sub myDomain_ProcessExit(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles myDomain.ProcessExit
StopUpdater()End SubPrivate Sub frmMain_Closed( _
ByVal sender As Object, _
ByVal e As System.EventArgs) Handles MyBase.Closed
StopUpdater()End SubDelegate 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").InnerTextDim 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 IfEnd Sub
That was it for code, no other line anywhere in the app was changed.
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>
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
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.
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.
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.
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.