Create a custom component to deploy SharePoint Solutions – Part 2

8 minute read

In the first part of the series we have adjusted a build template in order to copy content from source control to the drop location after the build. This content can then be used to supply release management with information about the deployment. This functionality will be used to copy a configuration file to the drop location. With PowerShell were our custom component will exist off we can read out that configuration file.

As you can read we will create our custom component in specific steps and we will start with the configuration file.

 Configuration File

The configuration file will contain all of the information that is needed to deploy a SharePoint solution package. As example you would like to know to which web application the solution needs to be deployed to.

As example take a look at the following sample configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<!-- 
  Section to specify the configuration for the deployment
-->
<Configuration>
  <!--
    Section to specify environment information
  -->
  <Environment>
    <DEV>
      <WebApplications>
        <WebApplication ID="1" Url="http://sample.msftplayground.local"></WebApplication>
      </WebApplications>
    </DEV>
    <TST>
      <WebApplications>
        <WebApplication ID="1" Url="http://sample-tst.msftplayground.local"></WebApplication>
      </WebApplications>
    </TST>
    <ACC>
      <WebApplications>
        <WebApplication ID="1" Url="http://sample-acc.msftplayground.local"></WebApplication>
      </WebApplications>
    </ACC>
    <PROD>
      <WebApplications>
        <WebApplication ID="1" Url="http://sample.msftplayground.local"></WebApplication>
      </WebApplications>
    </PROD>
  </Environment>
  <!--
    Section to specify information on solutions were to deploy to or retract.
    -->
  <Solutions>
    <!--
    Section to specify the solutions that need to deployed.
    -->
    <Deploy>
      <!--
      Section to specify specific solution information
      
      Name = Name of the solution 
      Upgrade = Upgrade the solution if it exists
      DeployToAll = Deploy to all content applications
      DeployNotExits = Deploy the solution if it does not exist when upgrade is set to true
      WebApplication = The web application to deploy to specified with the ID when deploy to all is false
      -->
      <Solution Name="MSFTPlayground.SharePoint.wsp" Upgrade="true" DeployToAll="false" DeployNotExists="true">
        <WebApplication ID="1" />
        <WebApplication ID="2" />
        <WebApplication ID="3" />
      </Solution>
    </Deploy>
    <Retract>
      <!--
      Section to specify specific solution information
      
      Name = Name of the solution 
      Remove = Remove the solution after retraction
      RetractAll = Retract from all content applications
      -->
      <Solution Name="MSFTPlayground.SharePoint.wsp" Remove="true" RetractAll="true">
        <WebApplication ID="1" />
        <WebApplication ID="2" />
        <WebApplication ID="3" />
      </Solution>
    </Retract>
  </Solutions>
</Configuration>

In the configuration file you can specify which solution package you would like to deploy and to what web application. The web applications are specified in the “Environment” section. The configuration file contains a “Environment” section to be able to use the same mechanism for multiple environments that use different URLs. Within the solution section we then refer to a specific web application by its ID.

To make it more complex we also added some other properties (Upgrade, DeployToAll) to make the components more advanced.

PowerShell

With the configuration file we can start writing the PowerShell that will read out the configuration file and deploys the solution.

param(
    [string]$filename = $(throw "Specify the Config filename that is located in the drop folder"),
    [string]$environment = $(throw "Specify the Environment you would like to deploy to.")
)

if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) {
    Write-Host "Loading Microsoft SharePoint PowerShell" -ForegroundColor Green
    Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}
Write-Host "#### Start deploying solutions ####"

if ((Test-Path $fileName) -eq $true) {
    . ".\functions.ps1"
    Write-Host "Loading configuration file $filename" -ForegroundColor Green

    try{
        $config = [xml] (Get-Content $fileName)
        Deploy-Solutions
    }catch{
        Write-Host $_.Exception.Message -ForegroundColor Red
        throw "Exception occured while deploying solutions"
    }
}else {
    throw "Error loading configuration file, exiting the deployment"
}

Write-Host "#### End deploying solutions ####"

This PowerShell file needs to be run with two parameters:

  • Filename: The path to the configuration file in the drop location.
  • Environment: The environment you would like to deploy to. This should be the same as the environment section you have specified in the configuration file.

Within the PowerShell file the SharePoint PowerShell module is loaded and besides that I load a custom functions file. In the functions file I have a couple of PowerShell methods that I use to do some advanced stuff on the configuration file, and also some more SharePoint functions. When everything is loaded the configuration file is loaded in the  config variable and we call the function “Deploy-Solutions” that is within the same file.

