DNN Blog

Apr 7

Posted by: Charles Nurse
4/7/2008  RssIcon

In my previous blog in this series I described how the new Extension Installer processes the manifest.  In summary the Installer creates an instance of a PackageInstaller and passes the manifest to it.  The PackageInstaller then parses the manifest and creates a ComponentInstaller for each component referenced in the manifest.  we sthus end up with the PackageInstaller having a collection (SortedList(Of T)) of ComponentInstallers.

Installing the Package

In this blog I will describe the next step in the process - Installing the Package.  As in my previous blog we will start in the Install wizard.

  292         Private Sub InstallPackage(ByVal e As System.Web.UI.WebControls.WizardNavigationEventArgs)

  293             CreateInstaller()

  294 

  295             If Installer.IsValid Then

  296                 'Reset Log

  297                 Installer.InstallerInfo.Log.Logs.Clear()

  298 

  299                 'Install

  300                 Installer.Install()

  301 

  302                 If Not Installer.IsValid Then

  303                     lblInstallMessage.Text = Localization.GetString("InstallError", LocalResourceFile)

  304                 End If

  305 

  306                 phInstallLogs.Controls.Add(Installer.InstallerInfo.Log.GetLogsTable)

  307             Else

  308                 'Error reading Manifest

  309                 Select Case e.CurrentStepIndex

  310                     Case 2

  311                         lblAcceptMessage.Text = Localization.GetString("InstallError", LocalResourceFile)

  312                         phAcceptLogs.Controls.Add(Installer.InstallerInfo.Log.GetLogsTable)

  313                     Case 3

  314                         lblInstallMessage.Text = Localization.GetString("InstallError", LocalResourceFile)

  315                         phInstallLogs.Controls.Add(Installer.InstallerInfo.Log.GetLogsTable)

  316                 End Select

  317                 e.Cancel = True

  318             End If

  319         End Sub

The InstallPackage method first creates the Installer (and parses the manfest).  As long as the manifest is valid, it clears the Logs and call the Install method of the Installer.  If the Install fails for any reason it displays a message to the user.  In both cases (success or failure) the Logs are displayed so the user can see the results of the install.

  325         Public Function Install() As Boolean

  326             InstallerInfo.Log.StartJob(Util.INSTALL_Start)

  327             Dim bStatus As Boolean = True

  328             Try

  329                 InstallPackages()

  330             Catch ex As Exception

  331                 InstallerInfo.Log.AddFailure(ex)

  332                 bStatus = False

  333             End Try

  334 

  335             If InstallerInfo.Log.Valid Then

  336                 InstallerInfo.Log.EndJob(Util.INSTALL_Success)

  337             Else

  338                 InstallerInfo.Log.EndJob(Util.INSTALL_Failed)

  339             End If

  340 

  341             ' log installation event

  342             LogInstallEvent("Package")

  343 

  344             'Clear Host Cache

  345             DataCache.ClearHostCache(True)

  346 

  347             Return bStatus

  348         End Function

The Install method attempts to install the packages(line 329), loggeing any exception that might occur.  The results of the installation are logged to the EventLog and the Host Cache is cleared.

  188         Private Sub InstallPackages()

  189             'Iterate through all the Packages

  190             For Each kvp As KeyValuePair(Of String, PackageInstaller) In Packages

  191                 'Check if package is valid

  192                 If kvp.Value.Package.IsValid Then

  193                     InstallerInfo.Log.AddInfo(Util.INSTALL_Start + " - " + kvp.Value.Package.Name)

  194                     kvp.Value.Install()

  195                     If InstallerInfo.Log.Valid Then

  196                         InstallerInfo.Log.AddInfo(Util.INSTALL_Success + " - " + kvp.Value.Package.Name)

  197                     Else

  198                         InstallerInfo.Log.AddInfo(Util.INSTALL_Failed + " - " + kvp.Value.Package.Name)

  199                     End If

  200                 Else

  201                     InstallerInfo.Log.AddFailure(Util.INSTALL_Aborted + " - " + kvp.Value.Package.Name)

  202                 End If

  203             Next

  204 

  205             'Delete Temp Folder

  206             If Not String.IsNullOrEmpty(TempInstallFolder) Then

  207                 Directory.Delete(TempInstallFolder, True)

  208             End If

  209             InstallerInfo.Log.AddInfo(Util.FOLDER_DeletedBackup)

  210         End Sub

The installPackages method iterates through the packages (Note: The Installer framework is setup to support the concept of multiple packages in a single manifest/zip.  However, the initial implementation of the Wizard only supports one package per manifest/zip).  For each Package, the method calls the PackageInstallers Install method.

  278         Public Overrides Sub Install()

  279             Dim isCompleted As Boolean = True

  280 

  281             Try

  282                 'Save the Package Information

  283                 If InstalledPackage IsNot Nothing Then

  284                     Package.PackageID = InstalledPackage.PackageID

  285                 End If

  286 

  287                 'Save Package

  288                 PackageController.SavePackage(Package)

  289 

  290                 'Iterate through all the Components

  291                 For index As Integer = 0 To ComponentInstallers.Count - 1

  292                     Dim compInstaller As ComponentInstallerBase = ComponentInstallers.Values(index)

  293                     If compInstaller.Version > Package.InstalledVersion Then

  294                         Log.AddInfo(Util.INSTALL_Start + " - " + compInstaller.Type)

  295                         compInstaller.Install()

  296                         If compInstaller.Completed Then

  297                             Log.AddInfo(Util.COMPONENT_Installed + " - " + compInstaller.Type)

  298                         Else

  299                             Log.AddFailure(Util.INSTALL_Failed + " - " + compInstaller.Type)

  300                             isCompleted = False

  301                             Exit For

  302                         End If

  303                     End If

  304                 Next

  305             Catch ex As Exception

  306                 Log.AddFailure(Util.INSTALL_Aborted + " - " + Package.Name)

  307             End Try

  308 

  309             If isCompleted Then

  310                 'All components successfully installed so Commit any pending changes

  311                 Commit()

  312             Else

  313                 'There has been a failure so Rollback

  314                 Rollback()

  315             End If

  316         End Sub

This method is the "hub" of the installation process.  The first part of the method (lines 282-288) determine whether we are dealing with an upgrade or a new install and save the Package level settings to the database.  Next, the method iterates through the ComponentInstallers collection.  Remember, this collection is a SortedList, and the loop processes the ComponentInstallers in order.  The loop checks if the componentInstaller's version is greater than the version of any previously installed package - we don't want to replace a version 2 component with a version 1 component.  If the new version is greater than any existing version, the PackageInstaller calls the ComponentInstallers Install method.  Once all the components have been installed, the isCompleted flag is checked to determine if any components failed to install.  If there was a failure, then the Rollback method is called, while if the installs all completed successfully the Commit method is called.  This provides a level of Transactional support.  If one component fails, an attempt is made to "undo" the changes that were successfully made in other components.

If we look at the Commit method

  256         Public Overrides Sub Commit()

  257             For index As Integer = 0 To ComponentInstallers.Count - 1

  258                 Dim compInstaller As ComponentInstallerBase = ComponentInstallers.Values(index)

  259                 If compInstaller.Version > Package.InstalledVersion AndAlso compInstaller.Completed Then

  260                     compInstaller.Commit()

  261                 End If

  262             Next

  263             If Log.Valid Then

  264                 Log.AddInfo(Util.INSTALL_Committed)

  265             Else

  266                 Log.AddFailure(Util.INSTALL_Aborted)

  267             End If

  268         End Sub

and the Rollback method

  429         Public Overrides Sub Rollback()

  430             For index As Integer = 0 To ComponentInstallers.Count - 1

  431                 Dim compInstaller As ComponentInstallerBase = ComponentInstallers.Values(index)

  432                 If compInstaller.Version > Package.InstalledVersion AndAlso compInstaller.Completed Then

  433                     Log.AddInfo(Util.COMPONENT_RollingBack + " - " + compInstaller.Type)

  434                     compInstaller.Rollback()

  435                     Log.AddInfo(Util.COMPONENT_RolledBack + " - " + compInstaller.Type)

  436                 End If

  437             Next

  438 

  439             'If Previously Installed Package exists then we need to update the DataStore with this

  440             If InstalledPackage Is Nothing Then

  441                 'No Previously Installed Package - Delete newly added Package

  442                 PackageController.DeletePackage(Package.PackageID)

  443             Else

  444                 'Previously Installed Package - Rollback to Previously Installed

  445                 PackageController.SavePackage(InstalledPackage)

  446             End If

  447         End Sub

we can see that both methods follow the same pattern as the Install method.  They iterate through the ComponentInstallers calling the relevant methods in these classes.

The advantage of this process is that the installation of each component type is delgated to a custom installer that knows how to handle the component.  Thus the install method of the AssemblyInstaller is very different from the Install method of a ConfigInstaller.  As we saw in the previous blog, this approach is also extensible.

Uninstalling an Extension works in much the same way as Installing, so we will get a Log of the Uninstall, both if the uninstall is a success or a failure.

I hope these last two blogs have given you an overview of how the process works.  Unless you actually need to create your own ComponentInstaller, you should not need to delve into this code in detail.  However, if you would like to take advantage of some of the new aspects of the Extension Installer, you will need to understand how to build a version 5 manifest, and I will delve into that aspect in more detail in my next blog on the subject.

Tags:
Categories:
Location: Blogs Parent Separator Charles Nurse

7 comment(s) so far...


Re: Cambrian First Look - Extension Installer (Pt 3)

Charles,

I recently created an authentication service and noticed one issue with the Commit() logic.

Installers that update the database in a tranaction can deadlock other installers. Consider ScriptInstaller. Its Install() method starts a transaction (in ExecuteSql()). Installers that execute after it (before that transaction is committed) can deadlock if they try update the database too.

I had this issue with a manifest that had a component with type="Script" and one with type="Assembly". Originally I had the Script before the Assembly, causing the Assembly deadlock and time out. Luckily I could switch the order around to make things work. I'm just wondering if that will always be the case.

What if two installers both use transactions? Won't that mean they can never be used together?

By DrewTheRat on   4/9/2008

Re: Cambrian First Look - Extension Installer (Pt 3)

Yes - we have addressed that at least in the interim - by removing the transactional processing used by the Script Installer. This means that the rollback of scripts will not work anymore. In the meantime, I am investigating the use of the .NET System.Transactions classes.

By cnurse on   4/10/2008

Re: Cambrian First Look - Extension Installer (Pt 3)

These (quite technical!) blog entries by Charles about the installer have been well received by many with a far greater technical knowledge than I. As a DNN user with no ASP.net technical skills I am keen to know what these changes would mean for those who are not developing DNN modules. Does it mean , for example, the module dev cycle will be quicker (hence modules more quickly released / updated / fewer bugs, or with more functionality can be developed for a module in a fixed amount of time than before, etc?). Or are the benefits only likely to be seen by developers and not by users? I am curious!

By eoghano on   4/10/2008

Re: Cambrian First Look - Extension Installer (Pt 3)

Most of these changes have no direct impact on users. Most of the changes are to make it easier for developers to create new Extensions of all types, including skinobjects and providers, which in the past were tricky to install, as well as provide a central API that can be used by Module Developers to create their own Extensions (templates etc).

The goal of this first Cambrian release is to provide better, more unified, extensibility to the platform, in preparation for the other announced enhancments, which will come in future releases.

By cnurse on   4/11/2008

Re: Cambrian First Look - Extension Installer (Pt 3)

I think Charles' blog entries about the new Features Module are definitely of interest to consumers who are administering DNN sites and installing third-party extensions. This should make their lives much easier!

By DrewTheRat on   4/14/2008

Re: Cambrian First Look - Extension Installer (Pt 3)

Charles,

Does the Extension Installer do anything to avoid mid-installation application restarts resulting from updates to assemblies in the /bin folder?

I ask this because I've been dealing with large PA files that fail because assemblies they've installed cause the application to restart before the installation is complete. What I've learned through testing is that the moment you touch something in the bin folder, you have two minutes to do everything else before the application restarts. I'm learning that in larger installations, that might not be enough time. If the installation is still going, it fails with a ThreadAbortException.

In looking for a solution, the only one I can think of is to ensure that all database updates are performed first, before any files are deployed. The package author could definitely organize the package to accomplish this, but might it make sense to enforce this in the installer logic itself?

By DrewTheRat on   5/7/2008

Never mind....

Of I could just set the shutdownTimeout attribute of the httpRuntime element. That's probably easier....

By DrewTheRat on   5/8/2008
Attend A Webinar
Free Demo Site
Download DotNetNuke Professional Edition Trial
Have Someone Contact Me

Like Us on Facebook Join our Network on LinkedIn Follow DNN Corporate on Twitter Follow DNN on Twitter

Advertisers

Sponsors

DotNetNuke Corporation

DotNetNuke Corp. is the steward of the DotNetNuke open source project, the most widely adopted Web Content Management Platform for building web sites and web applications on Microsoft .NET. Organizations use DotNetNuke to quickly develop and deploy interactive and dynamic web sites, intranets, extranets and web applications. The DotNetNuke platform is available in a free Community and subscription-based Professional and Enterprise Editions with an Elite Support option. DotNetNuke Corp. also operates Snowcovered.com where users purchase third party apps for the platform.