Deploying SharePoint 2010 Solution Packages Using PowerShell

Posted on Posted in Scripts, SharePoint 2010

Update 4/19/2011: I’ve reworked this script completely. You can find the update here: http://blog.falchionconsulting.com/index.php/2011/04/deploying-sharepoint-2010-solution-package-using-powershell-revisited/

With SharePoint 2010 we can now deploy our Solution Packages using PowerShell. What’s cool about this is that it’s a bit easier than it was with 2007 to check if a package is already deployed and conditionally retract, delete, and then re-add and re-deploy. By now most people already know how to do this as it’s fairly straightforward but I thought I’d go ahead and share the script that I use as it’s great for deploying lots of Solution Packages to my various client environments in bulk.

Like most of my scripts this one is driven by an XML file but I have a core function which can be called directly – I just wrap that in another function which can iterate through the XML file thus facilitating bulk installs of packages. First lets look at the XML file:

<Solutions>
<Solution Path="W:\my.sharepoint.package.wsp" CASPolicies="false" GACDeployment="true">
<WebApplications>
<WebApplication>http://portal</WebApplication>
</WebApplications>
</Solution>
</Solutions>

As you can see the structure is fairly simplistic – just provide the path to the WSP file and whether it contains CAS policies and whether it should be deployed to the GAC or not. If it’s a Farm level solution (no web application resources) then simply omit the <WebApplications /> element. If you have more than one solution just add another <Solution /> element. If you’re deploying to multiple web applications then add as many <WebApplication /> elements as is needed.

Now we’ll take a look at the wrapper function which loops through the XML:

function Install-Solutions([string]$configFile)
{
    if ([string]::IsNullOrEmpty($configFile)) { return }

    [xml]$solutionsConfig = Get-Content $configFile
    if ($solutionsConfig -eq $null) { return }

    $solutionsConfig.Solutions.Solution | ForEach-Object {
        [string]$path = $_.Path
        [bool]$gac = [bool]::Parse($_.GACDeployment)
        [bool]$cas = [bool]::Parse($_.CASPolicies)
        $webApps = $_.WebApplications.WebApplication
        Install-Solution $path $gac $cas $webApps
    }
}

As you can see the code just loads the passed in file as an XmlDocument object and grabs each Solution element ($solutionsConfig.Solutions.Solution) and then iterates through each object using the ForEach-Object cmdlet. For pure convenience I grab each attribute and assign it to a local variable. And finally I call the Install-Solution function which is shown below:

function Install-Solution([string]$path, [bool]$gac, [bool]$cas, [string[]]$webApps = @())
{
    $spAdminServiceName = "SPAdminV4"

    [string]$name = Split-Path -Path $path -Leaf
    $solution = Get-SPSolution $name -ErrorAction SilentlyContinue

    if ($solution -ne $null) {
        #Retract the solution
        if ($solution.Deployed) {
            Write-Host "Retracting solution $name..."
            if ($solution.ContainsWebApplicationResource) {
                $solution | Uninstall-SPSolution -AllWebApplications -Confirm:$false
            } else {
                $solution | Uninstall-SPSolution -Confirm:$false
            }
            Stop-Service -Name $spAdminServiceName
            Start-SPAdminJob -Verbose
            Start-Service -Name $spAdminServiceName

            #Block until we're sure the solution is no longer deployed.
            do { Start-Sleep 2 } while ((Get-SPSolution $name).Deployed)
        }

        #Delete the solution
        Write-Host "Removing solution $name..."
        Get-SPSolution $name | Remove-SPSolution -Confirm:$false
    }

    #Add the solution
    Write-Host "Adding solution $name..."
    $solution = Add-SPSolution $path

    #Deploy the solution
    if (!$solution.ContainsWebApplicationResource) {
        Write-Host "Deploying solution $name to the Farm..."
        $solution | Install-SPSolution -GACDeployment:$gac -CASPolicies:$cas -Confirm:$false
    } else {
        if ($webApps -eq $null -or $webApps.Length -eq 0) {
            Write-Warning "The solution $name contains web application resources but no web applications were specified to deploy to."
            return
        }
        $webApps | ForEach-Object {
            Write-Host "Deploying solution $name to $_..."
            $solution | Install-SPSolution -GACDeployment:$gac -CASPolicies:$cas -WebApplication $_ -Confirm:$false
        }
    }
    Stop-Service -Name $spAdminServiceName
    Start-SPAdminJob -Verbose
    Start-Service -Name $spAdminServiceName

    #Block until we're sure the solution is deployed.
    do { Start-Sleep 2 } while (!((Get-SPSolution $name).Deployed))
}

The code looks more complicated than it really is. I first start out by getting the solution name which I always assume to be the filename of the WSP file. I then use that to get the SPSolution object using Get-SPSolution. If a value comes back then I check if it has been deployed and if it has then I retract it by calling Uninstall-SPSolution. The trick is knowing whether it has web application scoped resources and if it does then we need to retract using the -AllWebApplications parameter. Once retracted I stop the SharePoint Administration Service (SPAdminV4) so that I can call Start-SPAdminJob and force the retraction timer job to execute. Once the Start-SPAdminJob cmdlet returns I then restart the SharePoint Administration Service. With the solution retracted I can now delete the solution from the solution store using Remove-SPSolution (I re-get the solution to make sure that I get no errors due to the current variables state being invalid).

Once deleted I can then add the new solution to the store using the path provided (Add-SPSolution). Now that it’s in the store I can check if it has web application resources or not – if it does not then the deployment is simply a matter of calling Install-SPSolution and specifying whether it should be deployed to the GAC and if it contains CAS policies. If it does contain web application resources then I have to loop through all the web application items in the provided string array ($webApps) and then pass each one into a separate call to Install-SPSolution.

You now have two options for adding your solution: you can call the first function and pass in an XML file or you can call the second function directly. I’ll first show how to call the second function directly:

PS C:\>Install-Solution "w:\my.sharepoint.package.wsp" $true $false @("http://portal","http://mysites")

Now lets look at how to call the first function given an XML file named “solutions.xml” containing a structure similar to that shown above:

PS C:\>Install-Solutions "w:\solutions.xml"

Hopefully you’ll find this script useful for deploying your custom SharePoint 2010 Solution Packages.

20 thoughts on “Deploying SharePoint 2010 Solution Packages Using PowerShell

  1. this is awesome, so much easier to see whether it’s finished or not! good job!
    I’ll use this in a demo at Australian SharePoint Conference on Wednesday! Perfect for me as I’m demonstrating deploying versioned features in PowerShell etc.

  2. Great post!

    When I add more than a single -section to the xml its breaks when trying to DeActivate and Active the features.

    When debugging I have found that the $featureName is empty or not defined.

    Have you got an xml-example containing more than a single feature that works? 🙂

    Thanks in advance.

  3. Thanks Gary this script is veru useful to deploy solution but i have question what is SilentlyContinue in script is it function or is it out of box function.

  4. There is a problem with this implementation. If deployment fails, it will get stuck waiting for the solution to be deployed. Here are some suggested changes:

    $solution | Install-SPSolution -GACDeployment:$gac -CASPolicies:$cas -WebApplication $webApp -Confirm:$false -Force:$force
    Execute-AdminServiceJobs

    # block until deployed
    do
    {
    Start-Sleep 2
    $retryCount++
    Write-Host “.” -NoNewLine
    if($retryCount -gt 60)
    {
    Write-Host “”
    Write-Error ” Timeout deploying solution $name to $webApp”
    break
    }
    } while ((Get-SPSolution $name).JobExists)

    Write-Host “”
    $solution = (Get-SPSolution $name)
    if($solution.LastOperationResult -ne “DeploymentSucceeded”)
    {
    Write-Error $solution.LastOperationDetails
    }

  5. Thanks Gary!
    Great work. A nice addition would be to pass the folder path where all the solutions are located instead of type the absolute path for each file.
    Anyways, great start for anyone wanting to deploy through Powershell!

  6. Im getting the following error Pls help me…
    The term ‘Get-SPSolution’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of
    the name, or if a path was included, verify that the path is correct and try again.

  7. When using more than one web application, the solution deployment fails because it doesn’t wait for the previous deployment to finish.

    I think the deployment code should look more like this:

    #Deploy the solution
    if (!$solution.ContainsWebApplicationResource) {
    Write-Host “Deploying solution $name to the Farm…”
    $solution | Install-SPSolution -GACDeployment:$gac -CASPolicies:$cas -Confirm:$false
    } else {
    if ($webApps -eq $null -or $webApps.Length -eq 0) {
    Write-Warning “The solution $name contains web application resources but no web applications were specified to deploy to.”
    return
    }
    $webApps | ForEach-Object {
    Write-Host “Deploying solution $name to $_…”
    $solution | Install-SPSolution -GACDeployment:$gac -CASPolicies:$cas -WebApplication $_ -Confirm:$false
    #Block until we’re sure the solution is deployed.
    do { Start-Sleep 2 } while ((-not (Get-SPSolution $name).Deployed) -and (Get-SPSolution $name).JobExists)
    }
    }
    Stop-Service -Name $spAdminServiceName
    Start-SPAdminJob -Verbose
    Start-Service -Name $spAdminServiceName

  8. We have host header site collection in a web app in sharepoint 2010 and when we do redeployment of wsp, nothing gets updated but suprising thing is root site collection which is not host header site collection inside same web app gets updated master page/ page layouts. I tried to activate/deactivate feature but nothing getting updated and I can not delete existing page layouts/ master page as they are being used by number of pages and that is not the solution I am looking for.

    Can anybody guide on how to deploy wsp in host header site collection in sharepoint 2010/

    amitloh@gmail.com

  9. Hi Gary,

    Script is awesome. Only thing is I can’t seem to call the function from the PS Cmd prompt….
    If I run the command PS C:\>Install-Solutions “c:\solutions.xml”

    I get the error
    The term ‘C:\>Install-Solutions’ is not recognized as the name of a cmdlet, function, script file, or operable program.
    Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
    At line:1 char:22
    + C:\>Install-Solutions <<<Install-Solutions:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

    I’ve tried many different things including adding the script location the the PATH environment variable but no luck.

    What am I doing wrong?

    Cheers,
    Claud

    1. Found that I needed to run the script as,

      . .\Install-Solutions.ps1 ; Install-Solutions “.\Solutions.xml”

      Awesome script! Truly see the power of PowerShell.

      Cheers,

      Claudio

Leave a Reply

Your email address will not be published. Required fields are marked *

*