SharePoint solution deployment with PowerShell

PowerShell logoMarcin Baczewski

While working on SharePoint solutions I often need to deploy those solutions in the development environment. Repetitive actions in that process should be automated in order for me to have more time for development and tests. In this post I will present the basic methods that I use to build custom deployment scripts.

WSP deployment

The deployment includes the following steps: adding a solution to SharePoint, installing the solution, and waiting. Those steps are executed by the following PowerShell scripts:

function Wait-Solution($name)
{
	do
	{
		Write-Host "." -NoNewline
		Start-Sleep 2
	}
	while ((Get-SPSolution $name).JobExists -eq $true)
	Start-Sleep 2
	Write-Host ""
}
function DeploySolutions([string]$webAppUrl, [string[]]$solutions)
{
	foreach ($name in $solutions)
	{
		Write-Host "Starting deploying solution: $($name)"
		$solution = Add-SPSolution -LiteralPath $(Get-Childitem $name)
		if($solution.ContainsWebApplicationResource -eq $true)
		{
			$wa = Get-SPWebApplication -Identity $webAppUrl
			if($solution.DeployedWebApplications.Contains($wa) -eq $true)
			{
				Write-Host "Solution $($name) has already been deployed" -ForegroundColor Yellow
			}
			else
			{
				Write-Host "Deploying solution $($name)"
				Install-SPSolution -Identity $name `
					-WebApplication $webAppUrl `
					-GACDeployment -Force
				Wait-Solution $name
			}
		}
		else
		{
			if($solution.Deployed -eq $true)
			{
				Write-Host "Solution $($name) has already been deployed" -ForegroundColor Yellow
			}
			else
			{
				Write-Host "Deploying solution $($name)"
				Install-SPSolution -Identity $name `
					-GACDeployment -Force
				Wait-Solution $name
			}
		}
	}
}
function RetractSolutions([string]$webAppUrl, [string[]]$solutions)
{
	foreach ($name in $solutions)
	{
		Write-Host "Starting retracting solution: $($name)"
		$solution = Get-SPSolution | Where-Object {$_.Name -eq $name}
		if($solution -ne $null -and $solution.Deployed -eq $true)
		{
			if($solution.ContainsWebApplicationResource -eq $true)
			{
				$wa = Get-SPWebApplication -Identity $webAppUrl
				if($solution.DeployedWebApplications.Contains($wa) -eq $true)
				{
					Write-Host "Retracting solution $($name)"
					Uninstall-SPSolution -Identity $solution -WebApplication $wa -Confirm:$false
					Wait-Solution $name
				}
			}
			else
			{
				Write-Host "Retracting solution $($name)"
				Uninstall-SPSolution -Identity $solution -Confirm:$false
				Wait-Solution $name
			}

			if($solution.Deployed -eq $false)
			{
				Remove-SPSolution -Identity $solution -Confirm:$false
			}
		}
		else
		{
			Write-Host "Solution: $($name) is not deployed" -ForegroundColor Yellow
		}

	}
}

Upgrade of the deployed solution is executed by the following script:

function UpdateSolutions([string[]]$solutions)
{
	foreach ($name in $solutions)
	{
		Write-Host "Updating solution: $($name)"
		Update-SPSolution -Identity $name `
			-LiteralPath $(Get-Childitem $name) `
			-GACDeployment -Force
		Wait-Solution $name
	}
}

Restarting services

Restarting the SPTimerv4 service is essential when the new WSP solution has been deployed. To download the service instance I use Get Service command which requires an open port 445 (TCP) for remote machines.

function RestartService([string]$name)
{
	foreach($server in [Microsoft.SharePoint.Administration.SPFarm]::Local.Servers)
	{
		if($server.Role -ne [Microsoft.SharePoint.Administration.SPServerRole]::Invalid)
		{
			try
			{
				Write-Host "Restarting $($name) on $($server.Name)"
				$serviceInstance = Get-Service -ComputerName $server.Name -Name $name -ErrorAction Stop
				if($serviceInstance -ne $null)
				{
					Restart-Service -InputObject $serviceInstance;
				}
			}
			catch
			{
				$ErrorMessage = $_.Exception.Message
				Write-Host "Error Occured: " $ErrorMessage -ForegroundColor Red
				Write-Host "Check service name or firewall settings on $($server.Name). This requires an open 445 (TCP) port." -ForegroundColor Yellow
			}
		}
	}
}

Features activation

Here I present scripts that activate features for different Scopes (Farm, WebApplication, Site, Web).

function ActivateFarmFeatures([string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to activate feature $($name)"
		$feature = Get-SPFeature | Where-Object {$_.DisplayName -eq $name }
		if($feature -eq $null)
		{
			Write-Host "Activating Feature $($name)"
			Enable-SPFeature -Identity $name
		}
		else
		{
			 Write-Host "Feature $($name) was already activated." -ForegroundColor Yellow
		}
	}
}
function ActivateWebAppFeatures([string]$webAppUrl, [string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to activate feature $($name)"
		$feature = Get-SPFeature -WebApplication $webAppUrl | Where-Object {$_.DisplayName -eq $name }
		if($feature -eq $null)
		{
			Write-Host "Activating Feature $($name)"
			Enable-SPFeature -Identity $name -Url $webAppUrl -Force
		}
		else
		{
			 Write-Host "Feature $($name) was already activated at $($webAppUrl)" -ForegroundColor Yellow
		}
	}
}
function ActivateSiteFeatures([string]$siteUrl, [string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to activate feature $($name)"
		$site = Get-SPSite -Identity $siteUrl
		$f = Get-SPFeature -Site $site | Where-Object {$_.DisplayName -eq $name }
		if($f -eq $null)
		{
			Write-Host "Activating feature $($name) at $($siteUrl)"
			Enable-SPFeature -Identity $name -Url $siteUrl -Force
		}
		else
		{
			Write-Host "Feature $($name) was already activated at $($siteUrl)" -ForegroundColor Yellow
		}
		$site.Dispose()
	}
}
function ActivateWebFeatures([string]$webUrl, [string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to activate feature $($name)"
		$f = Get-SPFeature -Web $webUrl | Where-Object {$_.DisplayName -eq $name }
		if($f -eq $null)
		{
			Write-Host "Activating feature $($name) at $($webUrl)"
			Enable-SPFeature -Identity $name -Url $webUrl -Force
		}
		else
		{
			Write-Host "Feature $($name) was already activated at $($webUrl)" -ForegroundColor Yellow
		}
	}
}

Features deactivation

Here I present the similar scripts that deactivate features:

function DeactivateFarmFeatures([string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to dectivate feature $($name)"
		$feature = Get-SPFeature | Where-Object {$_.DisplayName -eq $name }
		if($feature -ne $null)
		{
			Write-Host "Deactivating Feature $($name)"
			Disable-SPFeature -Identity $name -Confirm:$false
		}
		else
		{
			 Write-Host "Feature $($name) was already deactivated" -ForegroundColor Yellow
		}
	}
}
function DeactivateWebAppFeatures([stirng]$webAppUrl, [string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to dectivate feature $($name)"
		$feature = Get-SPFeature -WebApplication $webAppUrl | Where-Object {$_.DisplayName -eq $name }
		if($feature -ne $null)
		{
			Write-Host "Deactivating Feature $($name)"
			Disable-SPFeature -Identity $name -Url $webAppUrl -Confirm:$false
		}
		else
		{
			 Write-Host "Feature $($name) was already deactivated at $($webAppUrl)" -ForegroundColor Yellow
		}
	}
}
function DeactivateSiteFeatures([string]$siteUrl, [string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to dectivate feature $($name)"
		$site = Get-SPSite -Identity $siteUrl
		$f = Get-SPFeature -Site $site | Where-Object {$_.DisplayName -eq $name }
		if($f -ne $null)
		{
			Write-Host "Deactivating feature $($name) at $($siteUrl)"
			Disable-SPFeature -Identity $name -Url $siteUrl -Confirm:$false
		}
		else
		{
			Write-Host "Feature $($name) was already deactivated at $($siteUrl)" -ForegroundColor Yellow
		}
		$site.Dispose()
	}
}
function DeactivateWebFeatures([string]$webUrl, [string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to dectivate feature $($name)"
		$f = Get-SPFeature -Web $webUrl | Where-Object {$_.DisplayName -eq $name }
		if($f -ne $null)
		{
			Write-Host "Deactivating feature $($name) at $($webUrl)"
			Disable-SPFeature -Identity $name -Url $webUrl -Confirm:$false
		}
		else
		{
			Write-Host "Feature $($name) was already deactivated at $($webUrl)" -ForegroundColor Yellow
		}
	}
}
}

Install/Uninstall Features

The script to install or uninstall features is useful when the solution was expanded with more features and we updated the solution. The script is the following.

function InstallFeatures([string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to install feature $($name)"
		$feature = Get-SPFeature | Where-Object {$_.DisplayName -eq $name }
		if($feature -eq $null)
		{
			Write-Host "Installing Feature $($name)"
			Install-SPFeature -Identity $feature.Id -Force -Confirm:$false
		}
		else
		{
			 Write-Host "Feature $($name) was already installed." -ForegroundColor Yellow
		}
	}
}
function UninstallFeatures([string[]]$features)
{
	foreach ($name in $features)
	{
		Write-Host "Attempting to uninstall feature $($name)"
		$feature = Get-SPFeature | Where-Object {$_.DisplayName -eq $name }
		if($feature -ne $null)
		{
			Write-Host "Uninstalling Feature $($name)"
			Uninstall-SPFeature -Identity $feature.Id -Confirm:$false -Force
		}
		else
		{
			 Write-Host "Feature $($name) was already uninstalled" -ForegroundColor Yellow
		}
	}
}

Upgrade features

Feature upgrade can be problematic when the solution has been updated. If we update the solution with one script and then wish to upgrade features, PowerShell console does not delete old DLLs, and upgrade will not be executed correctly.

One of the solutions is restarting the PowerShell console or calling the script in the new instance of PowerShell.

function UpgradeWebFeatures([string]$siteUrl, [bool]$confirm)
{
	$site = Get-SPSite -Identity $siteUrl
	$featuresToUpgrade = $site.QueryFeatures([Microsoft.SharePoint.SPFeatureScope]::"Web", $true)
	if ($featuresToUpgrade.Count -eq $null)
	{
		Write-Host "No features need upgrade."  -ForegroundColor Yellow
		return
	}

	Write-Host "Following features need upgrade:"
	$featuresToUpgrade.Reset()
	foreach ($f in $featuresToUpgrade)
	{
		Write-Host -ForegroundColor Yellow "Feature" $f.Definition.DisplayName -NoNewline
		Write-Host -ForegroundColor Yellow " Current version" $f.Version -NoNewline
		Write-Host -ForegroundColor Yellow " Latest version" $f.Definition.Version
	}
	$title = "Upgrade web features."
	$message = "Do you want to upgrade features?"
	$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Yes"
	$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "No"
	$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
	$choice = 1
	if ($confirm)
	{
		$choice = $host.ui.PromptForChoice($title, $message, $options, 1)
	}

	if ($choice -eq 0)
	{
		$featuresToUpgrade.Reset()
		foreach ($f in $featuresToUpgrade)
		{
			Write-Host -ForegroundColor Yellow "Upgrading feature " $f.Definition.DisplayName -NoNewline
			$f.Upgrade( $false )
			Write-Host -ForegroundColor Green " DONE"
		}
	}
}

The separate file UpgradeFeatures.ps1 calls the function UpgradeFeatures

[string]$SiteUrl = $args[0]
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
. ./SharePoint.Solutions.ps1
UpgradeWebFeatures $SiteUrl $true

Example

param
(
	 [string]$WebAppUrl = "http://testsite"
	,[string]$SiteUrl = "http://testsite"
	,[string]$WebUrl = "http://testsite/web"
	,[string[]]$PackageNames = @("Solution1.wsp", "Solution2.wsp")
	,[string[]]$SiteFeatures = @("Solution1_WebParts")
	,[string[]]$WebFeatures = @("Solution2_Provision")
)
Add-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue
. ./SharePoint.Solutions.ps1
# install solutions and activate features
DeploySolutions $WebAppUrl $PackageNames
ActivateSiteFeatures $SiteUrl $SiteFeatures
ActivateWebFeatures $WebP3Url $WebFeatures
# update solutions and try upgrade features
UpdateSolutions $PackageNames
$scriptUpgrade = $(Get-Childitem 'UpgradeFeatures.ps1')
PowerShell.exe $scriptUpgrade $SiteUrl

You can download the whole script SharePoint.Solutions.ps1 here: [SharePoint.Solutions.ps1] [Upgrade.Features.ps1]

One thought on “SharePoint solution deployment with PowerShell

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s