Navigation

Thursday, 16 October 2008

How to check whether a user has completed a survey or not [MSDN Forum Post]

Spotted this one on the MSDN forums.
 
Describes code you can run to check whether or not a specific user has responded to a survey (great for those corporate compliance ones!)
 
You can read the full post here.


Wednesday, 15 October 2008

Hiding Menu Items and Site Settings in SharePoint using Features

I have used the example of hiding the "Theme" button in Site Settings (as for corporate intranets with strict branding, this is quite a comment request. I would like to show you though how to not only hide specific menus, but how to hide whole sections of the Site Settings menu and other SharePoint menus too!

This all hinges on the HideCustomAction element which you can use in Features.

There are actually some pretty good (MSDN) references:

HideCustomAction - element

Default Custom Action Locations & IDs

How to: Hide a Menu Item in the ECB from SharePoint List Items

John Holliday - SharePoint Custom Action Identifiers

And a cross post, for completion:

How to: Add Actions to the User Interface

So What about this HideCustomAction thing then?

Ahhh yes, got carried away with MSDN references …

First off, you will need a feature (scoped at the Site Level)

Feature File (Feature.xml, don't forget to put your own GUID value in)



<?xml version="1.0" encoding="utf-8" ?>



<Feature Id="GUID" 
    Title="Hide UI Custom Actions"

    Description="This example shows how you can hide menus inside Windows SharePoint Services."

    Version="1.0.0.0"

    Scope="Site"

    xmlns="http://schemas.microsoft.com/sharepoint/">

  <ElementManifests>

    <ElementManifest Location="HideUICustomActions.xml" />

  </ElementManifests>

</Feature>



Elements File (HideUICustomActions.xml)







<?xml version="1.0" encoding="utf-8" ?>



<Elements xmlns="http://schemas.microsoft.com/sharepoint/">



<HideCustomAction



Id="HideThemeLink"



GroupId="Customization"



HideActionId="Theme"



Location="Microsoft.SharePoint.SiteSettings"



</HideCustomAction>



</Elements>





That will give you a feature which will remove the "Theme" links from the Site Settings menu. Now, let me quickly walk you through what the element file is composed of (hopefully the Feature file should be familiar enough).



HideCustomAction – This element defines a new custom action which we want to hide.



Id – This is optional (but recommended), used to identify this specific "hide" action from others.



GroupId – This is the GroupId value for the Custom Action that you want to hide.



HideActionId – This is the Id value in the Custom Action that you want to hide.



Location – This is the location value from the Custom Action that you want to hide.



If you want to find custom actions (and therefore access all of these values) then simply open up the Features folder (..\12\Templates\Features) and do a search for the phrase "CustomAction" in the files. You should find all of the CustomActions that are provided out of the box, and subsequently the ability to hide them if you want to! Exactly the same thing applies to Site Settings, so if you wanted to hide (for example) the "Themes" menu then you can find the Custom Action in the standard "SiteSettings" feature.



Enjoy, and let me know how you get on!



Monday, 13 October 2008

[Code Update] SDK and Microsoft Press Both Wrong?? Custom Fields in the schema.xml

UPDATE
For those who missed my original article (SDK and Microsoft Press Both Wrong?? Custom Fields in the schema.xml) , you can find it here!
 
I finally got round to posting the code that goes with this article ..
 
Please find below the details on code for an SPFeatureReceiver class, that will automatically push down changes from your Content Type Features
(you will need to attach the code to your feature using the ReceiverAssembly and ReceiverClass attributes in your feature.
 
The code needs to be created as a Class in a Class Library, with Microsoft.SharePoint.dll added as a reference, and then added as a Using statement in the class itself.
 
Code Sample
 
EDIT - Thanks to a quick spot from Nick's SharePoint blog, I have updated the code to match! Thanks Nick!
 
class
ContentTypeInstaller : SPFeatureReceiver
{
// CODE DESCRIPTION
// ----------------
/*
* This is a feature handler which should be paired
* with a content type feature.
*
* We have identified a problem with Content Types,
* where the content type site columns are not pushed
* down to custom list definitions, until they are
* "modified" first.
*
* This feature will interrogate all of the associated
* xml files that are attached to the feature.
* Once found, it will "modify" each of the custom fields
* that are referenced.
*
* This should allow list definitions to use the content
* type columns, without declaring them in the schema.xml
*/
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
using(SPSite site = (SPSite)properties.Feature.Parent)
{
SPWeb web = site.OpenWeb(
"");
// loop through each of the elements in the feature
foreach (SPElementDefinition element in properties.Definition.GetElementDefinitions(CultureInfo.CurrentCulture))
{
#region
Loop through feature elements
 
// retrieve the xml content for the feature element
XmlNode content = element.XmlDefinition;
// only continue if the element is a content type definition
if (content.Name == "ContentType")
{
// grab a new Content Type object
string strCTypeID = content.Attributes["ID"].Value;
SPContentType cType = web.ContentTypes[
new SPContentTypeId(strCTypeID)];
#region
Get FieldRef Order from Content type
// grab the original order, we will need this later
string[] fieldOrder = new string[cType.FieldLinks.Count];
int x = 0;
foreach (SPFieldLink fieldlink in cType.FieldLinks)
{
fieldOrder[x] = fieldlink.Name;
x++;
}
#endregion
#region
Add new columns to the content type
// loop through each sub-node in the Content Type file
foreach (XmlNode node in content.ChildNodes)
{
#region
loop through sub nodes
// only continue for
// the FieldRefs collection
if (node.Name == "FieldRefs")
{
foreach (XmlNode fieldRef in node.ChildNodes)
{
#region
Loop through FieldRefs
// only apply for FieldRef tags
if (fieldRef.Name == "FieldRef")
{
// get the FieldID and use it to
// retrieve the SPField object
string fieldID = fieldRef.Attributes["ID"].Value;
//SPFieldLink fieldLink = cType.FieldLinks[new Guid(fieldID)];
SPField field = cType.Fields[
new Guid(fieldID)];
// first we need to remove the fieldref
cType.FieldLinks.Delete(
new Guid(fieldID));
// and save, pushing this change down
cType.Update(
true);
// NOTE - this will NOT delete any content
// in existing lists!
// now add the field back in again
cType.FieldLinks.Add(
new SPFieldLink(field));
// and call an update, pushing down all changes
cType.Update(
true);
// NOTE - this is what adds the column to those
// lists who don't already have it.
}
#endregion
}
 
}
#endregion
}
#endregion
#region
Apply changes
// reset the field order
// it is possible that adding and
// removing fields would have
// affected this
cType.FieldLinks.Reorder(fieldOrder);
// force update of the content type,
// pushing down to children
cType.Update(
true);
#endregion
}
#endregion
}
}
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
}
}
Enjoy, and happy coding!


Friday, 10 October 2008

Quick Tip - Site Collection Relative URLs in MOSS 2007

This is a nice quick one...

 

If you try to use normal relative URL paths, then you may run into a spot of bother when trying to call a URL path that is relative to the Site Collection, especially when you have multiple Site Collections in your web application:

 

http://RootSite/sites/ThisSite/

 

How do you make an URL that is relative to the "ThisSite" site collection?

 

Well, you could hard-code the URL prefix, but that doesn't really sit very well for most implementations, especially when using multiple-access mappings, you might have multiple URLs for the same site collection!

 

Alternatively, you can use the SPUrl function:

 

Code Sample:

This example code will take you to the Site Settings page for the current Site Collection.

 

<a runat="server" href="<% SPUrl:~SiteCollection\_layouts\settings.aspx %>">Go to Top Level Site Settings</a>

 

Note - You must use the runat="server" attribute, otherwise it will send the literal text of <% SPUrl ...%> instead of the desired relative URL!

Also, you will need to add a reference to the Microsoft Publishing Assembly in the header tags; for that, see this blog

 

Publishing Header Tags

Thanks go to Hannah Scott (and her subsequent source) for this blog post.

 

<%@ Register Tagprefix="PublishingWebControls" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="PublishingNavigation" Namespace="Microsoft.SharePoint.Publishing.Navigation" Assembly="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="PublishingVariations" TagName="VariationsLabelMenu" src="~/_controltemplates/VariationsLabelMenu.ascx" %>
<%@ Register Tagprefix="PublishingConsole" TagName="Console" src="~/_controltemplates/PublishingConsole.ascx" %>
<%@ Register TagPrefix="PublishingSiteAction" TagName="SiteActionMenu" src="~/_controltemplates/PublishingActionMenu.ascx" %>



Thursday, 9 October 2008

SDK and Microsoft Press Both Wrong?? Custom Fields in the schema.xml

Ok, so I think I've found something here. It relates specifically to Site Columns and Content Types, and how they can be applied to Custom List Definitions.

According to the latest release of the WSS 3.0 SDK (August 2008?) and one of the "bible" textbooks for WSS 3.0 development (Inside WSS 3.0 - Ted Pattison & Daniel Larson - Microsoft Press) there are a couple of questionable "best practice" methods that I wanted some insight on.

Personally, I think I've found quite a big oversight, which could be causing problems all over the shop.

The Big Issue (not the magazine!)
The question is around building a custom List Definition, in which you want to implement a custom Content Type (with custom Site Columns).

Now, both the SDK and the Inside WSS 3.0 book (along with many many other references I've seen, including people) state that if you have custom fields in your content type, then you have to add them to the Schema XML.

This has always struck me as very odd. I mean, you have to add all of that information already to your Site Columns (fields) feature .. right? So aren't you just copy-pasting the same info?

Also, if you try to do this through the user interface, SharePoint will automatically add all of the columns from the content type when you add it to the list ... so why can't it do that through CAML too ??

Well, I wasn't happy about this, and decided to try things a different way.

The Big Experiment (not the Discovery Channel)
I wanted to try and make a set of features that proved these statements wrong:
  • Site Column Feature (which declares a single, custom text field)
  • Content Type Feature (which declares a single Content Type, inheriting from Document, and includes my site column as a Field Reference)
  • Document Library Feature (declares a new custom document library definition. The only change is the Content Type and switching on "management of content types". The custom field is NOT added to the Fields section in the schema.xml)

TAKE 1 - As expected
I finished my CAML, installed my features and activated them in a completely new vanilla Site Collection.
My site column and content type both appeared on activation (as expected) and my new library definition popped up in the create menu (also as expected).

I then created my Document Library ... and ... <anti><climax>my columns did not appear in the library</climax></anti>.

This was, for the cynics among you, entirely expected. Microsoft themselves (through the holy scriptures of the SDK) have stated that this would happen.

But then things got interesting ...

TAKE 2 - The interesting result
Ok I thought ... what is going on behind the scenes? Somehow I need my Content Type to take preference over the schema...

I had a bit of a think (and probably scratched my chin a few times).. eventually I hit upon an idea (don't ask me how .. one of those late night coffee moments!)

I reset my environment (brand new, clean site collection) and re-installed and re-activated my features.

BUT .. this time, BEFORE I created any libraries I went to the Content Type Gallery and looked at my Content Type.
I clicked on my custom field (in the content type itself) and changed one of the values (I actually changed it into a required field) and hit the OK Button (note - the "push changes down to child content types" WAS selected).

I then backed out of those screens, and created my Document Library... and ... <surprise>my custom columns DID APPEAR!!</surprise>!

This was quite a shock .. I mean .. the Fields section in my custom document library schema is empty (apart from the fields that come out of the box).

I checked the library settings, tried uploading a file, and even checked the Document Information Panel in Word 2007. My custom field was there, with all the correct metadata.

Conclusion - (the important bit .. for those who bothered to scroll down)

My conclusion:

You do not have to declare custom fields in the schema.xml, as long as you manually update your content type before creating any libraries.

But that's not practical ... I have <insert huge number>'s of content types!
Well .. I hear ya.

I have just finished off a custom Feature Receiver which can be attached to any Content Type Feature as a Receiver Assembly / Receiver Class.

The code will (when the feature is activated) locate all of the Content Type elements in the feature, and do an initial "Update - push down changes" when the content types are first activated. (UPDATE code as been published to my Blog. See this article for details).

This is effectively the "magic bullet" I was looking for, and with this:

  • Custom Fields do not have to be declared in the schema.xml
  • Custom Fields can still be referenced in views, even though they are not referenced as Fields.
  • All column and meta data management can be developed in the Content Types and Site Columns

Comments / Discussions / <not>Flames</not> ...

all are welcome :)



Tuesday, 30 September 2008

Evaluation Virtual PC images set to expire ...

One to punt over from the Microsoft SharePoint Team Blog.

Basically, if you are using this then it's going to expire ... TODAY! (bummer eh?)

But fear not ... another one has been published ... so download that and be merry instead :)


Monday, 15 September 2008

what happens when you tempt fate ...

Ok .. so I'm back from holiday.

The weather was ... well what can I say.

I experienced very nice weather. 35 *C (90+ *F) pretty much every day, just not where I wanted it to be.

You see I should never have opened my big mouth and mentioned hurricanes. Why? Because I had a mandatory evacuation of the Florida Keys to thank for ruining my 2nd week on holiday :(

Oh well, 2 weeks in Orlando is a little bit too much for me (but at least the weather was good .. where I was staying at least!)


Thursday, 28 August 2008

Holiday in Florida ... woot!

Yep. I'm packing my bags and off to Florida for 2 weeks.

Got a friend who is getting married in DisneyWorld (make of that what you will!!) Personally I'm looking forward to the 2nd week .. relaxation in the Florida Keys (assuming I don't get a hurricane to spoil my day).

ahhh ... beaches ... swimming pools .. and drinks with little umbrellas in them :)

Wednesday, 27 August 2008

How to handle document retention and expiry in MOSS 2007 - Disposition Workflow and Expiration Policy

This is rapidly becoming a hot topic in Records Management with SharePoint, as organisations put increasing amounts of information into their SharePoint environments serious thought needs to be put into how that information should expire and (more importantly) what happens when it expires.

SharePoint has several solutions to these issues which, if configured correctly, can help pave the way to successful document expiration management.

The Expiration Management Policy
Information Management Policies are a new framework introduced in MOSS 2007, and allow specific "policies" to be applied system wide using Content Types, or to individual document libraries and lists.

One of the most popular policies in information management is the Audit Policy (which is vastly superior to version history, as it can track who has viewed, downloaded or deleted a document, as well as who has made changes), but another equally important policy is the Expiration Policy.

The Expiration Policy effectively allows you to specify the retention period for content. This is generally calculated from the created or modified date, although you can specify any formula (i.e. calculated fields) based on any date/time column value.

When a document "expires" you can then select from a number of actions: delete the item, perform another custom action (extensible through development) or start a workflow, and it is the latter that we will focus on here.

As mentioned, you can develop additional "custom actions" for the expiry Information Policy, which you can deploy as a Feature (there is a good article here and also a forum post discussing the options for this).

The Disposition Approval Workflow
The disposition approval workflow is designed specifically for document expiry, and has been designed with a very simple user interface.

When started the workflow creates a task, which is linked to the document. The task presents the user with the options of deleting the item, or keeping it, and the ability to add some comments. Completing the task will perform the appropriate actions on the server.

This basically gives you "out of the box" capability to have items which expire and then get (optionally) deleted upon expiry.

..

Now, I put my developer hat on and delved behind the scenes.

The Disposition Approval Workflow consists of a custom Task Content Type which is used for the task, and an InfoPath form which is used for the task edit form (via Form Services).

This gives us 2 options for replacing / extending the Workflow.

1) Create our own custom content type for the tasks. This allows us to attach event handlers and perform any custom operations when the tasks are created / edited / completed.

2) Create a replacement InfoPath form and use that for the task completion, perhaps with additional options (such as "archive" ?).

...

Either way, the out of the box options are quite extensive, and the Workflow / Content Type structure gives enough extensibility to provide almost any functionality for document retention.



Friday, 22 August 2008

People search errors when My Site is running on SSL

This was a great gotcha that I picked up duing a client project handover.
They reported issues with search, and I found a load of errors in the crawl logs regarding accessing the mysite.

You will find that this issue occurs when you configure your My Sites to run under SSL (HTTPS), and the problem is that the People Search isn't really interested in the My Site content, it is actually interested in the Profile Database (which is another thing most people forget to switch on .. make sure your Profile Database has an import schedule setup!)

Basically, you need to set the Content Source URL to the following:
sps3s://<mysiteurl>/

So if your mysite is running under https://mysite.company.com/ then you need to set your Content Source URL to:

sps3s://mysite.company.com/
Personally I found this pretty confusing, so I actually  split out the Profile Database content into a separate Content Source, and configured the People Scope to only retrieve items from that specific Content Source.

Happy searching!

Update
Just a quick update, because this is another "People Search" problem. Even if you don't use My Sites, you should still switch on and use the MySite web application and settings (You can stop people from creating "My Sites" by disabling Self Service Site Creation at the Web Application level).

The reason is that the My Site web application is used for People Search URLs and will automatically dsiplay information from the Profile Database (which is basically the user's "public profile") even if they don't have a My Site created!