Showing posts with label SharePoint 2010. Show all posts
Showing posts with label SharePoint 2010. Show all posts

Saturday, July 14, 2012

Prevent Text Box Selection And Automatically Select Different Control

I ran into an interesting issue where I needed to allow a user to select a contact in a custom SharePoint 2010 application. Preventing the selection of the text box is not as straight forward as one might hope, especially handling cross browser. Since SharePoint has the concept of a People Picker, I decided that I could leverage that pattern to increase application performance. The requirement had several parts:
  1. The result needed to only display the users name
  2. The ID of the contact needed to be saved
  3. The label needs to be able to send the value back to the server, but prevent people from editing the label
The first and third points are solved by using a text box instead of a label. The ID is saved by hiding ID text box so that it is posted back with the rest of the data. We make the solution slightly more elegant by treating the text box like modern browsers handle the file input control; clicking the text box opens the dialog. There is nothing that is specific to ASP.NET and can be easily converted to standard HTML.
<asp:TextBox ID="txtContact" onfocus="focusCtrl('btnContact', event);" runat="server"></asp:TextBox>
<asp:TextBox ID="txtContactID" style="display: none;" runat="server"></asp:TextBox>
<asp:Button ID="btnContact" OnClientClick="return contactPicker('txtContactID', 'txtContact');" Text="Select" CausesValidation="false" runat="server"></asp:Button>
<asp:RequiredFieldValidator ID="rfvContact" ControlToValidate="txtContact" ErrorMessage="Contact is required" ValidationGroup="Required" Display="Dynamic" InitialValue="" CssClass="ms-error" EnableClientScript="false" runat="server">
    <img alt="Validation Error" title="Contact is required" src="/_layouts/images/EXCLAIM.GIF" />
</asp:RequiredFieldValidator>
<script type="text/javascript">
     $(function () { focusCtrl('btnContact'); });
</script>
A couple important notes about the focusCtrl function, the code above assumes that the specified control will be initially selected, but the same function is used for the text box focus event with different outcomes. The initial run without the event won't pass an event into the function which won't trigger the control click event. Once the focus event has fired off an event is passed in and causes the controls click event to fire. I included a version of the contact picker function to show how we are populating controls on the callback.
function focusCtrl(ctlID, e) {
    var ctl = $('input[id$=\'' + ctlID + '\']');
    e = e || window.event;
    var t = (e) ? e.target || e.srcElement : null;
    if (ctl.length > 0) {
        ctl.focus();
        if (t != null) {
            ctl.click();
        }
    } else if (e || window.event) {
        $(t).blur();
        //Focus next textbox
        //$(t).next("input:not(input[type='submit']):visible").focus();
    }
    return false;
}

function contactPicker(txtID, txtLabel) {
    var options = {
        url: '../Contacts/ContactPicker.aspx',
        title: 'Contact Picker',
        allowMaximize: true,
        showClose: true,
        showMaximized: false,
        dialogReturnValueCallback: function (dialogResult, returnValue) {
                                        if (dialogResult == SP.UI.DialogResult.OK) {
                                            var ret = unescape(returnValue),
                                                name = ret.split(";#")[1];
                                            $("input[id$='" + txtName + "']").val(name);
                                            $("input[id$='" + txtID + "']").val(ret);
                                        }
                                    }
    };
    SP.UI.ModalDialog.showModalDialog(options);
    return false;
}

Monday, February 20, 2012

Log Manager With Extra Information Using SharePoint 2010 Unified Logging Service

Here is a wrapper class which covers the SharePoint ULS interface and logs information to the ULS. I attempted to add as much information as I could and allow for a property dictionary to be appended to the log entry.

namespace Demo.Web.Logging
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.SharePoint.Administration;
    using System.Diagnostics.Eventing;
    using System.Runtime.InteropServices;
    using System.Security.AccessControl;
    using System.Security.Principal;
    using System.Threading;
    using System.Diagnostics;

    public class LogManager
    {
        public static void Write(Exception ex, SPDiagnosticsCategory category, TraceSeverity severity)
        {
            if (ex.Data != null && !ex.Data.Contains("CallingFunction"))
            {
                ex.Data.Add("CallingFunction", System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name);
            }
            Dictionary<string, object> props = null;
            if (ex.Data != null && ex.Data.Count > 0)
            {
                props = new Dictionary<string, object>();
                foreach (string key in ex.Data)
                {
                    props.Add(key, ex.Data[key]);
                }
            }
            LoggingService.Log(ex.ToString(), props);
        }

        public static void Write(string message, Dictionary<string, object> props)
        {

            LoggingService.Log(message, props);
        }

        public static void Write(string message, Dictionary<string, object> props, TraceSeverity severity)
        {
            LoggingService.Log(message, props, severity);
        }

        public static void Write(string message, Dictionary<string, object> props, TraceSeverity severity, string categoryName)
        {
            LoggingService.Log(message, props, severity, categoryName);
        }

        public static void Write(string message, Dictionary<string, object> props, TraceSeverity severity, SPDiagnosticsCategory category)
        {
            //SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("My Category", TraceSeverity.Unexpected, EventSeverity.Error), TraceSeverity.Unexpected, ex.Message, ex.StackTrace);
            LoggingService.Log(message, props, severity, category);
        }
    }

    #region [ Internal ULS Access Implementation ]
    
    internal class LoggingService : SPDiagnosticsServiceBase
    {
        public static string DemoDiagnosticAreaName = "Demo";
        private static LoggingService _Current;
        public static LoggingService Current
        {
            get
            {
                if (_Current == null)
                {
                    _Current = new LoggingService();
                }

                return _Current;
            }
        }

        private LoggingService()
            : base("Demo Logging Service", SPFarm.Local)
        {

        }

        protected override IEnumerable<spdiagnosticsarea> ProvideAreas()
        {
            List<spdiagnosticsarea> areas = new List<spdiagnosticsarea>
            {
                new SPDiagnosticsArea(DemoDiagnosticAreaName, new List<spdiagnosticscategory>
                {
                    new SPDiagnosticsCategory("Application", TraceSeverity.Unexpected, EventSeverity.Error),
                    new SPDiagnosticsCategory("WebService", TraceSeverity.Unexpected, EventSeverity.Error),
                    new SPDiagnosticsCategory("WebConfigMod", TraceSeverity.Unexpected, EventSeverity.Error)
                })
            };

            return areas;
        }

        public static void Log(string message)
        {
            Log(message, null);
        }

        public static void Log(string message, Dictionary<string, object> props)
        {
            Log(message, props, TraceSeverity.Unexpected);
        }
        public static void Log(string message, Dictionary<string, object> props, TraceSeverity severity)
        {
            Log(message, props, TraceSeverity.Unexpected, "Application");
        }
        public static void Log(string message, Dictionary<string, object> props, TraceSeverity severity, string categoryName)
        {
            SPDiagnosticsCategory category = LoggingService.Current.Areas[DemoDiagnosticAreaName].Categories[categoryName];
            Log(message, props, severity, category);
        }
        public static void Log(string message, Dictionary<string, object> props, TraceSeverity severity, SPDiagnosticsCategory category)
        {
            if (props == null)
            {
                props = new Dictionary<string, object>();
            }
            if (!props.ContainsKey("CallingFunction"))
            {
                props.Add("CallingFunction", (new StackTrace()).GetFrame(1).GetMethod().Name);
            }
            string propSerial = "{" + string.Join(",", props.Select(
                d => string.Format("\"{0}\":\"{1}\"", d.Key, d.Value.ToString())
                ).ToArray()) + "}";

            //SPDiagnosticsCategory category = LoggingService.Current.Areas[DemoDiagnosticAreaName].Categories[categoryName];
            LoggingService.Current.WriteTrace(0, category, TraceSeverity.Unexpected, message + " ~ Properties: " + propSerial);
        }
    }

    #endregion
    }
}

Wednesday, December 28, 2011

SharePoint 2010 Custom Application Page Affix Ribbon To Top Using CSS

Migrating existing applications into SharePoint can be difficult depending on the JavaScript functionality of the old code. Using the default SharePoint 2010 custom application page, the s4-workspace is a div that is re-sized and scrollable to allow the SharePoint ribbon to display. I don't know why Microsoft felt it necessary to do far more work than necessary to fix a div to the top of the window.

Below is the code I used to fix the scroll bars on the page. This makes the ribbon not fixed and will scroll out of view. This could be enough if you don't use the ribbon in your pages.
body {
    overflow: auto ! important;
}
body.v4master { 
    height:inherit; 
    width:inherit; 
    overflow:visible!important;
}

body #s4-workspace {
   overflow-y:auto !important;
   overflow-x:auto !important;
   height:auto !important;
}

If the ribbon absolutely must be at the top of the page. You can add this bit of code after the above code to properly affix the div to the top of the visible window. My tests showed that this worked for me in IE8, IE 9, and Firefox.

body #s4-ribbonrow {
    left: 0;
    position: fixed;
    top: 0;
    width: 100%;
    z-index: 101;
}
body #s4-workspace {
    padding-top: 44px;
}

That should be it. Not too hard.





Tuesday, December 27, 2011

SharePoint 2010 Custom Application Page jQuery Lightbox Plug-in Fix

I used a fantastic jQuery lightbox plug-in which can be found at http://leandrovieira.com/projects/jquery/lightbox/. There was almost no setup involved. That is, until it meets SharePoint 2010 custom application pages. Since the page doesn't use the body scroll bars and creates faux-scroll bars in the s4-workspace div, it is possible that the image is too large for the visible area and there are no functional scroll bars to view the rest of the picture. I should suffix my last statement with the fact that this probably would have happened with any lightbox plug-in; it just happened that this is the plug-in I used.

I was tasked with finding a fix and below is the result. I only had to add code to the beginning of two functions: _set_interface and _finish. The short of it is that I cache the important styles that I am going to change, then I modify the styles to enable the page scroll bars. When the lightbox is closed, the cached styles are restored.

var htmlbody = $("BODY"),
    bodyMaster = $("body.v4master"),
    bodyWorkspace = $("body #s4-workspace"),
    savedCSS = {
        BodyOverflow: htmlbody.css("overflow"),

        BodyMasterHeight: bodyMaster.css("height"),
        BodyMasterWidth: bodyMaster.css("width"),
        BodyMasterOverflow: bodyMaster.css("overflow"),

        BodyWorkspaceOverflowY: bodyWorkspace.css("overflow-y"),
        BodyWorkspaceOverflowX: bodyWorkspace.css("overflow-x"),
        BodyWorkspaceHeight: bodyWorkspace.css("height")
    };
    settings.SavedCSS = savedCSS;
            
htmlbody.css({ "overflow": "auto" });
bodyMaster.css({ "height": "inherit", "width": "inherit", "overflow": "visible" });
bodyWorkspace.css({ "overflow-y": "auto", "overflow-x": "auto", "height": "auto" });

var htmlbody = $("BODY"),
    bodyMaster = $("body.v4master"),
    bodyWorkspace = $("body #s4-workspace"),
    savedCSS = settings.SavedCSS;

htmlbody.css({ "overflow": savedCSS.BodyOverflow });
bodyMaster.css({ "height": savedCSS.BodyMasterHeight, "width": savedCSS.BodyMasterWidth, "overflow": savedCSS.BodyMasterOverflow });
bodyWorkspace.css({ "overflow-y": savedCSS.BodyWorkspaceOverflowY, "overflow-x": savedCSS.BodyWorkspaceOverflowX, "height": savedCSS.BodyWorkspaceHeight });

SharePoint 2010 Custom Application Page Scroll To Top On Postback

SharePoint 2010 is full of wonderful features that make developers' lives just a bit harder. I ran across an issue where validation was returning a message back to the screen, the page would display the page scrolled to the top and then immediately scroll down to the position to the location of the page prior to posting back. I have had previous run-ins with the s4-workspace, but nothing JavaScript related. I tried several avenues for solutions:

1. Setting the page directive attribute "MaintainScrollPosition" to be false
2. Registering a start up script: $(window).scrollTop(0)
3. Registering a start up script: $("#s4-workspace").scrollTop(0)
4. Attempted to register the the functions via a client script block to add a "pageLoaded" event

The short of it was that none of these worked. I decided to dive in to the HTML source and discovered a "_maintainWorkspaceScrollPosition" hidden field. This looked amazingly like the MaintainScrollPosition functionality, I thought I might be on the right path. The "Workspace" term jumped out at me since the SharePoint custom application page's content is in the s4-workspace; I started to get the feeling that this was a SharePoint feature. After searching all the files for the hidden field, I discovered it was only referenced in the SharePoint JavaScript files. Searching the internet did not yield any solutions on ways to disable the feature. So I generated a function that I would execute to scroll the page to the top.

function scrollToTop() {
    $(window).scrollTop(0);
    $("#s4-workspace").scrollTop(0);
    $("#_maintainWorkspaceScrollPosition").val(0);
}

The function above probably does more than required, but I don't control the Master Page and need to make sure the page can tolerate changes to Master Page style changes.

The server side needs to register a script to execute on the post back. This is a simple line that can be thrown about anywhere.

ScriptManager.RegisterStartupScript(this, this.Page.GetType(), "scrollToTop", "scrollToTop();", true)

Tuesday, August 16, 2011

A Domain Specific Language For SharePoint 2010 Deployment

Visual Studio 2010 and SharePoint 2010 has made great improvements to deployments, but still lacks important features. This method is designed to allow for:
  • 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
To achive this, we will use a Domain Specific Language (DSL) using XML and powershell as the processor.

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
Note: Due to an apparent limitation in the syntax highlighter functionality, the XML brush does not handle self closing nodes correctly. For presentation purposes, I have changed the self-closing nodes to have end tags.

	
	
		
	
	
	<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 files
Usage from the C:\SharePointDeploy directory:
.\SPDeploy.ps1 .\Deploy-Logos-EVERYTHING.xml
.\SPDeploy.ps1 .\Deploy-Logos-EVERYTHING.xml -remove