Self installing/maintaining sitecore modules

So i was busy finalizing a Sitecore module i was working on when i had a thought about Sitecore content packages.  Asking a user to install a Sitecore package to get non-content Sitecore items as well as passing along a dll for them to add as a dependency is a little overbearing.

My idea is simple, since the sitecore item dependencies are in essence developer maintained, we can add the content package into the project as an embedded resource.  At which point you add an on init pipeline to validate that the critical items in your module exist.  If they are missing write the embedded resource content package to the data/packages folder and use the sitecore api to consume the content package.

The steps

  1. Identify if any of the required sitecore content items are missing or malformed
  2. Get the packages folder path under the data folder
  3. Extract the package out of the dll where it lives as an embedded resource and write it to a zip in the packages folder
  4. Wait until the file is done being written (if we don’t wait it will throw an error that the file is already in use)
  5. Set up the required context and install the package in a separate thread

The code

 public void Process(PipelineArgs args)
        {

            Assert.ArgumentNotNull(args, "args");
            
            if (RequiredSitecoreItemsMissing())
            {
                var filepath = "";
//if we have an absolute path, rather than relative to the site root
                if (System.Text.RegularExpressions.Regex.IsMatch(Settings.DataFolder, @"^(([a-zA-Z]:\\)|(//)).*")) 
                    filepath = Settings.DataFolder +
                               @"\packages\ThisIsMyPackage.zip";
                else
                    filepath = HttpRuntime.AppDomainAppPath + Settings.DataFolder.Substring(1) +
                               @"\packages\ThisIsMyPackage.zip";
                try
                {
                    this.GetType().Assembly
                        .GetManifestResourceStream("[namespace].ThisIsMyPackage.zip")
                        .CopyTo(new FileStream(filepath, FileMode.Create));
                    Task.Run(() =>;
                    {

                        while (true)
                        {
                            if (!IsFileLocked(new FileInfo(filepath)))
                            {

                                using (new SecurityDisabler())
                                {
                                    using (new ProxyDisabler())
                                    {
                                        using (new SyncOperationContext())
                                        {
                                            IProcessingContext context = new SimpleProcessingContext();
                                            IItemInstallerEvents events =
                                                new DefaultItemInstallerEvents(
                                                    new BehaviourOptions(InstallMode.Overwrite, MergeMode.Undefined));
                                            context.AddAspect(events);
                                            IFileInstallerEvents events1 = new DefaultFileInstallerEvents(true);
                                            context.AddAspect(events1);

                                            Sitecore.Install.Installer installer = new Sitecore.Install.Installer();
                                            installer.InstallPackage(MainUtil.MapPath(filepath), context);
                                            break;
                                        }
                                    }
                                }
                            }
                            else
                                Thread.Sleep(1000);
                        }
                    });
                }
                catch (Exception e)
                {
                    Log.Error("unable to initialize", e, this);
                }
            }
        }
        ///

<summary>
        /// checks to see if the file is done being written to the filesystem
        /// </summary>


        /// from http://stackoverflow.com/questions/29359132/reading-file-that-might-be-updating
        /// <param name="file"></param>
        /// <returns></returns>
        protected virtual bool IsFileLocked(FileInfo file)
        {
            FileStream stream = null;

            try
            {
                stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
            }
            catch (IOException)
            {
                //the file is unavailable because it is:
                //still being written to
                //or being processed by another thread
                //or does not exist (has already been processed)
                return true;
            }
            finally
            {
                if (stream != null)
                    stream.Close();
            }

            //file is not locked
            return false;
        }

So what does this earn us

Now we can set up our module as a nuget package.  Leveraging the nuget package to bring down the dll and place any needed web config patches and let the init pipeline take care of the rest.  Users will just need to add the nuget package to their web project and poof, sitecore items magically appear.