Eric Kraus' Microsoft Blog

innovation. strategy. Microsoft.

Automating your SharePoint Development with PowerShell

Automating your SharePoint Development with PowerShell

  • Comments 1

Developing in SharePoint is much like preparing a large holiday meal.  It involves strict ingredients for the recipes, which must be prepared in the correct order using the finite tools you have in your kitchen.  No matter how many times you prepare the same meal year-after-year, it is just as challenging the next year around…and there always seems to be that special “request” for something unique. 

Unless you have a Pee-wee Herman Rube Goldberg machine in your kitchen…the meal requires some planning and structure! (ok maybe not the best analogy, I just wanted to say “Pee-wee Herman”)

There are lots of third-party tools out there that help us in the development process, but none seem to incorporate the entire solution development process (including deployment) in a “TRANSPARENT” manner.  Meaning, I want to see and know what’s going on and I want to be able to tweak it if I need to.

The problem isn’t that a tool can’t be created, it’s the lack of development discipline by SharePoint developers.  And creating “features” is really only half of the answer.  I won’t try and pawn off my solution as “the best” or “the only”.  But it does work, and it structurally makes sense.  I also won’t claim all of it as my own idea, so I’ll give credit to those who have paved the path before me (Andrew Connell, Ted Pattison, etc.)  

For me, the key is not the ability to easily create a web part, it’s the ability for my customers to support the development solution after I leave.  And so here’s what works for me:

1.  You absolutely HAVE to create SharePoint Solution Package(s) for your development components.  Features alone are great, but the deployment and management for them is based on the solution package.  (How to create a SharePoint Solution Package)

2.  Create your project folder structure to mirror the 12 Hive.  (For now, ignore the section on “Deployment”).  This will make it MUCH easier to build automation into our project and also easier when it comes time to that pesky *.ddf file. I also create a separate project/folder for each “functional” development component.

d:\projects\<Company>\<Company>.SharePoint.Finance\  (where my *.sln file sits)
d:\projects\<Company>\<Company>.SharePoint.Finance\<Company>.SharePoint.Finance.Branding\
d:\projects\<Company>\<Company>.SharePoint.Finance\<Company>.SharePoint.Finance.ContentTypes\
d:\projects\<Company>\<Company>.SharePoint.Finance\<Company>.SharePoint.Finance.WebParts\
d:\projects\<Company>\<Company>.SharePoint.Finance\<Company>.SharePoint.Finance.Solution\


3.  Use VERY GOOD naming schemes for your code.
           I like to use:   <Company>.SharePoint.<Application>.<Feature type>.<Feature name>

               so for instance:  
                      Microsoft.SharePoint.Finance.WebParts.MyCustomWebPart   class in 
                      Microsoft.SharePoint.Finance.WebParts.dll   assembly

     Not only does this leave it easy to find things later, but it adds that all too important structure and repetitiveness to your solution.

4.  Build automation.  This, I think, is the least performed function during development and where I want to focus on in this post.

Where can we add automation?

1.  Copying files between projects
2.  Building .wsp solution packages
3.  Deploying .wsp solution packages
4.  Deploying XML configuration files
5.  Logging builds, releases, errors, etc.

Copying Files Between Projects

I like using user controls in my SharePoint projects.  The problem with these (and pages), is that you create them in a Class Library project, which makes them cumbersome to debug.  I create all of my user controls in a separate web application project.  This makes them easy to test and debug in mock ASPX pages.  Then, upon successful build, I use a PowerShell script to copy the files over to my Microsoft.SharePoint.Finance.UserControls project.

It looks something like this:

function Copy-SPControls()
{
    ##two parameters needed
    ##    $prjSrc  - Path to the source Web Application FOLDER User Controls
    ##    $prjDest - Path to the SharePoint Feature PROJECT for User Controls

    param($prjSrc=$(throw "You must pass the path to the source project and folder where User Controls are developed/tested"), $prjDest=$(throw "You must pass the path to the destination project where User Controls are included in a feature"))
    trap [Exception] {  continue;}

    $userControlsDest = $prjDest;
    if($userControlsDest.EndsWith("\") -eq $false) {$userControlsDest = $userControlsDest + "\";}
    $userControlsDest = $userControlsDest + "TEMPLATE\CONTROLTEMPLATES";

    COPY $prjSrc $userControlsDest -RECURSE -FORCE
}

Command:
powershell.exe import-module –name SPoshMod –force;Copy-SPControls -prjSrc  $(ProjectDir)\UserControls  –prjDest $(SolutionDir)Company.SharePoint.Finance.UserControls

Building .WSP Solution Packages

There are two parts to the WSP solution package.  One is getting all of the files I need into one project.  The other is calling makecab.exe to make my WSP file for me.

Move all files to “Solution” project
When I create Visual Studio solution for SharePoint development, I always create one project named <Company>.SharePoint.<Application>.Solution    I call this my “Solution” project.  This project will store all of my TEMPLATE files, my manifest.xml, and *.ddf file needed to make my WSP file.  To get all of the files down there is actually quite easy. 

When I build the “Solution” project (which is dependent on all other projects), it will call a script and pass in the Visual Studio Solution directory as well as its own project directory.  Then for each project under the solution, the script will copy all of the files in the TEMPLATE folder down to the “solution” project’s TEMPLATE folder.  It will also get the assembly from the “bin\debug” folder and copy that to an “ASSEMBLIES” folder in the “solution” project.  The end result is that I have all of my features, assemblies, etc. ready for me to build a WSP.

Here’s what the script looks like:

function Copy-SPFeatureFiles()

    ##three parameters needed
    ##    $buildType -  Debug|Release  right now, "debug" is implemented
    ##    $solFldr - Visual Studio solution folder - all project folders underneath
    ##    $spSolFldr - project that contains the SharePoint Features for building the SharePoint Solution Package (WSP)
    param($buildType=$(throw "You must pass the build type: debug|release"), $solFldr=$(throw "You must pass the absolute path to the visual studio solution folder"), $spSolFldr=$(throw "You must pass the absolute path to the SharePoint (feature) solution folder"))
   
    trap [Exception] {  continue;}
   
    [System.Reflection.Assembly]::LoadWithPartialName("System.IO");
   
    $path = $solFldr;
    if($path.EndsWith("\") -eq $false) {$path = $path + "\";}
   
    $dest = $spSolFldr;
    if($dest.EndsWith("\") -eq $false)  {$dest = $dest + "\";}
    $destTemp = $dest.ToLower();
   
    $destTEMPLATE = $dest + "12";
    $destASSEMBLIES = $dest + "GAC";
   
    $diRoot = new-object System.IO.DirectoryInfo($dest);
    
    $diTEMPLATE = new-object System.IO.DirectoryInfo($destTEMPLATE);
    if($diTEMPLATE.Exists -eq $true) 
    {
        $diTEMPLATE.Delete($true);
    }
   
    $diRoot.CreateSubdirectory("12");
    $diTEMPLATE = new-object System.IO.DirectoryInfo($destTEMPLATE);
    $diTEMPLATE.CreateSubdirectory("TEMPLATE");
   
    $diASSEMBLIES = new-object System.IO.DirectoryInfo($destASSEMBLIES);
    if($diASSEMBLIES.Exists -eq $true) 
    {
        $diASSEMBLIES.Delete($true);
    }
   
    $diRoot.CreateSubdirectory("GAC");
   
    $diSource = new-object System.IO.DirectoryInfo($path);
    foreach($dir in $diSource.GetDirectories())
    {
        $dirTemp = $dir.FullName.ToLower();
        if($dirTemp.EndsWith("\") -eq $false)  {$dirTemp = $dirTemp + "\";}
       
        if($dirTemp -ne $destTemp)
        {
            $dirSource = new-object System.IO.DirectoryInfo($dir.FullName);
            $dirSource = $dirTemp + "TEMPLATE\*";

            $dirBin = new-object System.IO.DirectoryInfo($dir.FullName);
            $dirBin = $dirTemp + "bin\*.dll";

            $dirBinType = new-object System.IO.DirectoryInfo($dir.FullName);
            $dirBinType = $dirTemp + "bin\" + $buildType + "\*.dll";

            $dirDestAssem = $dest + "GAC";
            $dirDestTempl = $dest + "12\TEMPLATE";

            copy "$dirSource" "$dirDestTempl" -RECURSE -FORCE
            copy "$dirBinType" "$dirDestAssem" -RECURSE -FORCE
        }
    }
}


Command:

powershell.exe import-module –name SPoshMod –force;Copy-SPFeatureFiles –buildType “debug” –solFldr $(SolutionDir)  -spSolFldr $(ProjectDir)


Making the Solution

After all of the files are in the “Solution” project, it’s very easy for me to create the WSP.  I use WSPBuilder from CodePlex.  Unzip the files into the root of the “Solution” project.

When the “Solution” project builds, it calls the following command after the Copy-SPFeatureFiles script.  This will automatically create my manifest.xml and solution.ddf files.

Command:
$(ProjectDir)\WSPBuilder.exe –SolutionPath $(ProjectDir)\SOLUTION –OutputPath $(ProjectDir)\SOLUTION –WSPName <Company>.SharePoint.wsp

Thanks for the tip, @AndersRask

Deploying .WSP Solution Packages

Here, there is a bit of flexibility in when this script runs.  Some developers like to have it run on successful build of the “Solution” project.  Some like to also make it only run when built in “Release”.  Others just run it as they need.  I like to build my projects periodically (without deployment), so I will choose the last option for simplicity.

Nothing complicated here, just wrapping the STSADM commands with PowerShell.  The nice thing is that the functions are grouped so that I only have to call the Upgrade-Solution  function and everything will be updated for me.

function Add-SPSolution()
{
    param($filename=$(throw "You must pass the filename path of the solution"))
    ##$filename = "e:\projects\customers\<project folder>\SOLUTION\PACKAGE\SharePoint.wsp"
    trap [Exception] {  continue;}

    stsadm -o addsolution -filename $filename
}
function Get-Solutions()
{
    stsadm -o enumsolutions
}
function Get-SPSolution()
{
    param($name=$(throw "You must pass the name of the solution"))
    ##$name = SharePoint.wsp
    $solutions = get-solutions
    $solutionsXML = [xml]$solutions
    $solutionsNode = [System.Xml.XmlNode]$solutionsXML
    $solutionsNode.SelectSingleNode("Solutions/Solution[File='$name']")
}
function Deploy-SPSolution()
{
    param($name=$(throw "You must pass the name of the solution"),$url=$(throw "You must pass the url to the site for deployment"),[switch]$allowgacdeployment)
    ##$name = SharePoint.wsp
    if($allowgacdeployment -eq $true)
    {
        stsadm -o deploysolution -name $name -url $url -force -immediate -allowgacdeployment
    }
    else
    {
        stsadm -o deploysolution -name $name -url $url -force -immediate
    }
}
function Retract-SPSolution()
{
    param($name=$(throw "You must pass the name of the solution to retract"),$url="",[switch]$allcontenturls)
    ##$name = SharePoint.wsp
    if($allcontenturls -eq $true)
    {
        stsadm -o retractsolution -name $name -allcontenturls -immediate
    }
    else
    {
        if($url -eq "")
        {
            throw "You must pass the url if -allcontenturls is set to false"
        }
        else
        {
            stsadm -o retractsolution -name $name -url $url -immediate
        }
    }
}
function Delete-SPSolution()
{
    param($name=$(throw "You must pass the name of the solution to delete"))
    ##$name = SharePoint.wsp
    stsadm -o deletesolution -name $name
}
function Upgrade-SPSolution()
{
     $filename = “HARD CODE PATH TO VISUAL STUDIO WSP OUTPUT FILE”
     $name = “HARD CODE SOLUTION NAME”
     $url = “HARD CODE URL”

     Retract-SPSolution –name $name –url $url

     Delete-SPSolution –name $name

     Start-Sleep –seconds 5

     Add-SPSolution –filename $filename

     Deploy-SPSolution –name $name  -url $url
}

Command:
Now, all I have to do is build my “Solution” project, and call manually call “Import-Module –name SPoshMod –force;Upgrade-SPSolution” from a powershell prompt

Deploying XML Configuration Files

Again, there is some flexibility here in where your configuration files are deployed and when they are moved to their final resting place.  I like to package everything up into one WSP, so my config files will be deployed out to the CONFIG folder in the 12 Hive.  This also makes it very easy for me (or PowerShell) to go looking for them.

function Deploy-SPConfigFiles()
{

    trap [Exception] {  continue;}

    $12Hive = “C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\”
    $configSource = $12Hive + “CONFIG\<CompanyName>”

    $configDest = “D:\wss\virtualdirectories\<your web app folder>\”

    COPY $configSource $configDest -RECURSE -FORCE
}



Logging Builds, Releases, Errors, etc.

Lastly, and completely optional…some customer have configured their deployments to use TFS, NAnt, etc. and want to monitor progress during automated build and deploys.  I don’t have any example scripts here, but remember PowerShell operates in a managed runtime.  This means you can leverage the .NET assemblies in System.Data.SqlClient and System.Diagnostics to do some logging and error tracking.

-------------------------- 

Calling Scripts From Visual Studio

Now, to truly call this automation, we will rely on trusty old MSBuild.  To add these PowerShell function to your projects is fairly straightforward:

1.  Right click on the project, and choose Unload Project

2.  Right click on the now “Unavailable” project, and choose Edit <Project Name>

3.  Scroll to the bottom and add your property command to the <PropertyGroup> node
               <PropertyGroup>
                     <CopyUserControls>
powershell.exe import-module –name SPoShMod –force;Copy-SPControls –prjSrc $(ProjectDir)\UserControls –prjDest $(SolutionDir)Microsoft.SharePoint.UserControls</CopyUserControls>
               </PropertyGroup>

Then, just up a little bit above that, add the following section just after the <Import Project=”…….” /> node(s):

<Target Name=BeforeBuild>

</Target>
<Target Name=AfterBuild>
     <Exec Command=$(CopyUserControls)/>
</
Target>

4.  Now, close the project file you are editing.

5.  Lastly, right click on your “Unavailable” project again and choose Reload Project

Now, every time you build your web application, it will execute the PowerShell script and copy your user controls over to the feature project.  Just apply this same technique for the other scripts and you’re good to go.

These scripts are all included in SPoshMod (THE SharePoint PowerShell Module) on codeplex.  http://www.codeplex.com/SPoshMod

  • Developing in SharePoint is much like preparing a large holiday meal.&#160; It involves strict ingredients

Page 1 of 1 (1 items)
Leave a Comment
  • Please add 8 and 8 and type the answer here:
  • Post