Click here to Skip to main content
15,881,757 members
Articles / Desktop Programming / Windows Forms

Visual Application Launcher

Rate me:
Please Sign up or sign in to vote.
4.91/5 (37 votes)
23 Jan 2012CPOL23 min read 106.9K   3.7K   116  
A WinForms UI using WCF services, Entity Framework, repository data access, repository caching, Unit of Work, Dependency Injection, and every other buzz work you can think of!
namespace VAL
{
    using System;
    using System.Diagnostics;
    using VAL.Model;
    using VAL.Model.Entities;
    using VAL.Core;
    using VAL.Contracts;
    using System.Threading.Tasks;

    /// <summary>
    /// The Launcher class starts new processes.
    /// </summary>
    internal sealed class Launcher
    {
        /// <summary>
        /// Access to the log4Net logging object
        /// </summary>
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private const int MaximumWaitForIdleTime = 5000;
        private const string WhiteSpace = " ";

        /// <summary>
        /// Set to true the first time we find the Access Executable, so we
        /// don't have to attempt File.Exists each time we launch a 
        /// database
        /// </summary>
        private static bool accessExists;

        #region Access Switches Class

        /// <summary>
        /// This class contains all of the command line switches that are used by Microsoft
        /// Access when launching databases
        /// </summary>
        private class AccessSwitches
        {
            /// <summary>
            /// Opens the specified Access database for exclusive access. To open the database for shared access in a multiuser environment, omit this option. Applies to Access databases only. "
            /// </summary>
            public const string Exclusive = "/excl";

            /// <summary>
            /// Opens the specified Access database or Access project for read-only access. 
            /// </summary>
            public const string ReadOnly = "/ro";

            /// <summary>
            /// User name Starts Access by using the specified user name. Applies to Access databases only. 
            /// </summary>
            public const string UserName = "/user";

            /// <summary>
            /// Password Starts Access by using the specified password. Applies to Access databases only. 
            /// </summary>
            public const string Password = "/pwd";

            /// <summary>
            /// User profile Starts Access by using the options in the specified user profile instead of the standard Windows Registry settings created when you installed Microsoft Access. This replaces the /ini option used in versions of Microsoft Access prior to Access 97 to specify an initialization file.
            /// </summary>
            public const string Profile = "/profile";

            /// <summary>
            /// Target database or target Access project Compacts and repairs the Access database, or compacts the Access project that was specified before the /compact option, and then closes Access. If you omit a target file name following the /compact option, the file is compacted to the original name and folder. To compact to a different name, specify a target file. If you don't include a path in target database or target Access project, the target file is created in your My Documents folder by default. 
            /// In an Access project, this option compacts the Access project (.adp) file but not the Microsoft SQL Server database.
            /// </summary>
            public const string Compact = "/compact";

            /// <summary>
            ///  Repairs the Access database that was specified before the /repair option, and then closes Microsoft Access. In Microsoft Access 2000 or later, compact and repair functionality is combined under /compact. The /repair option is supported for backward compatibility.
            /// </summary>
            public const string Repair = "/repair";

            /// <summary>
            /// Starts Access and runs the specified macro. Another way to run a macro when you open a database is to use an AutoExec macro. 
            /// </summary>
            public const string Macro = "/x";

            /// <summary>
            /// Specifies that what follows on the command line is the value that will be returned by the Command function. This option must be the last option on the command line. You can use a semicolon (;) as an alternative to /cmd. 
            /// Use this option to specify a command-line argument that can be used in Visual Basic code.
            /// </summary>
            public const string Command = "/cmd";

            /// <summary>
            /// Starts Access by using the specified workgroup information file. Applies to Access databases only. 
            /// </summary>
            public const string Workgroup = "/wrkgrp ";
        }

        #endregion


        /// <summary>
        /// No construct
        /// </summary>
        private Launcher() { }

