Deploying multiple wsp packages with one installation file

4 minute read

Today the public beta of SharePoint 2010 is released and everyone started blogging about SharePoint 2010. I am still preparing a Virtual Machine because the beta does not fully works on a Windows Server 2008 R2 installation and I had prepared a virtual machine with this operating system. But know back to deploying multiple wsp packages.

At the moment I’m working for a client that want to install multiple wsp packages and keep it simple for administrators to change the deployment. To accomplish this I created a sample configuration file for a custom installation.

<?xml version="1.0" encoding="utf-8" ?>
<Solution>
    <Package file="Solutions\CustomPackage2.wsp" deployment="Globally"></Package>
    <Package file="Solutions\CustomPackage1.wsp" deployment="WebApplication">
       <WebApplication url="http://myportal.com" />
    </Package>
</Solution>

In the configuration file you can see that there are two packages. One of the packages has to be deployed globally and 1 of them for several web applications that is why the second packages has child elements.

Know that we have a sample configuration file we want we to start building a application to install those packages. First of all we will try to read out the xml file in a console application.

class Program
 {
    public const string SolutionElement = "Solution";
    public const string PackageElement = "Package";
    public const string FileAttribute = "file";
    public const string DeploymentAttribute = "deployment";

    public static void Main()
    {

       try
       {
          if (File.Exists("Installation.config"))
          {
             //get the installation document
             WriteSectionLine("Loading installation configuration file.....");
             var config = XDocument.Load("Installation.config");

             var packages = from XElement package in config.Element(SolutionElement).Elements(PackageElement)
                            select package;

             foreach (XElement element in packages)
             {
                ProcessPackage(element);
             }
          }
          else
          {
             Console.WriteLine("Configuration file 'Installation.config' is nog in the same directory as the solution installer.");
          }
       }
    catch (Exception exception)
    {
       Console.WriteLine("An exception ocurred during the installation");
       Console.WriteLine("-------------------------------------------------------------------");
       Console.WriteLine(exception.Message);
       Console.WriteLine("-------------------------------------------------------------------");
    }
    WriteSectionLine("Press any key to close.");
    Console.ReadKey();
 }

In the ‘Main’ of the console application we check if the configuration file exists and when it exists we load the file and run a Linq query for the package elements. When we have package elements within the configuration file we run the method Process Package for each package element.

private static void ProcessPackage(XElement element)
{
    var deployment = element.Attribute(DeploymentAttribute).Value;
    if (deployment.ToUpperInvariant() == "GLOBALLY")
    {
       DeploymentGlobally(element);
    }
    else
    {
       DeploymenWebApplication(element);
    }
}

In the process package we check the deployment attribute to check how to deploy the package, globally or for web applications.

private static void DeploymenWebApplication(XElement element)
 {
    var file = element.Attribute(FileAttribute).Value;
    var fileName = Path.GetFileName(file);

    WriteSectionLine(string.Format(CultureInfo.InvariantCulture, "Processing Global Solution: {0}", fileName));
    var query = from childElement in element.Elements("WebApplication")
                select childElement;

    //delete / retract solution
    foreach (XElement el in query)
    {
      var url = el.Attribute("url").Value;
      Console.WriteLine(Processes.RunStsAdmProcces("retractsolution", string.Format(CultureInfo.InvariantCulture, "-name {0} -url {1} -immediate", fileName, url)));
      Console.WriteLine(Processes.RunStsAdmProcces("execadmsvcjobs", string.Empty));
    }

    Console.WriteLine(Processes.RunStsAdmProcces("deletesolution", string.Format(CultureInfo.InvariantCulture, "-name {0} -override", fileName)));
    Console.WriteLine(Processes.RunStsAdmProcces("addsolution", string.Format(CultureInfo.InvariantCulture, "-filename {0}", file)));

    foreach (XElement el in query)
    {
       var url = el.Attribute("url").Value;
       Console.WriteLine(Processes.RunStsAdmProcces("deploysolution", string.Format(CultureInfo.InvariantCulture, "-name {0} -url {1} -allowgacdeployment -immediate", fileName, url)));
       Console.WriteLine(Processes.RunStsAdmProcces("execadmsvcjobs", string.Empty));
    }
 }

private static void DeploymentGlobally(XElement element)
{
    var file = element.Attribute(FileAttribute).Value;
    var fileName = Path.GetFileName(file);

    WriteSectionLine(string.Format(CultureInfo.InvariantCulture, "Processing Global Solution: {0}", fileName));

    //preform processes
    Console.WriteLine(Processes.RunStsAdmProcces("retractsolution", string.Format(CultureInfo.InvariantCulture, "-name {0} -immediate", fileName)));
    Console.WriteLine(Processes.RunStsAdmProcces("execadmsvcjobs", string.Empty));
    Console.WriteLine(Processes.RunStsAdmProcces("deletesolution", string.Format(CultureInfo.InvariantCulture, "-name {0} -override", fileName)));
    Console.WriteLine(Processes.RunStsAdmProcces("addsolution", string.Format(CultureInfo.InvariantCulture, "-filename {0}", file)));
    Console.WriteLine(Processes.RunStsAdmProcces("deploysolution", string.Format(CultureInfo.InvariantCulture, "-name {0} -allowgacdeployment -immediate", fileName)));
    Console.WriteLine(Processes.RunStsAdmProcces("execadmsvcjobs", string.Empty));
 }

In the methods we read out additional information from the package element. In the ‘DeploymentWebApplication’ we read out the child elements and retract and delete the solution for each web application before we start installing the solution. Each time we retract or deploy a package we also run the execadmsvcjobs because we have to make sure no action for a package is running at the moment we start a new action.

The actions we do are started by the method RunStsAdmProcess this methods starts a new process that runs the stsadm tool of SharePoint. The methods needs two parameters the command and the argument.

internal static string RunStsAdmProcces(string command, string arguments)
 {
    var retVal = string.Empty;
    var commonFilesPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.CommonProgramFiles);

    using (var process = new Process())
    {
       //create the start info of the project
       var processStartInfo = new ProcessStartInfo(command);
       processStartInfo.UseShellExecute = false;
       processStartInfo.RedirectStandardOutput = true;
       processStartInfo.FileName = commonFilesPath + @"\Microsoft Shared\web server extensions\12\BIN\stsadm.exe";
       processStartInfo.Arguments = string.Format(CultureInfo.InvariantCulture, " -o {0} {1}", command, arguments);

       //initialize the process