function Deploy-Solutions(){
    $path = ".\";
    $type = "Deploy";

    foreach($solution in $config.Configuration.Solutions.Deploy.Solution){
        
        $solutionName =  $solution.Name;
        $currentDirectory = Get-Location
        $solutionPath = [IO.Path]::GetFullPath([String]::Concat($currentDirectory, "\" ,$path, "\", $solutionName))    
        Write-Host " > Solution path is '$solutionPath'" -ForegroundColor Yellow

        $webApplicationScoped = Get-SolutionProperty $solutionName "ContainsWebApplicationResource"
        $solutionDeployed = Get-SolutionProperty $solutionName "Deployed"
                               
        [bool]$deployToAll = Check-SolutionConfigProperty $solutionName "DeployToAll" $type
        [bool]$upgrade = Check-SolutionConfigProperty $solutionName "Upgrade" $type
        [bool]$deployNotExists = Check-SolutionConfigProperty $solutionName "DeployNotExists" $type
        $webApps = Get-WebAppsForSolution $solutionName $environment $type

        # Check if file exists
        if ((Test-Path $solutionPath) -eq $false) {
            Write-Host " > Solution file doesn't exist!" -ForeGroundColor Red
            throw "Solution file doesn't exist!";
        }
        $deploySolution = $false;

        if (Check-SolutionExists $solutionName) {
            Write-Host " > Solution exists in solution store" -ForegroundColor Yellow
            if(!$upgrade){
                Write-Host " > Solution exists in solution store" -ForegroundColor Yellow
                                               
                if($solutionDeployed) {
                    Write-Host " > Solution is deployed, uninstall solution $solutionName" -ForegroundColor Yellow
                    if ($webApplicationScoped) {
                        $solution = Get-SPSolution $solutionName
                        $deployedTo = $solution.DeployedWebApplications

                        foreach ($webApp in $deployedTo) {
                            Write-Host " > Uninstall from web application '$webApp'" -ForeGroundColor Yellow
                            Uninstall-SPSolution -Identity $solutionName -WebApplication $webApp -Confirm:$false  -ErrorAction Stop
                            Wait-ForJobToFinish($solutionName)
                        }                                                    
                    } else {
                        Write-Host " > Solution doesn't contain web application resource, uninstall globally." -ForeGroundColor Yellow
                        Uninstall-SPSolution -Identity $solutionName -Confirm:$false -ErrorAction Stop
                        Wait-ForJobToFinish($solutionName)
                    }
                    Write-Host " > Solution uninstalled" -ForeGroundColor Green
                }
                Write-Host " > Remove solution $solutionName from solution store" -ForeGroundColor Yellow
                Remove-SPSolution -Identity $solutionName -Confirm:$false -ErrorAction Stop
                Write-Host " > Solution removed" -ForeGroundColor Green        
                $deploySolution = $true                          
            }else{
                Write-Host " > Upgrade SPSolution $solutionName" -ForegroundColor Yellow
                Update-SPSolution -LiteralPath $solutionPath -Identity $solutionName -Force -GACDeployment -ErrorAction Stop       
                Wait-ForJobToFinish($solutionName)                
                $deploySolution = $false
            }

            
        }else{
            Write-Host " > Solution does not exists in solution store" -ForegroundColor Yellow

            if(($upgrade -And $deployNotExists)){
                Write-Host " > Solution does not exists and will be added because it needed to be deployed when it did not exists"  -ForegroundColor Yellow
                $deploySolution = $true
            }

            if(!($upgrade)){
                Write-Host " > Solution does not exists and will be added in order to deploy"  -ForegroundColor Yellow
                $deploySolution = $true
            }
        }
        
        if($deploySolution){
            Write-Host " > Add solution $solutionName to solution store" -ForeGroundColor Yellow
            Add-SPSolution -LiteralPath $solutionPath -ErrorAction Stop
            Write-Host " > Solution added" -ForeGroundColor Green

            Write-Host " > Deploy solution $solutionName" -ForeGroundColor Yellow
            $webApplicationScoped = Get-SolutionProperty $solutionName "ContainsWebApplicationResource"

            if ($webApplicationScoped) {
                if ($deployToAll) {
                    Write-Host " > Deploying to all Web Applications" -ForeGroundColor Yellow
                    Install-SPSolution –Identity $solutionName -GACDeployment -AllWebApplications -Force -ErrorAction Stop
                    Wait-ForJobToFinish($solutionName)
                } else {
                    foreach ($webApp in $webApps) {
                        Write-Host " > Deploying to $webApp" -ForeGroundColor Yellow                                                          
                        Install-SPSolution –Identity $solutionName -WebApplication $webApp -Force -GACDeployment -ErrorAction Stop
                        Wait-ForJobToFinish($solutionName)
                    }
                }
            } else {
                Install-SPSolution –Identity $solutionName -GACDeployment -Force -ErrorAction Stop
                Wait-ForJobToFinish($solutionName)
            }
        }

        $success = Get-SPSolutionSuccessState $solutionName

            if($success){
                Write-Host " > Solution $solutionName installed" -ForeGroundColor Green
            }
            else{
               Write-Host " > Solution $solutionName could not be installed correctly." -ForeGroundColor Green
               throw "Error occured while installing solution $solutionName"
            }
    }                          
}

In the Deploy-Solution function we navigate trough the xml and deploy the solution packages specified in the configuration file. Within the Deploy-Solution function we also use some functions that are specified in the functions.ps1 file.

With the PowerShell files in place we can start adding things to Release Management. The first action in Release Management is creating a new Tool. By going to “Inventory” – “Tools”  and click “New” to create a new tool.

ReleaseManagement-New-Tool