        /// <summary>
        /// Try and find access
        /// </summary>
        /// <param name="directory"></param>
        /// <returns></returns>
        private static string FindAccess(string directory)
        {
            string microsoftAccess = string.Empty;

            try
            {
                foreach (string d in System.IO.Directory.GetDirectories(directory))
                {
                    string[] f = System.IO.Directory.GetFiles(d, "msaccess.exe");
                    if (f.Length > 0)
                        microsoftAccess = f[0];

                    if (microsoftAccess.Length > 0)
                        break;

                    FindAccess(d);
                }
            }
            catch
            {
            }

            return microsoftAccess;
        }

        /// <summary>
        /// Launches a new process
        /// </summary>
        /// <param name="file">The <see cref="File"/> to launch</param>
        public static void LaunchFile(File file)
        {
            ProcessStartInfo psi = new ProcessStartInfo();

            FileTypeEnum filetype = (FileTypeEnum)file.FileTypeID;

            bool requiresDistribution = (file.DistributeToUserProfile || 
                !string.IsNullOrEmpty(file.DistributeTo));

            string filePath = file.Path;

            // See if we are distributing a local copy
            if (requiresDistribution)
            {
                filePath = System.IO.Path.GetDirectoryName(DistributeFile(file)) +
                    System.IO.Path.DirectorySeparatorChar.ToString();
            }

            switch (filetype)
            {
                // If this is a database, we need to launch it via the 
                // Access executable file. Therefore, the arguments are file name, workgroup etc
                case FileTypeEnum.Database:

                    #region Find Access Executable
                    
                    // Get the location from config, test that it exists
                    if (!accessExists)
                    {
                        bool requiresSearch = true;
                        if (!string.IsNullOrEmpty(UserOptions.Current.AccessExecutableLocation))
                        {
                            requiresSearch = !System.IO.File.Exists(UserOptions.Current.AccessExecutableLocation);
                        }

                        // If we don't have a setting yet or the filename currently stored in the user settings
                        // file doesn't exist (user may have signed on to another computer since last launch of VAL)
                        // then we need to try and find Access
                        if (requiresSearch)
                        {
                            UserOptions.Current.AccessExecutableLocation = FindAccess(Session.Configuration.MicrosoftOfficeLocation);

                            // If we still haven't got a setting after searching, we can't launch Access databases
                            if (string.IsNullOrEmpty(UserOptions.Current.AccessExecutableLocation))
                            {
                                throw new System.IO.FileNotFoundException("Microsoft Access cannot be located in any sub-directory of '" 
                                    + Session.Configuration.MicrosoftOfficeLocation + "', you cannot launch Access databases");
                            }
                        }
                        accessExists = true;
                    }
                    #endregion

                    // Set up the access exe as the main Win32 executable target
                    psi.FileName = UserOptions.Current.AccessExecutableLocation;

                    // Now add the database to open it with
                    psi.Arguments += @"""" + String.Concat(filePath, file.Name) + @"""";

                    // See if we're using a workgroup
                    if (!string.IsNullOrEmpty(file.Workgroup))
                    {
                        // Add in the workgroup info - if we're set to distribute workgroups then we
                        // should launch via the local copy
                        if (Session.Configuration.DistributeWorkgroups && requiresDistribution)
                        {
                            psi.Arguments += (AccessSwitches.Workgroup +
                                String.Concat(filePath + System.IO.Path.GetFileName(file.Workgroup)));
                        }
                        else
                        {
                            psi.Arguments += (AccessSwitches.Workgroup + file.Workgroup);
                        }

                    }

                    // See if the user wants to autosign on
                    if (!string.IsNullOrEmpty(file.Workgroup) && UserOptions.Current.AutoSignOn)
                    {
                        psi.Arguments += WhiteSpace + (AccessSwitches.UserName + WhiteSpace + UserOptions.Current.UserName);
                        psi.Arguments += WhiteSpace + (AccessSwitches.Password + WhiteSpace + UserOptions.Current.GetDecryptedPassword());

                    }

                    break;

                default:
                    // Otherwise, it must be an executable file type that we can
                    // just start up with no probs!
                    psi.FileName = String.Concat(filePath, file.Name);
                    if (!string.IsNullOrEmpty(file.Command))
                    {
                        psi.Arguments += WhiteSpace + file.Command;
                    }
                    break;
            }

            psi.UseShellExecute = true;
            psi.WindowStyle = ProcessWindowStyle.Normal;
            psi.ErrorDialog = true;

            if (!System.IO.File.Exists(psi.FileName))
            {
                throw new System.IO.FileNotFoundException("The file '" + psi.FileName + "' could not be found");
            }

            Process proc = Process.Start(psi);

            try
            {
                proc.WaitForInputIdle(MaximumWaitForIdleTime);
            }
            catch (InvalidOperationException)
            {
                // Not much we can do about this...probably no UI on the application?
            }
           
            #region Reporting Activity
            if (Session.Configuration.IsReportingEnabled)
            {
                var task = Task.Factory.StartNew(() =>
                {
                    // Log the file launched by the user
                    var activity = new UserActivity
                    {
                        FileID = file.Id,
                        UserID = Session.CurrentUser.Id,
                        LaunchDatetime = System.DateTime.Now,
                        LaunchedFrom = Environment.MachineName
                    };
                    try
                    {
                        using (var service = new VAL.ClientServices.UserServiceClient())
                        {
                            service.RecordUserActivity(activity);
                        }                        
                    }
                    catch (Exception ex)
                    {
                        // Just log this - it's only a problem with reporting the 
                        // data so we'll just silently log + continue
                        log.Error(ex.Message);
                    }

                });


            }
            #endregion
        }

        /// <summary>
        /// Method that helps with distributing files from their main location to another location that is 'local' to the user. 
        /// </summary>
        /// <remarks>
        /// In many cases, there will be a network version of an Access database that 
        /// each user will have their own front-end copy of. This class checks the local
        /// copy and distributes the network version if it is newer
        /// </remarks>
        /// <param name="file">The instance of the file to distribute</param>
        private static string DistributeFile(File file)
        {
            string targetDirectory = string.Empty;
            string applicationName = 
                System.IO.Path.GetFileNameWithoutExtension(file.Name).Replace(WhiteSpace, string.Empty);

            if (file.DistributeToUserProfile)
            {
                targetDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            }
            else
            {
                targetDirectory = file.DistributeTo;
            }

            // Regardless of 'target' directory, wrap up every distributed file into their own directories
            // simply to avoid cluttering root directories too much
            targetDirectory += @"\VAL\" + applicationName + System.IO.Path.DirectorySeparatorChar.ToString();

            string networkDatabase = String.Concat(file.Path, file.Name);
            string localDatabase = String.Concat(targetDirectory, file.Name);
            string localWorkgroup = String.Empty;

            // See if we also need to copy a workgroup
            if (!string.IsNullOrEmpty(file.Workgroup) && Session.Configuration.DistributeWorkgroups)
            {
                localWorkgroup = String.Concat(targetDirectory + System.IO.Path.GetFileName(file.Workgroup));
            }

            DateTime networkVersion = System.IO.File.GetLastWriteTime(networkDatabase);

            // Set the date to a version that networkVersion will always be greater
            // than to force an inital copy. I doubt they had Access in 1900...maybe
            // a really annoying, buggy version
            DateTime localVersion = new DateTime(1900, 1, 1);

            // Check that the path we are distributing to actually exists
            if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(localDatabase)))
            {
                System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(localDatabase));
            }

            // Get the two date times for comparison
            if (System.IO.File.Exists(localDatabase))
            {
                localVersion = System.IO.File.GetLastWriteTime(localDatabase);
            }

            // See if we need to copy a later version of the db
            if (networkVersion > localVersion)
            {
                System.IO.File.Copy(networkDatabase, localDatabase, true);
            }

            // See if we need to copy a workgroup as well
            if (!string.IsNullOrEmpty(localWorkgroup))
            {
                if (!System.IO.File.Exists(localWorkgroup) ||
                    System.IO.File.GetLastWriteTime(file.Workgroup) > System.IO.File.GetLastWriteTime(localWorkgroup))
                {
                    System.IO.File.Copy(file.Workgroup, localWorkgroup, true);
                }
            }

            return localDatabase;
        }

    }
}

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
Technical Lead
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions