Tools for Sharepoint Deployment

January 22nd, 2009 Michael Bell No comments

I’ve tried WSPBuilder, the Extensions, and STSDev all. They were all very useful in starting out and in simple projects. I’ve found that at least for my projects, manually writing a DDF file and using the makecab myself turns out to be far, far easier. When you start doing something that’s a little out of the ordinary, the tools can really eat your time. You end up fighting the tools automated processes rather than the code you’re suppose to be writing.

To build your deployment package, you will need to build a DDF file, or Diamond Directive File. This tells makecab what to include in your WSP, and where it should extract to.
Here is an example of a simple DDF file:
;This file is for WSP CAB Generation
;A WSS or in this case MOSS solution file is essentially a .cab file,
;use the makecab.exe tool to create the solution package.
;The makecab.exe tool takes a pointer to a .ddf file,
;which describes the structure of the .cab file. 
;The format of a .ddf file is basically that
;you declare a standard header and
;then enumerate, one file per line, the set of files by where they live on disk,
;separated by where they should live in the .cab file

.OPTION EXPLICIT     ; Generate errors
.Set CabinetNameTemplate=”MyCoolTool.wsp”
.set DiskDirectoryTemplate=CDROM ; All cabinets go in a single directory
.Set CompressionType=MSZIP;** All files are compressed in cabinet files
.Set UniqueFiles=”ON”
.Set Cabinet=on
.Set DiskDirectory1=”..\..\Package”
;All file reference should be from the project root
;Files to place into the CAB Root

MyCoolTool.dll

..\..\Manifest.xml

.Set DestinationDir=MyCoolTool
..\..\Feature.xml

.Set DestinationDir=MyCoolTool\ContentPages
..\..\ContentPages\Module.xml
..\..\..\MyCoolToolWebApp\Tool.aspx
..\..\..\
MyCoolToolWebApp\Tool.aspx.cs

.Set DestinationDir=ControlTemplates\MyCoolTool
..\..\..\MyCoolToolWebApp\Controls\MyCoolToolControl.ascx           
..\..\..\
MyCoolToolWebApp\Controls\MyCoolToolControl.ascx.cs       
..\..\..\
MyCoolToolWebApp\Controls\MyCoolToolControl.ascx.designer.cs

.Set DestinationDir=IMAGES\MyCoolTool
..\..\IMAGES\MyCoolFeaturePicture.gif

And on your Project Properties, go to the “Build Events” tab, then the “Post build event command line:” area, and type in:
makecab /f “$(TargetDir)NameOfMy.ddf”

This should build your WSP file for you every time you compile. When you’re ready for a deploy, build some batch files to make it easy. I actually joined my retract and deploy script together so I wouldn’t have to wait on one to finish and then click to do the other. Might as well get them both overwith. My batch file:

IF [%1]==[] (
  set APPURL=”http://localhost/”
) else (
  set APPURL=”%1″
)
@set PATH=C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN;%PATH%

stsadm -o deactivatefeature -name MyCoolTool-url %APPURL% -force
stsadm -o retractsolution -name
MyCoolTool.wsp -immediate -url %APPURL%
stsadm -o execadmsvcjobs
stsadm -o deletesolution -name
MyCoolTool.wsp -override
stsadm -o execadmsvcjobs

stsadm -o addsolution -filename MyCoolTool.wsp
stsadm -o execadmsvcjobs
stsadm -o deploysolution -name
MyCoolTool.wsp -url %APPURL% -immediate -allowCasPolicies -allowgacdeployment
stsadm -o execadmsvcjobs
stsadm -o activatefeature -name
MyCoolTool-url %APPURL%

That’s it for deployment. Of course configuration is another issue (manifest, elements, feature xml files), but that’s another post!

Categories: MOSS, SharePoint 2007 Tags:

Adding a Lookup Column to a SharePoint List

January 20th, 2009 Michael Bell No comments

This took me a heckuva lot longer to get working right than I will ever admit. I thought the end result was nice and tidy. This example shows how to take a list from one web, and populate a column in another list based on the values from the first. Modifications to the first list will be reflected in any columns that reference that list via lookup.

SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite website = new SPSite(SPContext.Current.Site.ID))
{
website.AllowUnsafeUpdates = true;

SPWeb newWeb = website.OpenWeb(_newWeb.ID);
newWeb.AllowUnsafeUpdates = true;

#region Add Lookup Column to new doc lib
SPDocumentLibrary newDocLib = (SPDocumentLibrary)newWeb.Lists["Documents"];

// Grab list guid and the web container
Guid customListGuid = ListManager.GetGuid(SPContext.Current.Web, “MyCustomWebList”);

newCRFDocLib.Fields.AddLookup(”Some Descriptive Field Name”, customListGuid, SPContext.Current.Web.ID, false);

// Add the new lookup field to the default view.
SPField fld = newCRFDocLib.Fields["Some Descriptive Field Name"];
SPView defaultView = newCRFDocLib.DefaultView;
SPViewFieldCollection viewFields = defaultView.ViewFields;

viewFields.Add(”Some Descriptive Field Name”);
defaultView.Update();

ListManager.ReorderField(newWeb.Lists["Documents"], fld, 0);
}
}

public static Guid GetGuid(SPWeb web, String listName)
{
foreach (SPList list in web.Lists)
{
if (true == list.Title.Equals(listName, StringComparison.OrdinalIgnoreCase))
return list.ID;
}
return Guid.Empty;
}

Categories: MOSS, SharePoint 2007, c# Tags:

Automating SharePoint web.config Changes

January 20th, 2009 Michael Bell No comments

Here is my take on automating web.config changes in SharePoint 2007. In this example I make some changes so that I see detailed errors and stack traces on exceptions that are thrown within SharePoint. A few things I learned while playing around with this the first few times:

  • If you have mods going in in several places, or have lots of mods, complete them all and then do your Update() and ApplyWebConfigModifications(). If you do several saves, you will run into some strange looking problems during your deploys. It acts like there’s another package that didn’t get finished deploying and stop you.
  • Be mindful of the “Owner” property. It keeps track of who owns what modifications. It’s easy to wind up with orphans while you’re just learning how to do this, and you can hack up the web.config pretty good like that. If you end up fouling up your config settings like I did (Hey, I’m not an XPath guy!), check out Vincent Rothwell’s post on cleaning it up.
  • Brush up on XPath before you start executing this stuff.

Keep in mind that in this example I put the Update and Apply in the end just for the purpose of providing a complete example. In a real world scenario, you could use this method while you’re adding other modifications, but you wouldn’t want to save those mods until they are all added to the collection. 1 save per activation.

internal static void setErrorDetailSettings(SPWebApplication WebApp, string Owner, bool ShowErrors)
{

    SPWebApplication WebApp = siteCollection.WebApplication;
    SPServiceCollection services = WebApp.Farm.Services;
    SPWebApplicationCollection WebApps = services.GetValue<SPWebService>().WebApplications;

    string callStackValue = (ShowErrors) ? “true” : “false”;
    string customErroresValue = (ShowErrors) ? “Off” : “On”;

    SPWebConfigModification stackTraceModification =
        new SPWebConfigModification(“CallStack”, “configuration/SharePoint/SafeMode”);
    stackTraceModification.Value = callStackValue;
    stackTraceModification.Sequence = 1;
    stackTraceModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
    WebApp.WebConfigModifications.Add(stackTraceModification);

    SPWebConfigModification customErrorsModification =
        new SPWebConfigModification(“mode”, “configuration/system.web/customErrors”);
    customErrorsModification.Value = customErroresValue;
    customErrorsModification.Sequence = 1;
    customErrorsModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
    WebApp.WebConfigModifications.Add(customErrorsModification);

    WebApp.Update();
    WebApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
}

Categories: ASP.NET, MOSS, SharePoint 2007, c# Tags:

“Column is constrained to be unique. Value is already present.” Error with in-memory table row inserts

November 30th, 2006 Michael Bell No comments

With my in-memory log-hit table I talked about in my last entry, I started getting these errors a few times a day, and it was enough to bomb the whole app:
Column ‘blahblah’ is constrained to be unique.  Value ‘-4862′ is already present.
Column ‘blahblah’ is constrained to be unique.  Value ‘-4847′ is already present.
Column ‘blahblah’ is constrained to be unique.  Value ‘-4768′ is already present.

Let me show you how the ‘blahblah’ column is built in the table construction:
DataColumn dc;

dc = new DataColumn(“HitID”, typeof(Int32));
dc.Unique = true;
dc.AutoIncrement = true;
dc.AutoIncrementSeed = -1;
dc.AutoIncrementStep = -1;
_MyTable.Columns.Add(dc);

So how in the world is this happening? Let me show the piece of code that was throwing the exception:
DataRow row = MemoryTables.HitList.NewRow();
row["FileID"] = Convert.ToInt32(context.Request.QueryString["fi"]);
row["FileName"] = fi.Name;
row["Username"] = _Username;
row["MemberID"] = _MemberID;
row["Success"] = true;
row["Bytes"] = fi.Length;
MemoryTables.HitList.Rows.Add(row);

Fairly straight forward.. right? With each hit to an image, this thing fires and inserts a new row into the in-memory table. About 20 rows get inserted a second. After much trial and error, I completely eliminated this problem by re-arranging a few lines of code. The new code reads:
DataRow row = MemoryTables.HitList.NewRow();
MemoryTables.HitList.Rows.Add(row);
row["FileID"] = Convert.ToInt32(context.Request.QueryString["fi"]);
row["FileName"] = fi.Name;
row["Username"] = _Username;
row["MemberID"] = _MemberID;
row["Success"] = true;
row["Bytes"] = fi.Length;

By creating a new row and adding it to the table before I populate it with any values, I eliminate my problem. I’d love to hear an explanation if anyone has one…

Categories: ASP.NET, c# Tags: