Visual Studio 2010 and SharePoint 2010 has made great improvements to deployments, but still lacks important features. This method is designed to allow for:
We'll start out creating the shared environment variables. These are the only part of the method that is environment specific. I generally have one file that I copy to all environments and only uncomment the environment section.
Some options not used in this example and I will leave it to the reader as an exercise to implement:
Now that we have the files needed, we can put them into the directory structure:
- Easily moving to a new environment
- Ease of deployment script enhancements without having to change all files
- XML driven deployment scripts
- Reduce need for having to know the SharePoint API
We'll start out creating the shared environment variables. These are the only part of the method that is environment specific. I generally have one file that I copy to all environments and only uncomment the environment section.
# # Environment specific variables for use in powershell scripts # Usage: # . (Join-Path $currentDirectory SharedEnvironment.ps1); # # Maintenance: # Initial development # ## VM $SPRootURL = "http://localhost"; $SPEnvironment = "VM"; $SharePointDeploymentFolder = "C:\SharePointDeploy"; $SharePointSolutionCache = "C:\SharePointDeploy\wsp"; ## DEV #$SPRootURL = "http://SPUrlRoot"; # no ending slash #$SPEnvironment = "DEV"; #$SharePointDeploymentFolder = "C:\SharePointDeploy"; #$SharePointSolutionCache = "C:\SharePointDeploy\wsp";The processor is the heart of the method. It takes in the XML document and performs tasks based on the documents contents and what actions are implemented.
param (
[string] $xmlPath = $(Throw 'Missing: xmlPath'),
[Switch] $remove
)
[void][System.Reflection.Assembly]::LoadWithPartialName( "Microsoft.SharePoint" );
if((Get-PSSnapin | Where-Object {$_.Name -eq "Microsoft.SharePoint.PowerShell"}) -eq $null) { Add-PSSnapIn "Microsoft.SharePoint.Powershell"; }
$currentDirectory = Split-Path $myInvocation.MyCommand.Path; # $myInvocation.MyCommand.Path;
$WSPCache = ".";
. (Join-Path $currentDirectory MemberCatalogEnvironment.ps1);
# Solution Handling
function ProcessSolution( [System.Xml.XmlNode] $solutionNode )
{
if ($remove) {
Retract-Solution $solutionNode;
} else {
Install-Solution $solutionNode;
}
}
function Install-Solution( [System.Xml.XmlNode] $solutionNode )
{
$curDir = Split-Path -Parent $MyInvocation.ScriptName;
$fileName = $curDir+"\"+$WSPCache+$solutionNode.File;
$name = $solutionNode.File;
try
{
if (!(test-path $fileName)) {
$(throw "The file $path does not exist.");
}
$solution = Get-SPSolution $name -ErrorAction SilentlyContinue
if ($solution -eq $null) {
Write-Host "Install Solution: $name";
#Add solution to SharePoint
Write-Host "Adding solution $name..."
$solution = Add-SPSolution (get-item $fileName).FullName
#Deploy the solution
if ($solution.ContainsWebApplicationResource -and $solutionNode.AllWebApplications) {
Write-Host "Deploying solution $name to $webApplication..."
$solution | Install-SPSolution -GACDeployment -CASPolicies:$false -AllWebApplications -Confirm:$false
}
elseif ($solution.ContainsWebApplicationResource) {
Write-Host "Deploying solution $name to $webApplication..."
$solution | Install-SPSolution -GACDeployment -CASPolicies:$false -WebApplication $webApplication -Confirm:$false
} else {
Write-Host "Deploying solution $name to the Farm..."
$solution | Install-SPSolution -GACDeployment -CASPolicies:$false -Confirm:$false
}
} else {
Write-Host "Update Solution: $name";
try {
Update-SPSolution –Identity $name –LiteralPath $fileName –GACDeployment -CASPolicies:$false -Confirm:$false
} catch [Exception] {
$_ | gm;
if ($_ -contains "Cannot uninstall the LanguagePack 0 because it is not deployed") {
Retract-Solution $solutionNode;
Install-Solution $solutionNode;
}
Else {
throw $_
}
}
}
WaitForJobToFinish $solutionNode.File
} catch [Exception] {
Write-Error $_;
log -message $_ -type "Error";
}
}
function Retract-Solution( [System.Xml.XmlNode] $solutionNode )
{
Write-Host "Retracting solution $solutionNode.Name...";
# Solution must be uninstalled and removed.
$curDir = Split-Path -Parent $MyInvocation.ScriptName
$fileName = $curDir+"\"+$WSPCache+$solutionNode.File
try
{
[Microsoft.SharePoint.Administration.SPSolution] $solution = (Get-SPSolution $name -ErrorAction SilentlyContinue)[0];
if (($solution -ne $null) -and ($solution.Deployed)) {
Write-Host "Retracting solution."
if ($solution.ContainsWebApplicationResource -and $solutionNode.AllWebApplications) {
Write-Host "Retracting solution $name to $webApplication..."
$solution | Uninstall-SPSolution -AllWebApplications -Confirm:$false
}
elseif ($solution.ContainsWebApplicationResource) {
Write-Host "Retracting solution $name to $webApplication..."
$solution | Uninstall-SPSolution -WebApplication $webApplication -Confirm:$false
} else {
Write-Host "Retracting solution $name to the Farm..."
$solution | Uninstall-SPSolution -Confirm:$false
}
#Uninstall-SPSolution -Identity $solutionNode.File -Confirm:$false
WaitForJobToFinish $solutionNode.File
Write-Host "Deleting solution."
Remove-SPSolution -Identity $solutionNode.File -Confirm:$false
}elseif (($solution -ne $null) -and ($solution.Deployed))
{
Write-Host "Deleting solution."
Remove-SPSolution -Identity $solutionNode.File -Confirm:$false
}
} catch [Exception] {
Write-Error $_;
log -message $_ -type "Error";
}
}
# Feature Handling
function ProcessFeatureActivation( [System.Xml.XmlNode] $featureNode, $retry = 2 )
{
try
{
if (-not $remove) {
[Microsoft.SharePoint.Administration.SPFeatureDefinition] $feature = Get-SPFeature | ? {$_.DisplayName -eq $featureNode.Name};
if ($feature -eq $null) {
Install-SPFeature -path $featureNode.Name;
}
if( ($featureNode.Url -ne $null) -and ($featureNode.Url -ne "") )
{
$url = $SPRootURL + $featureNode.Url;
Write-Host 'Enable feature:' $featureNode.Name;
Enable-SPFeature -identity $featureNode.Name -URL $url;
}
else
{
Write-Host 'Enable feature:' $featureNode.Name
Enable-SPFeature -identity $featureNode.Name
}
}
} catch [Exception] {
Write-Error $_;
log -message $_ -type "Error";
}
}
function ProcessFeatureDeactivation( [System.Xml.XmlNode] $featureNode )
{
try
{
if( ($featureNode.Url -ne $null) -and ($featureNode.Url -ne "") )
{
$url = $SPRootURL + $featureNode.Url;
#stsadm -o deactivatefeature -id $featureNode.Id -url $url
Write-Host 'Disable feature:' $featureNode.Name;
Disable-SPFeature -identity $featureNode.Name -confirm:$false -url $url;
}
else
{
#stsadm -o deactivatefeature -id $featureNode.Id
Write-Host 'Disable feature:' $featureNode.Name;
Disable-SPFeature -identity $featureNode.Name -confirm:$false;
}
} catch [Exception] {
Write-Error $_;
log -message $_ -type "Error";
}
}
function ProcessScript( [System.Xml.XmlNode] $scriptNode )
{
"Executing $($scriptNode.Name)...";
Invoke-Expression $scriptNode."#text";
}
function ProcessCopyFile( [System.Xml.XmlNode] $copyfileNode )
{
"Copying $copyfileNode.file to $copyfileNode.destination...";
$curDir = Split-Path -Parent $MyInvocation.ScriptName
$fileName = $curDir+"\"+$WSPCache+$copyfileNode.file
xcopy /Y /E /R $filename $copyfileNode.destination
if ($LASTEXITCODE -ne 0) {
Write-Error("Error Copying: " + $filename);
}
}
function Main
{
[string]$xmlName = Split-Path -Path $xmlPath -Leaf
Start-Transcript "$currentDirectory\$xmlName-log.txt";
Write-Host "Current Directory: $currentDirectory";
Write-Host "Config: $xmlPath";
if (test-path $xmlPath) {
# Found the file in the pwd or via the absolution path
#"Path Found.";
$configFileItem = Get-Item $xmlPath;
} else {
# Attempt to find the Config XML in the script's directory
#"Path Not Found.";
$idx = $inputConfigFile.LastIndexOf("\");
$configFileItem = Get-Item $(Join-Path $currentDirectory $xmlPath.substring($idx+1,$xmlPath.Length-1-$idx));
}
#$configFileItem;
$configXml = New-Object System.Xml.XmlDocument;
$configXml.Load( $configFileItem.FullName );
if ($configXml.SharePointDeploymentConfig.WSPCache) {
$WSPCache = $configXml.SharePointDeploymentConfig.WSPCache;
}
Write-Host "WSP cache location: $WSPCache"; Write-Host("");
#foreach ($taskNode in $configXml.SharePointDeploymentConfig.get_ChildNodes()|?{$_ -ne $null}) { ProcessTask $taskNode; }
$configXml.SharePointDeploymentConfig.get_ChildNodes()|?{$_ -ne $null} | % { ProcessTask $_; }
Write-Host("");
#[Microsoft.SharePoint.Administration.SPFarm]::Local.solutions | format-table -property name, Deployed, DeployedWebApplications, DeploymentState;
#"---";[Microsoft.SharePoint.Administration.SPFarm]::Local.solutions | ? { $_.LastOperationResult -ne "DeploymentSucceeded" } | % { $_.Name; $_.LastOperationDetails; $_; };"Done.";
Stop-Transcript;
}
function ProcessTask( [System.Xml.XmlNode] $taskNode )
{
#Write-Host("");Write-Host("---")
# For multiple web.config modifications, sleeping between activations reduces errors.
if ($taskNode.Sleep -ne $null) {
Sleep-For $taskNode.Sleep;
}
if( $taskNode.get_Name() -eq "Solution" )
{
#"Solution"
#$taskNode;
ProcessSolution $taskNode
}
elseif( $taskNode.get_Name() -eq "SiteCollection" )
{
#"SiteCollection"
ProcessSiteCollection $taskNode
}
elseif( $taskNode.get_Name() -eq "FeatureActivate" )
{
#"FeatureActive"
ProcessFeatureActivation $taskNode
}
elseif( $taskNode.get_Name() -eq "FeatureDeactivate" )
{
#"FeatureDeactive"
ProcessFeatureDeactivation $taskNode
}
elseif( $taskNode.get_Name() -eq "BdcAppDef" )
{
"BdcAppDef"
"Not Implemented"
ProcessBusinessDataCatalogAppDef $taskNode
}
elseif( $taskNode.get_Name() -eq "CopyFile" )
{
#"CopyFile"
ProcessCopyFile $taskNode
}
elseif( $taskNode.get_Name() -eq "Script" )
{
#"Script"
ProcessScript $taskNode
}
}
function WaitForJobToFinish([string]$SolutionFileName)
{
$JobName = "*solution-deployment*$SolutionFileName*"
$job = Get-SPTimerJob | ?{ $_.Name -like $JobName }
if ($job -eq $null)
{
Write-Host 'Timer job not found'
}
else
{
$JobFullName = $job.Name
Write-Host -NoNewLine "Awaiting job $JobFullName"
while ((Get-SPTimerJob $JobFullName) -ne $null)
{
Write-Host -NoNewLine .
Start-Sleep -Seconds 2
}
Write-Host "Finished."
}
}
function Pause ($Message="Press any key to continue...")
{
Write-Host -NoNewLine $Message
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
Write-Host ""
}
function log {
param (
[string] $message = $(throw "Please specify a message."),
[string] $type = "Information",
[string] $logName = "Application",
[string] $sourceName = "Logos Inc Deployment"
)
$EventLog = Get-EventLog -list | Where-Object {$_.Log -eq $logName}
$EventLog.MachineName = "."
$EventLog.Source = $sourceName;
$EventLog.WriteEntry($message, $type, 1103);
}
. main
After all of that, we can create our simple XML files. The number of XML files depends on the number of deployment options required. There could be 1 XML which deploys the whole environment (see example below) and a couple other XML files to deploy a small subset of functionality.Some options not used in this example and I will leave it to the reader as an exercise to implement:
- BCS
- Site collection restore
- Copy files elsewhere on the servers - this may be needed if placing files in places where the WSP won't deploy to
<Script Name="Remove Session"> Disable-SPSessionStateService; </Script> <Script Name="Remove Blocked Extension"> & .\RemoveBlockedExtension -webApplication "http://localhost"; </Script> <Script Name="Enable Session"> Enable-SPSessionStateService -DatabaseName "LogosIncSessionState" -SessionTimeout 120 </Script>
Now that we have the files needed, we can put them into the directory structure:
C:\SharePointDeploy - Powershell Scripts and XML files C:\SharePointDeploy\wsp - Cache of WSP files and other deployment filesUsage from the C:\SharePointDeploy directory:
.\SPDeploy.ps1 .\Deploy-Logos-EVERYTHING.xml
.\SPDeploy.ps1 .\Deploy-Logos-EVERYTHING.xml -remove
No comments:
Post a Comment