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); } . mainAfter 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