KnowDotNet NetRefactor

Updater Application Block - Writing a PostProcessor

PostProcessor Inside UAB Wrapper DLL

by Les Smith
Print this Article Discuss in Forums

Write a Updater Application Block Wrapper Class and include the PostProcessor in it.  Previously I showed you the C# code for a UAB Wrapper Class, that is reusable in any Sel-Updating application.  

Since you probably will have multiple DLLs and EXEs in an application of any size or complexity, you probably will not want to have to download assemlys that have not changed.  For example, the Microsoft Application Block files (8) have to be deployed to the client in order for the Self-Updating application to update itself.  Obviously, you do not want to have to download those files each time your application is updated, since they do not change.  Some of your own application files probably will not change very often and you only want to download those files that have changed.

This article will give you the C# Code for a PostProcessor Class that is called from the
UAB Wrapper Class, in the StartNewVersion method of the Wrapper Class.

Several things must be accomplished in this PostProcessor. They are listed in the order that they will be performed.

  • Get the path to the AppStart.exe.config file.  This tells me where the latest downloaded version files are located.

  • Get the path to the currently executing application, at whose behest this PostProessor is running.  This tells me where the complete list of files required to run the application are located.

  • Call helper methods, passing the respective paths to get arrays of the two sets of files in the respective folders.

  • Call helper method that returns an array of files that exist in the current exe folder, but were not downloaded to the new version path.

  • Copy the delta files (not downloaded list) from the current exe path to the new version path.

That's about all there is to the PostProcessor for my test app.  You can add anything that your application might need, after it is downloaded, but before it can be restarted.

I have added a call to the PostProcessor to the UAB Wrapper Class, if the application has requested that the PostProcessor be called.  That code has been extracted from the UAB Wrapper and shown in Figure 1.

Figure 1 - Modifications to the UAB Wrapper Class To Call the PostProcessor.

    /// <summary>
    /// If PostProcessor is required, call that DLL to move the files
    /// that were not updated.
    /// Finally, start AppStart.exe and dispose of this application.
    /// AppStart.exe will start the new version.
    ///
    ///
    private bool StartNewDownloadedVersion( ServerApplicationInfo server )
    {
      
try
      {
        
if (server==null)
        {
          
this.CantStart("server info is null");
          
return false;
        }

        
// if PostProcessor is called for, run it
        if(RunPostProcessor) {
          InfoPro.UABWrapper.PostProcessor pp =
new PostProcessor(ExeName);
          
bool ok = pp.CopyFilesNotUpdated();
          
if (!ok) {return false;}
        }

        XmlDocument doc =
new XmlDocument();

        
//  load config file to get base dir
        doc.Load( AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);

        
//  get the base dir
        string baseDir =
         doc.SelectSingleNode(
         "configuration/appUpdater/UpdaterConfiguration/application/client/baseDir")
         .InnerText;
        
if (baseDir.Length==0)
        {
          
throw(new Exception("baseDir is empty"));
        }

        
string newDir = Path.Combine( baseDir, "AppStart.exe" );
            
        ProcessStartInfo process =
new ProcessStartInfo( newDir );
        process.WorkingDirectory = Path.Combine( newDir , server.AvailableVersion );

        
//  launch new version (actually, launch AppStart.exe
        // which HAS pointer to new version )
        Process.Start( process );

        
//  tell updater to stop
        CurrentDomain_ProcessExit( null, null );
        
//  leave this app
        Environment.Exit( 0 );
        
// control should not reach here, but it satifies the compiler
        return true;
      }
      
catch(System.Exception ex)
      {
        
// add your code here
        this.NewVersionStartFailed(ex, new EventArgs());
        
return false;
      }
    }


Figure 2 shows the code for the PostProcessor.  You will notice an instantiation of the
XMLConfigurationHandler xCH object.  This is a simple wrapper for the XML Document object and allows me to easily retrieve any "inner text" of an Xml Node.

Figure 2 - PostProcess Class.


using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections;
using System.Configuration;
using System.Diagnostics;

namespace UABWrapper
{
  
/// <summary>
  /// This class handles the post processing of files.
  /// Since we may not want to transfer all files,
  /// because all files were not updated, this object
  /// is called after the files are downloaded by the
  /// downcompleted event to check the list of files
  /// in the current executing folder against the
  /// new version folder.  It will copy any files from
  /// the current that do not exist in the new.  It
  /// must verify that there is a a new folder (in case
  /// the validation or download failed and the UAB
  /// deleted all of the files).
  /// The assumption is that the Root folder, where
  /// AppStart resides, contains version folders as:
  /// c:\AppRoot
  /// c:\Approot\1.0.0.0
  /// c:\AppRoot\2.0.0.0
  /// AppStart.exe.config always points to the latest
  /// version folder.
  ///
  public class PostProcessor
  {
    
public PostProcessor(string appName)
    {
      AppName = appName;
    }

    #region Class Level Variables
    
private string _AppName;
    
private string _ThisExePath;
    
private string _NewVersionPath;
    
private UABWrapper.XMLConfigurationHandler xCH;

    #endregion

    #region
Public Properties
    
public string AppName
    {
      
get {return _AppName;}
      
set {_AppName = value;}
    }
    
public string ThisExePath
    {
      
get{return _ThisExePath;}
      
set{_ThisExePath = value;}
    }
    
public string NewVersionPath
    {
      
get{return _NewVersionPath;}
      
set{_NewVersionPath = value;}
    }

    #endregion


    #region Public Methods
    
/// <summary>
    /// Make a list of files in the new directory and copy
    /// any missing files from the old directory.
    ///
    /// <returns>true if successful
    public bool CopyFilesNotUpdated()
    {
      ArrayList missingFiles = GetMissingFilePaths(AppName);
      
return CopyFileList(missingFiles);
    }
    #endregion

    #region
Private Methods

    
/// <summary>
    /// Copy the list of files from the old directory
    /// to the new version path
    ///
    ///
    private bool CopyFileList(ArrayList missingFiles)
    {
      
for (int i =0; i<missingFiles.Count; i++)<BR>       {
        
string oldFile = Path.Combine(ThisExePath,missingFiles[i].ToString());
        
string newFile = Path.Combine(NewVersionPath,Path.GetFileName(
          missingFiles[i].ToString()));
        
try
        {
          File.Copy(oldFile,newFile);
        }
        
catch
        {
          
return false;
        }
      }

      
return true;
    }

    #endregion

    /// <summary>
    /// This method will return an arraylist of files that were
    /// in the current version folder, but are not in the new
    /// version folder.
    ///
    ///
    /// <returns>ArrayList of missing files
    private ArrayList GetMissingFilePaths(string exeName)
    {
      
// first we get the path to the appStart.exe.config
      // it has already been modified by the upadter to point
      // to the new exe path.
      xCH = new XMLConfigurationHandler("");
      
string appStartConfig =
       xCH.GetSingleNode(
       "configuration/appUpdater/UpdaterConfiguration/application/client/xmlFile");
      xCH =
new XMLConfigurationHandler(appStartConfig);
      
string newVersionAppPath =
       xCH.GetSingleNode(
       "configuration/appStart/ClientApplicationInfo/appFolderName");
      NewVersionPath = newVersionAppPath;

      
// the currently executing assembly is found by calling GetApppath
      string myPath =  Path.GetDirectoryName(GetAppPath());
      ThisExePath = myPath;
      
string[] oldFiles = GetCurrentVersionFileList(myPath);
      
string[] newFiles = GetNewVersionFileList(newVersionAppPath);
      ArrayList deltaFiles=GetFilesToCopy(oldFiles, newFiles);
      
return deltaFiles;
    }

    
/// <summary>
    /// Loop thru oldFiles array with inner loop to
    /// go thru the newFiles array.  If a file exists
    /// in the oldFiles and is not in newFiles, add to
    /// arraylist which is returned.
    ///
    ///
    ///
    /// <returns>arraylist of files that need copying
    private ArrayList GetFilesToCopy(string[] oldFiles, string[] newFiles)
    {
      ArrayList al =
new ArrayList();
      
foreach(string oldFile in oldFiles)
      {
        
bool found=false;
        
string oldFN = Path.GetFileName(oldFile);
        
string newFN=string.Empty;
        
foreach(string newFile in newFiles)
        {
          newFN = Path.GetFileName(newFile);
          
if (oldFN.ToLower() == newFN.ToLower())
          {
            found=
true;
            
break;
          }
        }
        
if(!found) {al.Add(oldFile);}
      }
      
return al;
    }



    
/// <summary>
    /// Return the path to the new version of my app.
    ///
    /// <returns>
    private string GetPathToNewVersion()
    {
      
string s=null;
      
return s;
    }


    /// <summary>
    /// Return list of files from new directory
    ///
    ///
    /// <returns>
    private string[] GetNewVersionFileList(string newVersionPath )
    {
      
string[] newList = Directory.GetFiles(newVersionPath);
      
return newList;
    }

    
/// <summary>
    /// Return array of files from current version
    ///
    /// <returns>
    private string[] GetCurrentVersionFileList(string currVersionPath)
    {
      
string[] currList = Directory.GetFiles(currVersionPath);
      
return currList;
    }
    
private string GetAppPath()
    {
      
return System.Reflection.Assembly.GetExecutingAssembly().Location;
    }


    #endregion

  }
}

Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing