Karine Bosch’s blog post on the SharePoint DateTimeControl is really great for understanding how to use this control in your custom web parts or application pages. It’s probably one of the only really good resources out there on this control.

So to summarize, the SelectedDate property let’s us know what date the user has selected. However, if the user didn’t enter a date the SelectedDate property returns Today’s date. Huh?

This presents a problem because of form submission. Let’s review a scenario where this causes issues:

  1. User enters a date in a DateTimeControl: April 3, 2011.
  2. User submits. Date is saved as April 3, 2011 in the SharePoint DateTime field.
  3. User loads form, and clears out the date in the DateTimeControl and hits submit.
  4. Date is saved as April 1, 2011 (Today’s date).

We don’t want it saved as Today’s date, we want it saved as blank. To get around this, we constantly have to check whether a value is entered using the IsDateEmpty property, and if so, handle the save appropriately. See my code sample below for an example of this.

if (!dateControl.IsDateEmpty)
{
    item["Deadline"] = dateControl.SelectedDate;
}
else
{
    item["Deadline"] = null;
}

I’ve got a backlog of articles that are in draft mode, waiting to be proof read and published, but this particular topic I wanted to take a minute and write about.

There is a whole slew of information out there for the best ways to create a Cloneable SharePoint 2007 Environment. Why might you want to do that? Well for our particular case, we do a lot of training here at Black Ninja, so depending on how many students we register, we like to provide each with their own isolated SharePoint 2007 environment.

In the past, we’ve done this a bit too manually for my liking. So this time around, I took some time and looked into our options for creating cloneable vm’s easily and without hassle. My starting point was Michael Sivers awesome blog post titled: “How to Create a Cloneable SharePoint Development Environment“. I followed his instructions to a point, and because I’m running a VMWare fusion environment, I had to tweak things a little bit. I also wanted to exclude the auto farm creation and deployment because I wanted to do those steps myself.

Software – Below is a list of software I used for my training VMs:

1. VMWare fusion for the mac, installed on my Mac Pro server that we use to host our VMs. This machine has 16GB of ram (soon to be 32GB) and an 8-core process.
2. Microsoft Windows Server 2003 Enterprise R2 with SP2
3. Microsoft SQL Server 2005 Developer Edition
4. Microsoft SharePoint Server 2007
5. Microsoft Visual Studio 2008 Professional
6. Visual Studio Extensions for WSS 1.3
7. Office Server SDK
8. SharePoint Designer 2007 with SP1
9. Firefox, Firebug and Web Developer extensions
10. Office 2007 Ultimate
11. Sysprep for Win2K3 SP2

Base Image – Below is a list of steps for creating the base image:

I used all of Michael’s steps except right before the Install Sysprep for Win2K3 SP2 step, I did the following:

1. Install SharePoint Designer 2007 with SP1
2. Install Firefox, Firebug and the Web Developer extensions
3. Install Office 2007 Ultimate
4. Run Windows Update
5. resume with Michael’s step: Install Sysprep for Win2k3 SP2

So what we’ve essentially just created is the base machine for all the other clone’s we’re going to create. The first step is to shut down this new VM and make a copy/backup of it. The reason we do this is because future training classes may require additional software, or patches and so you would spin up the backup, make the updates/installations and rerun Sysprep to create a new cloneable base.

So, Michael includes 3 files for your use that need to be configured before we run Sysprep: Sysprep.inf, startsql.bat and mossconfig.bat. In my case, I commented out the reference to mossconfig.bat in the Sysprep.inf file. I also made the changes to the startsql.bat file so that it uses the built in Administrator account.

Because I hadn’t used Sysprep in so long, I had no clue how to use it. In the end, I was able to find it in the deploy.cab file located in c:\Windows\system32.

I extracted the contents of the deploy.cab file to c:\sysprep. I copied my modified Sysprep.inf and startsql.bat files to that location. From the command line, I navigated to c:\sysprep and ran the following command:

sysprep -mini -quiet -reseal shutdown

Once my machine is shut down. I am ready to roll. Michael talks about Differencing .VHD files, but research seemed to indicate the VMWare Fusion equivalent was to simply copy the VM. So basically, to make clone’s of these VMs I simply copy and paste to make a copy. I rename the VM, start it up and walk through the prompts to configure the new server. Once that boots, I created a local user account: moss_farmsvc and ran the SharePoint Configuration Wizard. When prompted, I have it the moss_farmsvc account, specified the db server, and configured CA to run on port 8888. Voila, I’ve got one VM ready to go. Rinse and repeat until you’ve got enough VMs for all your students!

Tagged with:  

Because I don’t write nearly enough on this topic, I figured why not add another post about the splendid PeopleEditor control for SharePoint. Actually, these posts happen to be among my most popular. So popular in fact, that they are constantly being stolen and posted on other blogs without so much as a courtesy ping back! I won’t point any of those users out as they don’t deserve any precious link love. However, if you come across a PeopleEditor post that looks suspiciously like mine, I’m the original author!

Ok, so now that my rant is over, let’s go over a new trick I recently discovered when working with the PeopleEditor. Whenever I’ve worked with this control in the past, I’ve usually only dealt with SelectionSets that are limited to Users. So there has never been a need to handle entries where groups are specified. In case you are not familiar, here is a listing of the possible options for the SelectionSet property, posted by Alex and Ozaki on the MSDN PeopleEditor.SelectionSet Page – Community Section:

  • User = single user
  • DL = AD distribution list
  • SecGroup = AD security group
  • SPGroup = SharePoint group

I came across this requirement recently. More specifically, I needed to be able to save both users and groups, but obviously the logic for saving those is a little different. So we need a mechanism that helps us determine what type of entity a user has provided and based on that, process the correct save method.

So, let’s say we have a PeopleEditor defined as follows:

<SharePoint:PeopleEditor ID="peManager" runat="server" MultiSelect="false" Width="200px" AllowEmpty="true" SelectionSet="User,SPGroup" />

Now we want to save whatever the user enters into a column of type Person or Group that can accept both users and groups.

foreach (PickerEntity entity in peManager.ResolvedEntities)
{
    switch ((string)entity.EntityData["PrincipalType"])
    {
        case "User":
            item["Assigned"] = SavePeopleEditorControl(peManager.CommaSeparatedAccounts);
            break;
        case "SharePointGroup":
            SPGroup group = web.Groups[entity.EntityData["AccountName"].ToString()];
            item["Assigned"] = group;
            break;
        // add additional case statements for the other SelectionSets
    }
}

There you have it. Enjoy!

This is a quick overview on how to add a column’s description to your custom edit and disp forms in SharePoint 2007.

List Name: Projects
Column Name: Status
Column Type: Lookup
Column Description: “Choose the status for this current project.”

To add a column to a form and display it’s contents, it’s pretty straight forward:

SPWeb web = SPContext.Current.Web;
SPList projects = web.Lists["Projects"];
SPListItem item = projects.GetItemById(Convert.ToInt32(Request.QueryString["ID"]));
 
// use this to grab the value, you can get either the ID or the Value in the column
SPFieldLookupValue status = new SPFieldLookupValue(Convert.ToString(item["Status"]));
lblStatus.Text = status.LookupValue;
 
// use this to grab the columns description, or any other property for that column
SPFieldLookup statusDesc = new SPFieldLookup(projects.Fields, "Status");
lblStatusDesc.Text = statusDesc.Description;

The above works great, but please note that the column name for the SPFieldLookup method requires the SharePoint Internal Field Name. So for example, if your column name was Project Status, and you created the column with the spaces, then it will be Project_x0020_Status.

Tagged with:  

Hiding columns in SharePoint is nothing new really, there have been several articles written on this topic. The approach for doing this, however, does inspire some debate in terms of what the best practice is. Let’s say we can agree that there are three ways to get this done:

  1. Using the built in content types to specify which columns you wish to be hidden.
  2. Using jQuery and a content editor web part to hide the columns as desired.
  3. Hide the columns programmatically using code.

I just wanted to make some comments on all three. The content type approach is a good one and relatively simple to implement. Once you’ve enabled content types for your list or library, click on the default content type, let’s say it’s Item, and choose the column you’d like to have hidden and simply set it’s column settings to Hidden. Here is an excellent reference for this technique: http://littletalk.wordpress.com/2009/03/30/hide-remove-title-column-from-sharepoint-list/.

Ok so the above works good, but what happens if you only want to introduce these hidden columns on your New and Edit forms but not your Disp form as an example. Or what if your scenario requires that they are hidden from all default New, Edit and Disp forms, but you have some custom SharePoint Designer disp forms that you’ve created that those fields need to be visible on. The content type approach will hide the column for all forms and so that won’t always work. We need something more granular.

So there is where the jQuery approach comes in handy. Some great resources for this:

As per the comments by Nathan Ahlstrom in the second link noted above, the final fix for me was to use:

<script type="text/javascript">
$(document).ready(function() {
    $('nobr:contains("Completion time")').closest('tr').hide();
    $('nobr:contains("Score")').closest('tr').hide();
});
</script>

Please note that I had to add quotes around the tr tag for this to work for me.

The final approach uses some custom code and the object model to hide columns for a given list. http://www.sharepointkings.com/2008/05/how-to-hide-column-of-sharepoint-list.html. Now I’m not entirely sure how this works with custom forms besides the New, Edit and Disp forms, and until I have time to try out, I won’t know the answer to that. It’s worth mentioning because it does give you some granularity in that you can specify hidden properties per form.

That’s all for now. Please send in your comments if you have them, would love to hear from you.

Tagged with:  

Alright, today’s blog post is going to be a bit infrastructure related. More specifically, I’d like to document an issue we encountered recently trying to do a restore from a production server to one of our development boxes. We do this often to keep our development data fresh and in sync with production, otherwise it becomes difficult to do any sort of real testing with numbers. Usually, this is a pretty standard procedure for us:

  1. Within Central Administration, under Application Management, we navigate to Content databases
  2. Making sure to select the Web Application we plan to restore to, we click on the WSS_Content link (or whatever happens to be the name of your db)
  3. Within the Manage Content Database Settings page, change the Database status drop down from Ready to Offline
  4. From here I stop the IIS Web Site, restart SQL Server and kick off my restore
  5. Once the restore is complete, I head back into my Manage Content Database Settings page and switch the Database status back to Ready

Now usually this works without issue. But recently, while attempting to do this on a machine we hadn’t worked with on a few weeks, we were encountering all sorts of errors trying to navigate to our site collection. Specifically, we were seeing the following error in the Operations logs:

The specified SPContentDatabase Name=WSS_Content Parent=SPDatabaseServiceInstance Name=Microsoft##SSEE has been upgraded to a newer version of SharePoint. Please upgrade this SharePoint application server before attempting to access this object.

Based on this error, my first thought was to check the dbo.Versions table to see if perhaps we had a conflict in versions as this error would suggest. Unfortunately, trying to read from the dbo.Versions table resulted in this pretty error:

Msg 33002, Level 16, State 1, Line 1
Access to table dbo.Versions is blocked because the signature is not valid.

So now we’re having fun at this point. I can’t read from the dbo.Versions table, so I don’t really know what the patch level of this particular server is. After checking production, I know that my patch level is 12.0.0.6504. In order to cross check what this means, you should have this link handy from the SharePointDevWiki.

Based on what I was reading there, my production server had the: MOSS 2007 or WSS 3.0 April 2009 Cumulative update. I know I probably could have done the research and checked file versions, but why should I have to do that when all I really needed to do was read from dbo.Versions? After a bit of troubleshooting, I realized it would make the most sense to simply spin up a new Web Application and fresh content db on my development box and read ITS dbo.Versions table. Turns out that original error was right on the money, my development box was one version behind: 12.0.0.6421.

Once I upgraded to the missing cumulative update and tried my restore process again, everything was back to normal! I hope that helps someone understand what the dbo.Versions table is all about, and what you can do to get past these errors.

Tagged with:  

If you’re like me and you do a fair bit of object model programming, you’ve likely seen this error before. More specifically, the error is as follows:

Unhandled Exception: Microsoft.SharePoint.SPException: Invalid data has been used to update the list item. The field you are trying to update may be read only.

There are probably more than one cause for this particular issue, but in my case, it always comes down to an error in the way I’ve assigned a field of type Lookup.

So to set up our scenario, let’s say we have a custom list called Types, and it contains 3 items:

  1. Employee
  2. Contractor
  3. Manager

Now let’s assume we’ve created a custom list called Users and it contains a column called User Type of type Lookup that points to the Types list using the Title column.

If we’re doing some updates to our list programmatically, we’ll need to be able to set the Type column. Your typical scenario is that you’re reading this from a form control and populating this field. In my case, I knew specifically what value I wanted to set. So let’s start with something like this:

1
2
3
SPListItem item = list.Items.Add();
item["Type"] = "Employee";
item.Update();

Unfortunately, the above bit of code will fail with the error outlined at the beginning of this post. The data we’re attempting to assign is not valid. What we need to do is create another object, an SPFieldLookupValue object to be more specific, and use one of the constructors to build the lookup that we’ll then assign to our field.

1
2
3
4
SPListItem item = list.Items.Add();
SPFieldLookupValue lookup = new SPFieldLookupValue(1, "Employee");
item["Type"] = lookup;
item.Update();

I’m not satisfied with the above, because it’s forcing me to hard code the id and the value. I am interested in some feedback from my readers — does anyone have a better approach to this problem? I’ll be awarding a free Black Ninja TSHIRT to the reader who can provide me the most elegant workaround.

Tagged with:  

I’m very excited and I consider myself quite lucky to be able to attend the Microsoft SharePoint 2009 Conference in October. I’ve already been to a couple great conferences this year, but I am quite confident this will be a good one. I’m looking forward to seeing all that 2010 has to offer.

If you haven’t already, you might want to check out the preview videos over at the Microsoft SharePoint site.

Tagged with:  

I’ve been working over the past few days with a custom workflow I’m building in Visual Studio 2005 that’s responsible for notifying users when specific criteria was met. Inside the workflow, I have a few sendEmail functions responsible for building the content within the email message:

1
2
sendEmail1.Body += "<font face=\"Arial\">This is the body of my email.<br/><br/>";
sendEmail1.Body += "Adding some information for users that will describe some detail about this item.</font>";

The above worked well until I started to see some truncating in my emails. I did some comparisons and found that the truncating would occur when the character count exceeded 2048 characters. I figured this couldn’t be the limit for a string in .NET so I did a bit of digging around and finally discovered that the root of the issue was because I didn’t have any line breaks in my string: “\r\n”. Once I inserted those at specific intervals, the truncating disappeared.

Awesome! Hope this helps someone else out there.

Tagged with:  

Triggering SharePoint workflows programmatically from within a custom built Visual Studio workflow is not difficult once you know where to look. Tony Testa wrote a great post on this very same thing that describes these concepts in more detail than I will — so go check that out if you haven’t already. I had to tweak his concept a little bit because I was doing this within an already running workflow that belonged to a content type not a list.

So in my scenario, I had a single workflow tied to a Content Type, and because I knew there was only one workflow, I used index at 0 to get the current workflow association. If your workflow is tied to a list, then you may not need to call ContentType, you might be able to use:

1
SPWorkflowAssociation workflowAssociation = workflowProperties.List.WorkflowAssocations[0];

If you have multiple workflows tied to a Content Type or a List, you will need to grab the Guid associationId for that workflow instead of using the index.

Finally, once I have my workflow association configured, I can start my workflow by passing it the parameters it needs:

1
2
3
4
5
if (workflowProperties.Site.WorkflowManager.GetItemActiveWorkflows(setLeaderItem).Count == 0)
{
    SPWorkflowAssociation workflowAssociation = workflowProperties.Item.ContentType.WorkflowAssociations[0];
    workflowProperties.Site.WorkflowManager.StartWorkflow(setLeaderItem, workflowAssociation, workflowAssociation.AssociationData, true);
}

The IF block is there to check if any active workflow are running on that list item. If there aren’t, it attempts to start the workflow. There you have it! That should trigger the workflow on the list item you specified.

Tagged with: