Click here to Skip to main content
11,639,593 members (62,884 online)
Click here to Skip to main content

Downloading updates in Windows Form application

, 7 Jun 2007 CPOL 79.6K 2K 181
Rate this:
Please Sign up or sign in to vote.
Downloading updates from Windows Forms application in an intranet scenario

Screenshot - 2.jpg

Introduction

I was playing the role of a consultant in a small software house for almost a year. The software house developed large-scale health care software for a corporate health care establishment. The software was architected as a true three-tier model. The business tier (middle tier we can say) was written in J2EE (using couple of fancy frameworks like Spring, Hibernate etc) and was hosted in a Web Logic servlet container. And the client tier was written in C# using Windows Form API. The communication protocol between these two tiers is SOAP Web Service.

The deployment process was a big deal because they needed to distribute the client application into over 350 machines (the client terminals) – although it was an XCopy deployment. Well this is a known and old pain of desktop application deployment. But the software is in a maintenance mode, and naturally, the bugs and new enhancements requested are getting noticed on a regular basis and some developers are working on those.

Essentially another old pain introduced is redistributing the updates among the client terminal machines. We already know that many applications update themselves automatically (for example, messenger applications i.e. yahoo, MSN, skype etc). Therefore, the company was simply looking for such a solution.

Eventually, when they notified me about this, I searched on the internet to see if somebody already did something on it or not. But I did not find any general solution on this (rather the proprietary solutions). Finally, I wrote a quick solution (which I am going to discuss in this article) to make the workaround. I am not saying that my approach is a standard/best (or any other adjective) but it just worked for us.

Idea

When I started documenting the workflow of this auto update use-case I found the followings execution points:

A. The application (assume the application is running in a process named process A) will have a button with text "Check for updates" and after clicking on it, it will request the server if there are any updates available or not. If there are no updates available then the application will continue from the point G (below mentioned).
B. If any updates are ready, then it will notify user that updates are ready to be downloaded, and request a response for starting the download task. If the response is negative then the application will continue its work from point G (below mentioned).
C. If user agrees to update, then the application will launch another process (let's name it as process B).
D. Process B will kill process A first. And then it will download all the files from the server (using remoting and binary formatters- it is worth using these because we are in an intranet environment).
E. Process B will re-launch process A (which is the main application essentially).
F. Process B will terminate.
G. Process A will continue its task as usual.

Simple enough I believe.

Now, let's start thinking about the implementations.

Analyze complexity and select technology

I broke down the tasks into several small parts to analyze where the complexity could arise. And I found only the communication portion was challenging compared to the others. I decided to use .NET Remoting because of its promising architecture and performances over intranet scenarios. I need to thank .NET framework that it has the cool XCopy deployment feature- this provides a clean and simple deployment process and leverages from old native installation hassles. Our target environment is a corporate intranet therefore I am going to use a binary formatter along with TCP/IP channel-because, I believe, this is the best solution over an intranet environment, as there is no firewall present. Essentially it can provide a huge performance to an application.

So far so good, now we can start implementing the solution.

Implementing the update distribution server

First of all, we will implement a remote server which will have an updated copy of our application, and all the terminals will ask/communicate to this server for updates, and this server will respond accordingly. If any updates are available, then it will also provide the updated assemblies to the client terminals via binary format. In real scenario, an application is supposed to build as one or more assemblies (essentially DLLs) so that updates can be applied only by replacing some or all assemblies (DLLs) of that application – rather than replacing the entire application. But, in this article I am going to update *all* the executable (EXEs) and the assemblies (DLLs) just for demonstration purposes. Updating only the necessary assemblies will provide more performance improvement.

First of all I am writing a new project –essentially it is a Windows form application that will host the remote server in it. I am going to write a remote interface first which will play the contact role between the client and server.

/// <summary>
///     A Contact that will be used by the 
///     remote client to find a service 
///     from the server.
/// </summary>
public interface IUpdateService
{
    /// <summary>
    ///        Get all files
    /// </summary>
    /// <returns>
    ///     An array of <see cref="System.String"/> 
    /// contains the file names</returns>
    string[] GetFiles();

    /// <summary>
    ///        Get the current version of a file
    /// </summary>
    /// <param name="fileName">The file name</param>
    /// <returns>The version that is currently available</returns>
    string GetCurrentVersion(string fileName);

    /// <summary>
    ///        Gets the entire file as binary
    /// </summary>
    /// <param name="fileName">The file name</param>
    /// <returns>
    /// An array of <see cref="Byte"/> containing the file content
    /// </returns>
    byte[] GetFile(string fileName);
}

As you might already have guessed, this is the interface that our remote server is going to implement. Now we will implement this interface as follows.

/// <summary>
///        The update service
/// </summary>
public class UpdateService : MarshalByRefObject, IUpdateService
{
    #region IUpdateService Members

    /// <summary>
    ///        Get all files
    /// </summary>
    /// <returns></returns>
    public string[] GetFiles()
    {
        Logger.LogMessage("Inside UpdateService::GetFiles()");            
        ArrayList collection = new ArrayList();

        foreach(FileObject fileObject in ConfigInfo.Instance.FileObjects)
        {
            collection.Add(fileObject.FileInfo.Name);
        }
        return collection.ToArray(typeof(string)) as string[];
    }

GetFiles essentially reads the available update files from a predefined configured directory and exposes the name of each file to its consumer.

/// <summary>
///        Get the current version of the file
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public string GetCurrentVersion(string fileName)
{
    Logger.LogMessage("Inside UpdateService::GetCurrentVersion()");
    
    foreach(FileObject fileObject in ConfigInfo.Instance.FileObjects)
    {
        if( fileObject.FileInfo.Name.Equals(fileName)
            || fileName.EndsWith(fileObject.FileInfo.Name))
        {
            return fileObject.Version;
        }
    }
    throw 
    new ArgumentException("Given file is not found into the server.");
}

GetCurrentVersion replies with the latest version number of a file that is available on the server. We are going to use the .NET assembly version to keep track of version number. For non-assembly files (for example XML, config etc) it will reply with a string like "NA".

/// <summary>
///        Gets the entire file
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public byte[] GetFile(string fileName)
{
    Logger.LogMessage("Inside UpdateService::GetFile()");
    foreach(FileObject fileObject 
        in ConfigInfo.Instance.FileObjects)
    {
        if( fileObject.FileInfo.Name.Equals(fileName)
            || fileName.EndsWith(fileObject.FileInfo.Name))
        {
            return GetBinaryContents(fileObject);
        }
    }
    throw 
    new ArgumentException("Given file is not found into the server.");
}

Now, as the method name says, GetFile simply returns the entire file content in binary format. And the method reads the file content using the following method:

    /// <summary>
    ///        Gets the binary content of the entire file
    /// </summary>
    /// <param name="fileObject"></param>
    private byte[] GetBinaryContents(FileObject fileObject)
    {
        byte[] block;
        using(FileStream fileStream = 
            File.OpenRead(fileObject.FileInfo.FullName))
        {
            using(BinaryReader reader = new BinaryReader(fileStream))
            {
                block = reader.ReadBytes((int)fileStream.Length);
            }
        }
        return block;
    }
}

It is time to expose the service to the outside world. So I am going to host the service into a Windows Form. Here is the code snippet for exposing the service:

/// <summary>
///        Starts the server onto a specific port
/// </summary>
private void StartServer()
{            
    // Display the log
    Logger.LogMessage("Opening channel..");
    // start listening
    TcpServerChannel serverChannel = new TcpServerChannel(7444);
    // now register the channel
    Logger.LogMessage("Opening channel..completed.");
    Logger.LogMessage("Registering channel..");
    ChannelServices.RegisterChannel(serverChannel);
    Logger.LogMessage("Registering channel..completed.");
    Logger.LogMessage("Registering WKO Objects..");
    // register/expose the wko objects
    RemotingConfiguration.RegisterWellKnownServiceType(
        typeof(UpdateService),"UpdateService",
        WellKnownObjectMode.SingleCall);
    Logger.LogMessage("Registering WKO Objects..completed.");
}

The AppUpdateServer project is ready now.

Now it is time to modify our main application which will eventually contact the remote server and ask for updates. To demonstrate this, I am going to write a small application named SampleApplication. It is a GUI application and let's say it has a menu called "Check for Updates". Whenever the user clicks on it, the application will start the update process.

One thing we need to keep in mind is that this application needs the remote URL of the remote server in order to communicate with the remote server. So we need to put that into the configuration file of the SampleApplication. Now, I am writing a class here that will contain the update related stuff (just to keep this code separate from the other business code of the application), which will have a method named "Update" that will do all the work.

So here is the class implementation:

/// <summary>
///        Utility that provides update functionalities
/// </summary>
public class UpdateUtil
{
    /// <summary>
    /// 
    /// </summary>
    private string remoteObjectUri = string.Empty;
    
    /// <summary>
    /// 
    /// </summary>
    private IUpdateService remoteService;

    private IWin32Window owner;

    private string applicationName;

    /// <summary>
    ///        Creates a new instance
    /// </summary>
    public UpdateUtil(IWin32Window owner,string remoteObjectUri)
    {
        remoteService = null;
        this.owner = owner;
        this.remoteObjectUri = remoteObjectUri;
    }

As you can see, the class needs the remote object URI and a win32 owner (the sample application in this case) as a constructor argument.

/// <summary>
///        Connect to the remote server
/// </summary>
/// <remarks>
///        Tries to create a WKO Instance
/// </remarks>
/// <returns>
///        <c>true</c> if the connection establishes 
///    successfully, <c>false</c> otherwise.
/// </returns>
private bool ConnectRemoteServer()
{
    try
    {
        remoteService = 
            Activator.GetObject( typeof(IUpdateService),
             remoteObjectUri ) 
            as IUpdateService;
    }
    catch(Exception remoteException )
    {
        System.Diagnostics.Trace.WriteLine(remoteException.Message);
    }
    return remoteService != null;
}

ConnectRemoteServer simply establishes the connection to the server using the remote URI.

/// <summary>
///        Determine if a new version of this 
///    application is currently available
/// </summary>
/// <returns>
///        <c>true</c> if available, <c>false</c> otherwise
/// </returns>
private bool UpdateAvailable()
{        
    try
    {
        string assemblylocation 
            = Assembly.GetExecutingAssembly().CodeBase;
        assemblylocation 
            = assemblylocation.Substring(
            assemblylocation.LastIndexOf("/")+1);
        applicationName = assemblylocation;
        AssemblyName assemblyName 
            = Assembly.GetExecutingAssembly().GetName();
        string localVersion 
            = assemblyName.Version.ToString();
        string remoteVersion 
            = remoteService.GetCurrentVersion(applicationName);
        return IsUpdateNecessary(localVersion,remoteVersion);
    }
    catch(Exception ex)
    {
        System.Diagnostics.Trace.WriteLine(ex.Message);
    }
    return false;
}

Now, this method performs an important task, it asks the server what version is available at the remote side for the SampleApplication.exe; if any update is available that it returns true to its caller. The UpdateAvailable method using the following utility method IsUpdateNecessary to determine if the available version number is an update over the current one or not.

/// <summary>
///        Is update needed?
/// </summary>
/// <param name="localVersion"></param>
/// <param name="remoteVersion"></param>
/// <returns></returns>
private bool IsUpdateNecessary(string localVersion,string remoteVersion)
{
    try
    {
        long lcVersion = Convert.ToInt64( localVersion.Replace(".",""));
        long rmVersion = Convert.ToInt64( remoteVersion.Replace(".",""));
        return lcVersion < rmVersion ;
    }
    catch(Exception ex)
    {
        System.Diagnostics.Trace.WriteLine(ex.Message);
    }
    return false;
}

Now this is the only public method of the class that actually invokes the above defined method to do the Update kick. This method does some noticeable stuff inside it. I will explain these now. First of all, the method simply checks if any updates are available, if yes then it *does not do* the Update process itself. It launches another process AppUpdate.exe to do this job. The theory behind this is simple; the Update process will replace the current exe. But the system will not allow us to replace an exe file when it is in execution. So we have to launch another process.

/// <summary>
///        Update the application executable
/// </summary>
public void Update()
{
    if( !ConnectRemoteServer())
        return ;// the remote connection was not okay
    if( UpdateAvailable())
        {
            // lets checkout if any update version available or not
            if( DialogResult.Yes == 
            MessageBox.Show(owner,
            "An update is available. \nWould you like to update now?",
            "Sample Application Update",
            MessageBoxButtons.YesNo,
            MessageBoxIcon.Question))
            {
            string updateAppPath 
                = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, 
                    "AppUpdate.exe");

            Process updateProcess = new Process();
            updateProcess.StartInfo = 
            new ProcessStartInfo(updateAppPath,
            Process.GetCurrentProcess().Id.ToString()
            + " "+remoteObjectUri);
            updateProcess.Start();
            }                                        
        }
}
}

Now, AppUpdate.exe is the only thing that we need to implement to complete the task. AppUpdate is essentially another tiny Windows Forms application (I am using Windows Forms because my intent is to display a progress bar Window during the update process) which will perform the actual update task. This process takes two command line arguments. One is the Process ID of the Sample Application. The other is the remote server URL. The purpose of the latter one is simple to us – I believe. I am just explaining the purpose of the former one. The AppUpdate.exe will replace the SampleApplication exe, so it will first kill the process (inside which SampleApplication is running) and then it will update the application exe's and DLLs, finally it will re-launch the SampleApplication.

Let's focus on to the AppUpdate.exe now.

AppUpdate project contains a single Windows Form class where I have written the Update stuff. Here are the code snippets:

/// <summary>
///        Updates an application
/// </summary>
public class AppUpdate : System.Windows.Forms.Form
{
    // .. removing .net fx declared variables for simplicity
    private string applicationProcessID;
    private string remoteObjectUri ;

    /// <summary>
    ///     Creates a new instance
    /// </summary>
    public AppUpdate(string applicationProcessID,string remoteUrl)
    {
        // Required for Windows Form Designer support
        InitializeComponent();
        try
        {
            this.applicationProcessID = applicationProcessID;
            this.remoteObjectUri = remoteUrl;
        }
        catch(Exception){}
    }

The constructor of the class takes two arguments that I just talked about, one is the application process id of SampleApplication and another is the remote server URI.

/// <summary>
///     Runs the update process
/// </summary>
public void RunUpdateProcess()
{
    Process applicationProcess = 
        Process.GetProcessById(GetProcessID());
    
    if( applicationProcess == null ) return;
    
    targetApplicationFullpath = 
        applicationProcess.MainModule.FileName;

    applicationProcess.Kill();
    applicationProcess.WaitForExit();

    if( !ConnectRemoteServer()) return ;
    UpdateFiles();
}

RunUpdateProcess is the cue method that will do all the tasks. We will invoke this method when the Form gets loaded.

Screenshot - 3.jpg

I mean, when the Progress Window will be activated, we will invoke this method. So we can write an event handler for Form_Load inside this class and can invoke this method from inside Form_Load(). This method kills the SampleApplication process and then invokes another private method UpdateFiles, the implementation of which is given below:

/// <summary>
///     Updates the files from the remote server
/// </summary>
private void UpdateFiles()
{
    string targetDir 
        = targetApplicationFullpath.Substring(0,
            targetApplicationFullpath.LastIndexOf("\\"));            
    try
    {
        foreach( string file 
            in remoteService.GetFiles())
        {
            byte[] array = 
            remoteService.GetFile(file);

            string fileName = Path.Combine(targetDir,file);
            try
            {
                FileInfo finfo = new FileInfo(fileName);
                if( finfo.Exists ) finfo.Delete();

                using(FileStream outputFile 
                    = new FileStream(fileName,
                    FileMode.CreateNew , 
                    FileAccess.Write ))
                {
                    using(BinaryWriter writer =
                         new BinaryWriter(outputFile))
                    {
                        writer.Write(array,0,array.Length);
                    }
                }
            }
            catch(Exception)
            {
            }
        }
    }
    catch(Exception )
    {
    }
    Process launch = new Process();
    launch.StartInfo = 
        new ProcessStartInfo(targetApplicationFullpath);
    launch.Start();
    Application.Exit();
}

Therefore, we can see that this method copies each updated file from the remote server and replaces the existing ole version with those. Now it is time to run this application's Window – which we are going to do inside the Main method.

/// <summary>
///     Entry point of the application
/// </summary>
/// <param name="args"></param>
[STAThread()]
public static void Main(string [] args )
{
    if( args.Length > 0 )
    {
        Application.Run(
        new AppUpdate(args[0],args[1]));
    }
}
}

