Click here to Skip to main content
15,894,343 members
Articles / Programming Languages / C# 4.0

Installer and Patching Program using Visual Studio 2010

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
8 Nov 2010CPOL18 min read 66.5K   2.2K   64  
A How-To Guide for replacing the integrated ClickOnce technology built into Visual Studio, and control the install/update process using Visual Studio tools
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Security.Permissions;
using System.IO;

using SelfUpdatingSettings;
using SelfUpdatingSettings.Classes;
using System.Drawing.Drawing2D;
using System.Security.Cryptography;
using ICSharpCode.SharpZipLib.Zip;

namespace SelfUpdatingPatcher
{
    public partial class PatcherDialogBox : Form
    {
        public PatcherDialogBox()
        {
            InitializeComponent();

            try
            {
                // Ensure application is only run local to web by just checking for local access to directory
                DirectoryInfo r = new DirectoryInfo(Updater.Default.RepositoryExistsPath);
                if (r.Exists)
                {
                    DialogResult = System.Windows.Forms.DialogResult.OK;
                }
                else
                {
                    MessageBox.Show("Repository Cannot Run Outside of Servers, need local access to " + Updater.Default.RepositoryExistsPath,
                        "Failed to Run", MessageBoxButtons.OK);
                    this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
                }
            }
            catch (Exception exd)
            {
                MessageBox.Show("Repository Cannot Run Outside of Servers, need local access to " + Updater.Default.RepositoryExistsPath,
                    "Failed to Run", MessageBoxButtons.OK);
                MessageBox.Show(exd.StackTrace, exd.Message, MessageBoxButtons.OK);
                this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
            }
        }

        #region Form-centric events

        private void PatcherDialogBox_Load(object sender, EventArgs e)
        {
            // Refuse to open form if there's a problem
            if (this.DialogResult == System.Windows.Forms.DialogResult.Cancel)
            {
                this.Close();
            }
            try
            {
                initFileTable();
                obtainSettingValues();
                webBrowser.Visible = false;
                webBrowser.SetBounds(listResults.Bounds.X, listResults.Bounds.Y, listResults.Bounds.Width, listResults.Bounds.Height);
            }
            catch (Exception ex)
            {
                listResults.Items.Add(ex.Message + "\r\n" + ex.StackTrace.ToString());
            }
            versionUtil = new AssemblyInfoUtil(Updater.Default.VersionFile);
            version1Tbox.Value = versionUtil.Version1;
            version2Tbox.Value = versionUtil.Version2;
            version3Tbox.Value = versionUtil.Version3;
            version4Tbox.Value = versionUtil.Version4;
            version1Tbox.ValueChanged += versionTbox_ValueChanged;
            version2Tbox.ValueChanged += versionTbox_ValueChanged;
            version3Tbox.ValueChanged += versionTbox_ValueChanged;
            version4Tbox.ValueChanged += versionTbox_ValueChanged;
        }

        private void PatcherDialogBox_Paint(object sender, PaintEventArgs e)
        {
            Rectangle BaseRectangle =
                new Rectangle(0, 0, this.Width - 1, this.Height - 1);

            Brush Gradient_Brush =
                new LinearGradientBrush(
                BaseRectangle,
                Color.DarkOrange, Color.LightSlateGray,
                LinearGradientMode.Vertical);
            e.Graphics.FillRectangle(Gradient_Brush, BaseRectangle);
        }

        private void PatcherDialogBox_Resize(object sender, EventArgs e)
        {
            // Invalidate, or last rendered image will just be scaled
            // to new size
            this.Invalidate();
        }

        #endregion

        #region Button-centric events

        private void btnCancel_Click(object sender, EventArgs e)
        {
            DialogResult = System.Windows.Forms.DialogResult.Cancel;
            this.Close();
        }

        private void btnBeta_Click(object sender, EventArgs e)
        {
            listResults.Items.Add("Clicked Beta to Push");
            btnBeta.BackColor = Color.LightGreen;
            btnBeta.Refresh();
            btnProduction.BackColor = DefaultBackColor;
            btnProduction.Refresh();
            makeListResultsVisible();
            versionUtil.WriteFile();
            executePushToRepository(ExecuteModeEnum.Beta);
        }

        private void btnProduction_Click(object sender, EventArgs e)
        {
            listResults.Items.Add("Clicked Production to Push");
            DialogResult dialogResult = MessageBox.Show("Confirm patching of PRODUCTION?", "Patcher Question", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            if (dialogResult == DialogResult.OK)
            {
                btnBeta.BackColor = DefaultBackColor;
                btnBeta.Refresh();
                btnProduction.BackColor = Color.LightGreen;
                btnProduction.Refresh();
                makeListResultsVisible();
                versionUtil.WriteFile();
                executePushToRepository(ExecuteModeEnum.Production);
                if (MessageBox.Show(this, "Beta will now be pushed to ensure uniformity.", "Pushing Beta", MessageBoxButtons.OKCancel) ==
                         System.Windows.Forms.DialogResult.OK)
                    btnBeta_Click(null, null);
            }
            else
            {
                listResults.Items.Add("Production Push not Confirmed");
            }
        }

        private void btnViewRepositorySite_Click(object sender, EventArgs e)
        {
            Cursor.Current = Cursors.WaitCursor;
            if (btnViewRepositorySite.Tag == null)
            {
                listResults.Visible = false;
                webBrowser.Visible = true;
                webBrowser.SetBounds(listResults.Bounds.X, listResults.Bounds.Y, listResults.Bounds.Width, listResults.Bounds.Height);
                webBrowser.Navigate(Updater.Default.UriRepositoryDefault);
                btnViewRepositorySite.Text = "Hide &Repository Site";
                btnViewRepositorySite.Tag = "Hide";
                btnViewRepositorySite.BackColor = Color.PaleTurquoise;
                btnViewRepositorySite.Refresh();
                btnViewTempFolder.BackColor = DefaultBackColor;
                btnViewTempFolder.Refresh();
            }
            else
            {
                makeListResultsVisible();
            }
            Cursor.Current = Cursors.Default;
        }

        private void btnViewTempFolder_Click(object sender, EventArgs e)
        {
            btnViewRepositorySite.BackColor = DefaultBackColor;
            btnViewRepositorySite.Refresh();
            btnViewTempFolder.BackColor = Color.PaleTurquoise;
            btnViewTempFolder.Refresh();
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.InitialDirectory = pathTempCompany;
            openFileDialog.Filter = "All files (*.*)|*.*";
            openFileDialog.ShowDialog();
        }

        #endregion

        #region Form-level variables

        private string appFolder = string.Empty;
        private string companyName = string.Empty;
        private string repositoryFolder = string.Empty;
        private string launcherFolder = string.Empty;
        private string setupFolder = string.Empty;
        private string softwareManifestFileName = string.Empty;
        private DirectoryInfo projReleaseDirectoryInfo;
        private DirectoryInfo projSetupDirectoryInfo;
        private DirectoryInfo projLauncherDirectoryInfo;
        private DirectoryInfo repositoryInfo;
        private string pathTempCompany = string.Empty;
        private AssemblyInfoUtil versionUtil = null;

        private DataTable fileTable = new DataTable("Files");

        #endregion

        /// <summary>
        /// Mainline Logic to invoke upon clicking of a Production/Beta button
        /// </summary>
        /// <param name="chosenVersionToPush">Execution Mode</param>
        private void executePushToRepository(ExecuteModeEnum chosenVersionToPush)
        {
            Cursor.Current = Cursors.WaitCursor;
            try
            {
                switch (chosenVersionToPush)
                {
                    case ExecuteModeEnum.Beta:
                        appFolder = Updater.Default.AppSubFolderBeta;
                        repositoryFolder = Updater.Default.RepositoryDefault + @"\" + Updater.Default.AppSubFolderBeta;
                        break;
                    case ExecuteModeEnum.Production:
                        appFolder = Updater.Default.AppSubFolderProd;
                        repositoryFolder = Updater.Default.RepositoryDefault + @"\" + Updater.Default.AppSubFolderProd;
                        break;
                }

                initFileTable();
                DataSet dataSet = new DataSet("SoftwareManifest");

                // Make sure external Repository exists
                if (!repositoryInfo.Exists)
                    repositoryInfo.Create();

                // walk through the chosen app folder
                // and zip the files therein to the appropriate temp folder

                if (projReleaseDirectoryInfo.Exists)
                {
                    if (projSetupDirectoryInfo.Exists)
                    {
                        // Make sure temp folder is empty so we don't undo a previous push
                        DirectoryInfo di = new DirectoryInfo(pathTempCompany);
                        if (di.Exists)
                        {
                            foreach (DirectoryInfo prevDirectoryInfo in di.GetDirectories())
                            {
                                if (prevDirectoryInfo.Exists)
                                    prevDirectoryInfo.Delete(true);
                            }
                        }
                        // zip the files in the main application folder
                        zipFilesInFolder(projReleaseDirectoryInfo.FullName, pathTempCompany + "\\" + appFolder, projReleaseDirectoryInfo, true, true);
                        listResults.Items.Add(" ");

                        // zip the files in the setup Folder (msi will be skipped)
                        zipFilesInFolder(projSetupDirectoryInfo.FullName, pathTempCompany + "\\" + appFolder + "\\" + setupFolder, projSetupDirectoryInfo, true, true);
                        listResults.Items.Add(" ");

                        // create and zip the software manifest file
                        string manifestFile = pathTempCompany + "\\" + appFolder + "\\" + softwareManifestFileName;
                        if (File.Exists(manifestFile))
                        {
                            File.Delete(manifestFile);
                        }
                        listResults.Items.Add("Creating Software Manifest File:  " + softwareManifestFileName);
                        listResults.Items.Add(" ");
                        dataSet.Tables.Add(fileTable);
                        dataSet.WriteXml(manifestFile);

                        // Write files to repository folder (cleaned above)
                        UpdateRepository(repositoryInfo);
                        listResults.Items.Add("Program Completed...");
                        listResults.Items.Add(" ");
                    }
                    else
                    {
                        listResults.Items.Add("ProjSetupFolder| " + projSetupDirectoryInfo.FullName + " does not exist!");
                    }
                }
                else
                {
                    listResults.Items.Add("ProjReleaseFolder| " + projReleaseDirectoryInfo.FullName + " does not exist!");
                }

            }
            catch (Exception ex)
            {
                listResults.Items.Add(ex.Message);
                listResults.Items.Add(ex.StackTrace.ToString());
            }
            listResults.Refresh();
            listResults.TopIndex = listResults.Items.Count - 1;
            Cursor.Current = Cursors.Default;
        }

        /// <summary>
        /// Clear Repository, Transfer Directorys, then Transfer Files
        /// </summary>
        /// <param name="repositoryInfo">Repository DirectoryInfo</param>
        private void UpdateRepository(DirectoryInfo repositoryInfo)
        {

            // 1) Create directories in Repository and wipe out any existing data
            listResults.Items.Add("Clearing Existing Repository: " + repositoryInfo.FullName);
            DirectoryInfo updateDir = new DirectoryInfo(repositoryFolder);
            if (updateDir.Exists)
            {
                listResults.Items.Add("  Deleting Directory and SubDirectories: " + updateDir.FullName);
                updateDir.Delete(true);
            }
            listResults.Items.Add(" ");

            listResults.Items.Add("Transferring Directories");
            DirectoryInfo workingDir = new DirectoryInfo(pathTempCompany);

            TransferDirectories(workingDir, workingDir.FullName);
            TransferFiles(workingDir, workingDir.FullName);
        }

        /// <summary>
        /// Move the entire directories and files to the target Repository
        /// </summary>
        /// <param name="directoryInfo">Source DirectoryInfo</param>
        /// <param name="baseDir">The To Directory Target</param>
        private void TransferDirectories(DirectoryInfo directoryInfo, string baseDir)
        {
            DirectoryInfo workingDirectoryInfo;
            string workingDir = string.Empty;
            foreach (DirectoryInfo di in directoryInfo.GetDirectories())
            {
                workingDir = Updater.Default.RepositoryDefault + di.FullName.Remove(0, baseDir.Length + 1);
                workingDirectoryInfo = new DirectoryInfo(workingDir);
                if (!workingDirectoryInfo.Exists)
                    workingDirectoryInfo.Create();
                TransferDirectories(di, baseDir);
                listResults.Items.Add("  Transferred Directory: " + di.FullName);
                TransferFiles(di, baseDir);
                listResults.Items.Add(" Transferred Files from " + di.FullName);
                listResults.Items.Add(" ");
                listResults.Refresh();
                listResults.TopIndex = listResults.Items.Count - 1;
            }
        }

        /// <summary>
        /// Transfer files from the DirectoryInfo to the baseDirectory Repository
        /// </summary>
        /// <param name="directoryInfo">The From Directory to copy files</param>
        /// <param name="baseDir">The To Directory target</param>
        private void TransferFiles(DirectoryInfo directoryInfo, string baseDir)
        {
            FileInfo workingFile;
            string workingDir = string.Empty;
            foreach (FileInfo f in directoryInfo.GetFiles())
            {
                workingFile = new FileInfo(f.FullName);
                workingDir = workingFile.FullName.Remove(0, baseDir.Length + 1);
                workingDir = Updater.Default.RepositoryDefault + workingDir;
                workingFile.CopyTo(workingDir);
            }
        }

        /// <summary>
        /// Create File Table with the appropriate columns
        /// </summary>
        private void initFileTable()
        {
            fileTable = new DataTable("Files");
            fileTable.Columns.Add(new DataColumn("sourcePathName", typeof(string)));
            fileTable.Columns.Add(new DataColumn("sourceFileName", typeof(string)));
            fileTable.Columns.Add(new DataColumn("zippedPathName", typeof(string)));
            fileTable.Columns.Add(new DataColumn("zippedFileName", typeof(string)));
            fileTable.Columns.Add(new DataColumn("md5HashedValue", typeof(string)));
        }

        /// <summary>
        /// Make the listResults listbox visible and set the Text, Tag, and BAckColor of various buttons as needed
        /// </summary>
        private void makeListResultsVisible()
        {
            webBrowser.Visible = false;
            webBrowser.Navigate(string.Empty);
            listResults.Visible = true;
            btnViewRepositorySite.Text = "View &Repository Site";
            btnViewRepositorySite.Tag = null;
            btnViewRepositorySite.BackColor = DefaultBackColor;
            btnViewRepositorySite.Refresh();
            btnViewTempFolder.BackColor = DefaultBackColor;
            btnViewTempFolder.Refresh();
        }

        /// <summary>
        /// Obtain and Manipulate the program's settings from misc. values
        /// </summary>
        private void obtainSettingValues()
        {
            string pathTempFolder = string.Empty;
            try
            {
                listResults.Items.Clear();

                // By putting the setup in the beta/prod folders it allows you to process a "Beta" setup file without
                // touching users in production
                setupFolder = Updater.Default.AppSubFolderSetup;

                // This is the xml file managing your patches
                softwareManifestFileName = Updater.Default.SoftwareManifestFileName;

                // local path to your release project
                projReleaseDirectoryInfo = new DirectoryInfo(Updater.Default.ProjecctReleaseFolder);

                // local path to your Setup Project
                projSetupDirectoryInfo = new DirectoryInfo(Updater.Default.SetupReleaseFolder);
                
                // local path to your repository
                repositoryInfo = new DirectoryInfo(Updater.Default.RepositoryDefault);

                // create folders and zip files
                // as needed in a temporary folder

                pathTempFolder = Path.GetTempPath();
                DirectoryInfo di = new DirectoryInfo(pathTempFolder);

                pathTempCompany = pathTempFolder + Updater.Default.CompanyName;
                di = new DirectoryInfo(pathTempCompany);
                if (di.Exists)
                {
                    di.Delete(true);
                }
                di.Create();
            }
            catch (Exception ex)
            {
                listResults.Items.Add(ex.Message + "\r\n" + ex.StackTrace.ToString());
            }
        }

        /// <summary>
        /// Zip all of the files in the source folder to the specified temporary folder deriving MD5 hash values along the way, saving them to a datatable for later usage in outputting the manifest XML file.
        /// </summary>
        /// <param name="sourceFolderFullName">Source Folder Full Name</param>
        /// <param name="tempFolder">Temporary Folder Path</param>
        /// <param name="directoryInfo">DirectoryInfo object</param>
        /// <param name="omitVsHostExecutables">Whether to omit unneeded Visual Studio files</param>
        /// <param name="dontZipInstallers">Whether or not to zip the MSI file</param>
        private void zipFilesInFolder(string sourceFolderFullName, string tempFolder, DirectoryInfo directoryInfo, bool omitVsHostExecutables, bool dontZipInstallers)
        {
            string targetSubFolderName = directoryInfo.FullName.Substring(sourceFolderFullName.Length);
            while (targetSubFolderName.StartsWith("\\"))
            {
                targetSubFolderName = targetSubFolderName.Substring(1, targetSubFolderName.Length - 1);
            }
            string targetFolderName = tempFolder + "\\" + targetSubFolderName;
            while (targetFolderName.EndsWith("\\"))
            {
                targetFolderName = targetFolderName.Substring(0, targetFolderName.Length - 1);
            }
            // zip each file/sub-folder in the present folder
            foreach (FileInfo fi in directoryInfo.GetFiles())
            {
                // Get MD5 Hash
                string md5Hash = string.Empty;
                using (FileStream oStream = System.IO.File.OpenRead(fi.FullName))
                {
                    byte[] obuffer = new byte[oStream.Length];
                    oStream.Read(obuffer, 0, obuffer.Length);
                    byte[] hashValue = new MD5CryptoServiceProvider().ComputeHash(obuffer);
                    md5Hash = BitConverter.ToString(hashValue).ToLower();
                    oStream.Close();
                }

                // if .vshost executables are allowed
                // or the filename does not contain "vshost"
                // zip or copy the file as needed
                if (!omitVsHostExecutables || !fi.Name.ToLower().Contains("vshost"))
                {
                    // if MSI installers are not to be zipped
                    // then copy them unmolested to the target folder
                    if (dontZipInstallers && fi.Name.ToLower().EndsWith(".msi"))
                    {
                        string tgtFileName = targetFolderName + "\\" + fi.Name;
                        if (File.Exists(tgtFileName))
                        {
                            File.Delete(tgtFileName);
                        }
                        FileInfo tgtFileInfo = new FileInfo(tgtFileName);
                        if (!tgtFileInfo.Directory.Exists)
                        {
                            Directory.CreateDirectory(tgtFileInfo.Directory.FullName);
                        }
                        fi.CopyTo(tgtFileName);

                        addFileRow(tgtFileInfo.Directory.FullName.Substring(pathTempCompany.Length), fi.Name,
                            tgtFileInfo.Directory.FullName.Substring(pathTempCompany.Length), tgtFileInfo.Name,
                            md5Hash, 0);
                    }
                    //otherwise
                    else
                    {
                        listResults.Items.Add("Zipping file " + fi.FullName);
                        string zipFileName = targetFolderName + "\\" + fi.Name + ".zip";
                        listResults.Items.Add("    into " + zipFileName + "...");
                        listResults.Items.Add(" ");
                        listResults.Refresh();
                        listResults.TopIndex = listResults.Items.Count - 1;
                        if (File.Exists(zipFileName))
                        {
                            File.Delete(zipFileName);
                        }
                        FileInfo zipFileInfo = new FileInfo(zipFileName);
                        if (!zipFileInfo.Directory.Exists)
                        {
                            Directory.CreateDirectory(zipFileInfo.Directory.FullName);
                        }
                        using (ZipOutputStream oZipStream = new ZipOutputStream(File.Create(zipFileName)))
                        {
                            oZipStream.Password = Updater.Default.ZipFilePassword;
                            oZipStream.PutNextEntry(new ZipEntry(fi.Name));

                            FileStream oStream = File.OpenRead(fi.FullName);
                            byte[] obuffer = new byte[oStream.Length];
                            oStream.Read(obuffer, 0, obuffer.Length);

                            addFileRow(zipFileInfo.Directory.FullName.Substring(pathTempCompany.Length), fi.Name,
                                zipFileInfo.Directory.FullName.Substring(pathTempCompany.Length), zipFileInfo.Name,
                                md5Hash);

                            oZipStream.Write(obuffer, 0, obuffer.Length);
                            oZipStream.Finish();
                            oZipStream.Close();
                            oStream.Close();
                        }
                    }
                }
            }
            foreach (DirectoryInfo di in directoryInfo.GetDirectories())
            {
                zipFilesInFolder(sourceFolderFullName, tempFolder, di, omitVsHostExecutables, dontZipInstallers);
            }
        }

        private void addFileRow(string sourcePathName, string sourceFileName, string zippedPathName, string zippedFileName, 
            string md5hashedValue,  int insertLoc = -1)
        {
            DataRow dataRow = fileTable.NewRow();
            dataRow["sourcePathName"] = sourcePathName;
            dataRow["sourceFileName"] = sourceFileName;
            dataRow["zippedPathName"] = zippedPathName;
            dataRow["zippedFileName"] = zippedFileName;
            dataRow["md5hashedValue"] = md5hashedValue;

            if (insertLoc > -1)
                fileTable.Rows.InsertAt(dataRow, insertLoc);
            else
                fileTable.Rows.Add(dataRow);
        }

        /// <summary>
        /// Update AssemblyInfoUtil class Version Numbers
        /// </summary>
        private void versionTbox_ValueChanged(object sender, EventArgs e)
        {
            if (!versionUtil.fileisDirty)
                versionUtil.fileisDirty = true;
            if (version1Tbox.Value != versionUtil.Version1)
                versionUtil.Version1 = (int)(version1Tbox.Value);
            if (version2Tbox.Value != versionUtil.Version2)
                versionUtil.Version2 = (int)(version2Tbox.Value);
            if (version3Tbox.Value != versionUtil.Version3)
                versionUtil.Version3 = (int)(version3Tbox.Value);
            if (version4Tbox.Value != versionUtil.Version4)
                versionUtil.Version4 = (int)(version4Tbox.Value);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Other
United States United States
I am a self-taught programmer. I attended Tarleton State College studying Computer Science but did not finish. I've teetered throughout my career as management and programmer and have an interesting outlook on projects due to the experience. I've developed by myself and within a team numerous projects encompassing ASP.NET, .NET 2.0,3.5, and 4.0 using C# and VB.NET, WCF, WPF, Web Services, and one project in Java.

My philosophy on code is simple and two-fold;
1) What is fastest to the user.
2) What is the easiest and simplest to maintain.

Comments and Discussions