How can I allow the user to install a Self-Updating application wherever they choose, and still have the AppStart.exe.config file point to the right folder? When I build the app, the AppStart.exe.config has a hard-coded folder in it.
This is the sixth article that I have written in a series on the Updater Application Block. The code shown in Figure 1 is complete except that it references an XML Document Wrapper Class that I wrote for my self-updating application.
To overide the hard-coded path, I have made a modification to the AppStart.exe program, which comes with the Microsoft Application Block. The reason I have changed from a Custom Action program, which worked fine on the initial installation, is that it only works on the initial installation. It does not work for future updates because they do not involve an install and therefore, there is no place for a Custom Action to run.
This article shows the code for the class that updates the paths. First I must modify the AppStart.cs class to call the new code. That modification to the code is shown below. It is placed in the constructor for the AppStart class.
| static AppStart() { // New code by to modify the appcofig for this app and // the selfupdating app because we don't know where the user instatlled // us and the paths for the updater in the two respective config files // must be updated to reflect the user chosen install path, before the // AppStart code builds its _config object from the config file. if (!UpdateAppStartConfig.UpdateAppConfigPaths()) { MessageBox.Show( "AppStart cannot start the main application.", "AppStart Shim"); Environment.Exit(1); } // grab our config instance which we use to read app.config params, // figure out WHERE our target app is and WHAT version _config = (AppStartConfiguration)ConfigurationSettings.GetConfig( "appStart" ); } |
| using System; using System.Windows.Forms; using System.IO; using System.Diagnostics; using XMLConfigurationHandler; namespace UpdateAppStartConfig { /// <summary> /// This class represents a type of CustomAction that will /// be executed at the start of the AppStart Exe. Its purpose is to /// update the AppStart.exe.cofig file to point to the current /// version of the real application, regardless of where the user /// installed it. AppStart.exe is a shim application that the user /// will always execute to start the real application. The config /// file will point to the latest version of the application and is updated /// by the Updater Application Block. /// </summary> public class UpdateAppStartConfig { public UpdateAppStartConfig() { // // no constructor is called on static method class // } /// <summary> /// Application Starts Here with no UI. /// </summary> public static bool UpdateAppConfigPaths() { // call the update method string msg=""; bool b = UpdateAppStartPathToInstallPath(ref msg); if (!b) { return false; MessageBox.Show(msg); } else return true; } private static XMLConfigurationHandler.XMLConfigurationHandler xCH; /// <summary> /// This method will determine where the selfupdating app was /// installed and update the AppStart.exe.config to point to the /// current version of the exe to start. /// get the path to the appstart.exe.config file /// the only problem is that we can't use our appconfig to point /// to appstart.exe.config, because we don't know the absolute path /// to where it is at build time. That's why this custom action is /// running, Hello? So...we must use a path computed from where I /// am executing, as is AppStart located. /// the currently executing assembly is found by calling GetApppath. /// </summary> /// <returns></returns> private static bool UpdateAppStartPathToInstallPath(ref string msg) { try { // xPath to the keys in AppStart.exe.config string xPathToAppFolder = "//appStart/ClientApplicationInfo/appFolderName"; string xPathToAppName ="//appStart/ClientApplicationInfo/appExeName"; string xPathToInstalledVersion = "//appStart/ClientApplicationInfo/installedVersion"; // get my 20, in cb lingo, my location, where i be at string myPath = Path.GetDirectoryName(GetAppPath()); string appStartConfigPath = Path.Combine(myPath,"AppStart.exe.config"); // add the folder where the first version will be from my appconfig // (custom action config) // now get new xml wrapper to update the appstart.exe.config xCH = new InfoPro.XMLConfigurationHandler.XMLConfigurationHandler(appStartConfigPath); string myAppFolder = xCH.GetSingleNode(xPathToAppFolder); string installedVersion = xCH.GetSingleNode(xPathToInstalledVersion); string appName = Path.GetFileNameWithoutExtension(xCH.GetSingleNode(xPathToAppName)); string installedAppPath = Path.Combine(myPath,installedVersion); string appStartConfig = xCH.GetSingleNode(xPathToAppFolder); // next, replace the built path to the app with the // installed path in the appstart.exe.config if(! xCH.ReplaceSingleNodeValue(xPathToAppFolder,installedAppPath)) { msg= "Could not replace appFolder in AppStart.exe.config."; return false; } // now find the app cofig file for the main app so we can // update stuff in it..it will be at my folder + installedVersion string installedVersionFolder = Path.Combine(myPath,installedVersion); string mainAppConfigPath = Path.Combine(installedVersionFolder,appName + ".exe.config"); xCH = new InfoPro.XMLConfigurationHandler.XMLConfigurationHandler(mainAppConfigPath); // we must update these keys in the main appconfig // <client> // // replace baseDir with myPath string xPathBase = "//appUpdater/UpdaterConfiguration/application/client/"; string xPathToBaseDir = xPathBase + "baseDir"; if (!xCH.ReplaceSingleNodeValue(xPathToBaseDir,myPath)) { msg= "Could not replace path to baseDir in main app config."; return false; } // <xmlFile>C:\Self Updating Application\DemoApp\AppStart.exe.config string xPathToxmlFile = xPathBase + "xmlFile"; string pathToAppStartConfigFile = Path.Combine(myPath,"AppStart.exe.config"); if (!xCH.ReplaceSingleNodeValue(xPathToxmlFile ,pathToAppStartConfigFile)) { msg = "Could not replace path to xmlFile in main app config."; return false; } // <tempDir>C:\Self Updating Application\DemoApp\temp string xPathTotempDir = xPathBase + "tempDir"; string pathToTempDir = Path.Combine(myPath, "temp"); if (!xCH.ReplaceSingleNodeValue(xPathTotempDir,pathToTempDir)) { msg = "Could not replace path to temp folder in main app config."; return false; } // <xmlFileDest>C:\Self Updating Application\DemoApp\ServerManifest.xml string xPathToXmlFileDest="//application/server/xmlFileDest"; string xmlFileDest = Path.Combine(myPath,"ServerManifest.xml"); if (!xCH.ReplaceSingleNodeValue(xPathToXmlFileDest,xmlFileDest)) { msg = "Could not replace path to Server Manifest in main app config."; return false; } // insert loglistener path string xPathToLogListener = "//logListener/@logPath"; string logListener = xCH.GetSingleNode(xPathToLogListener); // logPath="C:\Self Updating Application\DemoApp\UpdaterLog.txt string logPath = Path.Combine(myPath, "UpdaterLog.txt"); //string pathToLogListener = Path.Combine(myPath, logPath); if (!xCH.ReplaceSingleNodeValue(xPathToLogListener, logPath)) { msg = "Could not replace path to UpdaterLog.txt in main app config."; return false; } msg = "Configuration files successfully updated."; return true; } catch(System.Exception ex) { Debug.WriteLine(ex.ToString()); msg = "Exception in Custom Action."; return false; } } /// <summary> /// Return the path to the currently executing assembly. This /// will be a dll if this is called from a dll and not the host /// exe. /// </summary> /// <returns></returns> private static string GetAppPath() { return System.Reflection.Assembly.GetExecutingAssembly().Location; } } } |
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startingVersionFolderName>1.0.0.0</startingVersionFolderName> </configuration> |