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.