The Main method receives the process id and remote URI thru the command line arguments and provides these into the AppUpdate class while instantiating it.

That's all there is to our implementation.

Review and Test

To test out this solution, first of all we will have to launch the server application. In the server machine, there should be a directory that will contain all the updated assemblies along with all XML files and the config file (as needed). And the server application will read all the file version numbers and expose the remote services. Then we will launch the client application (SampleApplication in this case) and click on the menu "Check for Updates". And the Update process will work.

Conclusion

Again, this is a quick and easy solution that I have written. So there no reason for me to claim it a standard or "something classic". But I believe this can help the desktop application written in .NET and running in an intranet scenario. In recent days, more classic Frameworks like ClickOnce and XBAP are being used in organizations for this kind of solution.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Moim Hossain
Architect BlueCielo ECM Solutions
Netherlands Netherlands
Just another developer.

You may also be interested in...

Comments and Discussions

 
GeneralProgram gets stuck when executing code line "remoteService.GetCurrentVersion(applicationName)" Pin
sohst18-Aug-09 23:33
membersohst18-Aug-09 23:33 
GeneralRe: Program gets stuck when executing code line "remoteService.GetCurrentVersion(applicationName)" Pin
MWRivera25-Aug-09 8:43
memberMWRivera25-Aug-09 8:43 
GeneralRe: Program gets stuck when executing code line "remoteService.GetCurrentVersion(applicationName)" Pin
aggrewolf19-Aug-12 20:02
memberaggrewolf19-Aug-12 20:02 
Questiononly can update application file? Pin
stanleylaw9-Nov-08 15:18
memberstanleylaw9-Nov-08 15:18 
GeneralIts Excellent [modified] Pin
frederic31-Oct-08 22:43
memberfrederic31-Oct-08 22:43 
QuestionStandard User and permissions Pin
CBFlo22-Jul-08 4:00
memberCBFlo22-Jul-08 4:00 
GeneralSimilar thing Pin
frixoft17-Sep-07 8:02
memberfrixoft17-Sep-07 8:02 
GeneralProblem with connecting Pin
vticky19-Jun-07 4:31
membervticky19-Jun-07 4:31 
GeneralRe: Problem with connecting Pin
Moim Hossain19-Jun-07 7:33
memberMoim Hossain19-Jun-07 7:33 
QuestionWhat about ClickOnce? Pin
oshaath18-Jun-07 20:25
memberoshaath18-Jun-07 20:25 
AnswerRe: What about ClickOnce? Pin
Moim Hossain18-Jun-07 21:03
memberMoim Hossain18-Jun-07 21:03 
GeneralError Pin
PQSIK16-Jun-07 4:27
memberPQSIK16-Jun-07 4:27 
AnswerRe: Error [modified] Pin
Moim Hossain16-Jun-07 6:51
memberMoim Hossain16-Jun-07 6:51 
GeneralRe: Error Pin
PQSIK16-Jun-07 11:26
memberPQSIK16-Jun-07 11:26 
GeneralRe: Error Pin
Moim Hossain16-Jun-07 19:51
memberMoim Hossain16-Jun-07 19:51 
GeneralRe: Error Pin
PQSIK18-Jun-07 3:38
memberPQSIK18-Jun-07 3:38 
GeneralRe: Error Pin
Moim Hossain18-Jun-07 5:09
memberMoim Hossain18-Jun-07 5:09 
GeneralNice Article Pin
yesildal12-Jun-07 7:25
memberyesildal12-Jun-07 7:25 
GeneralRe: Nice Article Pin
Moim Hossain16-Jun-07 7:01
memberMoim Hossain16-Jun-07 7:01 
GeneralSome further ideas Pin
Civilised Barbarian12-Jun-07 6:42
memberCivilised Barbarian12-Jun-07 6:42 
GeneralRe: Some further ideas Pin
Moim Hossain12-Jun-07 7:08
memberMoim Hossain12-Jun-07 7:08 
QuestionWhat about BITS? Pin
stensones12-Jun-07 4:13
memberstensones12-Jun-07 4:13 
AnswerRe: What about BITS? Pin
Moim Hossain16-Jun-07 19:54
memberMoim Hossain16-Jun-07 19:54 
GeneralGood work :D Pin
dotnetangel7-Jun-07 8:36
memberdotnetangel7-Jun-07 8:36 
GeneralRe: Good work :D Pin
Moim Hossain7-Jun-07 8:49
memberMoim Hossain7-Jun-07 8:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150731.1 | Last Updated 7 Jun 2007
Article Copyright 2007 by Moim Hossain
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid