Wednesday, June 3, 2009

Deploy Web Part as Feature with CAS

Introduction


I had started writing this blog over a year and half ago but I had hirer priority things come up. I still find that a lot SharePoint developers have challenges understanding how to deploy web parts. Specifically CAS, GAC vs bin, rely on CodePlex tools, etc. are a common issue.

As a SharePoint consultant I have seen many struggle with web parts. The issue is not usually with the development of a web parts. There are lots of blogs and books written that show how to develop web parts but the biggest issue I have seen is correctly deploying of web parts. More specifically:

  • Creating web part deployment solutions
  • Deploying web parts as a feature
  • Deploying the web parts in a secure fashion

With Visual Studio 2005, there was a plug-in that developers used to quickly deploy web parts out to a SharePoint environment however there was no control over the deployment package or the deployment process. This plug-in was great for development environments because it allowed developers to quickly deploy and iteratively debug web parts however developers would continue to use this deployment for production deployments. This caused many issues with the control, configuration and change management issues down the road.


With Visual Studio 2008, there is better tooling has been incorporated into Visual Studio by Microsoft for the creation of SharePoint web part projects.


Other tools have been created to help with the authoring WSP deployment files such as WSP Builder. This tool works pretty well but knowing how a WSP solution file is created is equally important. What I have seen is that developers become so dependent on tools such as these, they do not have an understanding of how web parts actually work.

Still the biggest issue we see with deployment is developers run into challenges with deployments of their web parts and they quickly do the following:


  • They raise the trust level on their SharePoint environments to either Medium or Full.
  • They deploy the web part DLL directly to the GAC.

Both are unacceptable solutions especially when this is a production environment (Intranet or not). What makes this worse is many forums, blogs, etc. usually state to do as such and do not outline the consequences. As well, I have not seen any truly good end to end discussions of this topic.


In this blog I will take a deep dive into how a web part should be properly deployed. The web part that will be developed will be inconsequential ("Hello World" example) because the focus will be on deployment. I will specifically focus on:


  • Deploying a web part as a Feature.
  • Deploying to web bin directory.
  • Code Access Security (CAS).

A lot of this information will become moot because we are expecting the next release of Visual Studio to fix a lot of these gaps and provide all the tooling to build SharePoint deployment packages end-to-end.


The first part of this blog is going to discuss how to create a web part feature and properly deploy it. The interesting stuff about properly setting up CAS will later on. However I believe it is important to understand the entire picture before we jump into CAS with web parts.


Creating a Web Part Project


In this first project I am going to create a basic web part project using no accelerator tools.


  • Create a C# code library project which I named WSSDistillery.Deploy.WebPart.
  • Add a references to Microsoft.SharePoint and System.Web.
  • In the AssemblyInfo.cs you will need to add [assembly: System.Security.AllowPartiallyTrustedCallers]. This will allow SharePoint to call the web part contained within dll.
  • Go into the properties of the project and sign the assembly.
  • Renamed Class1.cs to something else. I renamed it to SimplePart.cs.
  • Add an XML file called manifest.xml.
  • Add a file called WSP.ddf.
  • Add the following three folders. First "TEMPLATE", then beneath it "FEATURES", then beneath it "WSSDistillery.Deploy.WebPart".
  • Under the "WSSDistillery.Deploy.WebPart" folder add the following files elements.xml, Feature.xml and WSSDistillery.Deploy.WebPart.SimplePart.webpart.

It is pretty simple to throw together even without all of these tools out there. The final solution should look like:






I will go into further detail about each file and what goes into them. Some initial notes are:


  • It is common practice to create a hierarchy of folders in the Visual Studio project based on the folder hierarchy of files in the 12 Hive (\\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES).
  • I named the folder "WSSDistillery.Deploy.WebPart" because this is the folder that will be deployed into SharePoint. I put the namespace into the name of the folder to lower the chances of a name conflict for other SharePoint Features that may be created over time.
  • Even though the assembly has been signed, it will not be deployed to the GAC.

Creating the Web Part


I mentioned before, this will not be a lesson on how to develop a web part. As such, this web part is going to be a simple implementation; Hello World for now. I will add some code later which will require it to have a CAS policy.

public class SimplePart : System.Web.UI.WebControls.WebParts.WebPart
{
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);

writer.WriteLine("Hello World");
}

}

Get DLL Strong Name


Might as well go ahead and get this now, because this will be needed in several places. Open the Visual Studio command prompt and run the following command:


  • Sn –Tp [path]\[dll name]
  • sn -Tp "C:\WSSDistillery\Deploy\WSSDistillery.Deploy\WSSDistillery.Deploy.WebPart\bin\Debug\WSSDistillery.Deploy.WebPart.dll"

Two values will be generated. The first is a Public Key which is really long, go ahead and get that. The second is a Public Key Token which will be used in several places. These will be used in some of the files that will be created next.


Webpart File


I know "WSSDistillery.Deploy.WebPart.SimplePart.webpart" is a long filename. Again, it is good practice to have the namespace in the name of the file to ensure there are no name conflicts down the line during your web part deployments. This file will be deployed into the Web Part Gallery within a site collection. This file has a reference to the DLL that has the web part code. As well, the Data XML elements contain the text that a user will see when adding a to a web part page within SharePoint. A good description here of what the web part is required for the users of SharePoint.

<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
<metaData>
<type name="WSSDistillery.Deploy.WebPart.SimplePart, WSSDistillery.Deploy.WebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=aa9a1a08fbf22452" />
<importErrorMessage>Cannot import WSSDistillery.Deploy.WebPart.SimplePart</importErrorMessage>
</metaData>
<data>
<properties>
<property name="Title" type="string">Simple Web Part</property>
<property name="Description" type="string">This is a very simple web part for demonstrating deployments.</property>
</properties>
</data>
</webPart>
</webParts>

elements.xml File


This file will be responsible for deploying the WSSDistillery.Deploy.WebPart.SimplePart.webpart file to the Web Part gallery in the site collection.

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="WebParts" List="113" Url="_catalogs/wp">
<File Url="WSSDistillery.Deploy.WebPart.SimplePart.webpart" Type="GhostableInLibrary" />
</Module>
</Elements>

Feature.xml File


This is a standard SharePoint Feature file. There are references to both elements.xml and WSSDistillery.Deploy.WebPart.SimplePart.webpart files ion the ElementManifests.

<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="8CD4DE2E-353E-40e1-A2AB-8004F6E8AA5F"
Title="Simple Web Part"
Description="This is a very simple web part for demonstrating deployments."
Version="1.0.0.0"
Scope="Site"
Hidden="FALSE"
DefaultResourceFile="core"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="elements.xml" />
<ElementFile Location="WSSDistillery.Deploy.WebPart.SimplePart.webpart"/>
</ElementManifests>
</Feature>

manifest.xml File


This file contains the instructions that will be used by SharePoint to deploy the solution across the SharePoint farm. First, the FeatureManifest element has the location for where the Feature.xml file is. You can deploy many SharePoint Features using a single deployment package; in this example we are only deploying one.


Second the Assemblies element controls where the dll with the web part will be deployed. Notice the DeploymentTarget attribute is set to WebApplication. When this web part is deployed, SharePoint will create a folder called "WSSDistillery.Deploy.WebPart" on every web front end (WFE) web server. This folder will be created within the bin directory of IIS for the SharePoint web application. Within that new folder will be the WSSDistillery.Deploy.WebPart.dll file. The result it the dll is now deployed to the "bin" directory instead of the GAC. For many reason this is the most secure methodology for deploying a web part. Most notably dll will only be accessible to the SharePoint web site and not other applications can use it.

Finally the SafeControls element contains instructions that will modify the web.config file of the SharePoint web application. Specifically it will add the web part as a safe control so that it can be used within SharePoint.

<Solution xmlns="http://schemas.microsoft.com/sharepoint/" SolutionId="052C7D59-7DCF-4782-93B4-51C3F6DECF3B">
<FeatureManifests>
<FeatureManifest Location="WSSDistillery.Deploy.WebPart\Feature.xml"/>
</FeatureManifests>
<Assemblies>
<Assembly Location="WSSDistillery.Deploy.WebPart\WSSDistillery.Deploy.WebPart.dll" DeploymentTarget="WebApplication" >
<SafeControls>
<SafeControl Assembly="WSSDistillery.Deploy.WebPart.SimplePart, WSSDistillery.Deploy.WebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=aa9a1a08fbf22452"
Namespace="WSSDistillery.Deploy.WebPart"
Safe="True"
TypeName="*"/>
</SafeControls>
</Assembly>
</Assemblies>
</Solution>

WSP.ddf File


Last file to be created is the ddf file. It is really not that hard to get the hang of:

  • Create a name for the *.wsp file that will be created.
  • Create a destination for the where the file will be created.
  • Get the manifest.xml file.
  • Create a folder within the wsp called WSSDistillery.Deploy.WebPart. Special note, this folder must correspond to the paths to the assembly and feature files that were created within the manifest.xml.
  • Reference the dll, elements.xml, Feature.xml and WSSDistillery.Deploy.WebPart.SimplePart.webpart files.
.OPTION Explicit
.Set CabinetNameTemplate="WSSDistillery.Deploy.WebPart.wsp"
.Set DiskDirectory1="C:\WSSDistillery\Deploy\WSSDistillery.Deploy"

manifest.xml

.Set DestinationDir="WSSDistillery.Deploy.WebPart"

%outputDir%WSSDistillery.Deploy.WebPart.dll
TEMPLATE\FEATURES\WSSDistillery.Deploy.WebPart\elements.xml
TEMPLATE\FEATURES\WSSDistillery.Deploy.WebPart\Feature.xml
TEMPLATE\FEATURES\WSSDistillery.Deploy.WebPart\WSSDistillery.Deploy.WebPart.SimplePart.webpart

.Delete outputDir
To create the WSP file, go the project properties and add the following commands to the Post Build Events.

cd $(ProjectDir)
MakeCAB /D outputDir=$(OutDir) /f "WSP.ddf"

Now build the project and the WSSDistillery.Deploy.WebPart.wsp should be created in the C:\WSSDistillery\Deploy\WSSDistillery.Deploy folder.


Deploying the Web Part Files


The great thing about creating a WSP file is that you can now use the stsadm command line to deploy your SharePoint Feature. In this case, my Feature is a web part. I personally believe that if you cannot deploy a complete solution using a SharePoint Feature, you should reconsider your deployment approach. Deployment into your production environments should be a controlled, repeatable and automated.

The following commands need to be run to deploy the solution to SharePoint.


stsadm.exe -o addsolution -filename C:\WSSDistillery\Deploy\WSSDistillery.Deploy\WSSDistillery.Deploy.WebPart.wsp


stsadm.exe -o deploysolution -name WSSDistillery.Deploy.WebPart.wsp -url http://mossserver:28921 -immediate -force

stsadm.exe -o execadmsvcjobs

Note that the URL to the web application is specified in the deploysolution command. The is necessary because the Feature.xml is a scope of Site.


As a result running these commands the following changes occurred:

  • In C:\Inetpub\wwwroot\wss\VirtualDirectories\XXXX\bin\ a folder called WSSDistillery.Deploy.WebPart will be created and the WSSDistillery.Deploy.WebPart.dll has been deployed there.
  • In C:\Inetpub\wwwroot\wss\VirtualDirectories\XXXX\ web.config the WSSDistillery.Deploy.WebPart entry has been added to the SafeControls tag.
  • In C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES and new folder called WSSDistillery.Deploy.WebPart was created and elements.xml, Feature.xml and WSSDistillery.Deploy.WebPart.SimplePart.webpart will be in that folder.

I point this out because it is important to know all the changes that have occurred to your SharePoint environment based on the WSP solution deployment. As well, you will need to verify your deployment to ensure that everything was put in the right place – especially if this is a production deployment.

Activating the Web Part Feature


The next step is we need to activate the Feature with the following commands:

stsadm.exe -o installfeature -filename WSSDistillery.Deploy.WebPart\Feature.xml -force

stsadm.exe -o activatefeature -filename WSSDistillery.Deploy.WebPart\Feature.xml -url http://mossserver:28921 –force


The results are:

  • If you go to http://mossserver:28921/_layouts/ManageFeatures.aspx?Scope=Site in the Site Collection Features you will see the "Simple Web Part" Feature activated.
  • If you go to http://mossserver:28921/_catalogs/wp/Forms/AllItems.aspx you will see the WSSDistillery.Deploy.WebPart.SimplePart.webpart in the Web Part Gallery.
  • You will find the web part in the Miscellaneous group when adding the Simple Web Part to a web part page.

That is it! I know it seems like a lot – but it is really important that you know how all this is working under the hood regardless of what accelerator tool is being used to deploy your web part.


SharePoint and CAS Background


Before diving into how to configure a web part to work with CAS, let us get an understanding of how it works within SharePoint. I will try to make this as simple as can be. By default, whenever a new web application is created within SharePoint, if you go to the web.config you will find the following tag:

<trust level=" WSS_Minimal" originUrl="" />

This basically means that your SharePoint site is running under minimal trust. If you ever come to a SharePoint site that is not running minimal or WSS_Custom (I will explain shortly) there should be major concerns. If for any reason this has been switched to FULL – this would be cause for alarm and not acceptable for any reason. Most SharePoint solutions can be deployed under minimal trust.

In the web.config file, there are elements called trustLevel. By default there will be WSS_Medium and WSS_Minimal. The policyFile attribute shows where this policy file resides within the 12 hive. Let us look at the WSS_Minimal in \\12\config\wss_minimaltrust.config. Please note that these files should not be change for any reason!!!


In the WSS_Minimal you will see that the following libraries are trusted:


  • System.Security.Policy.AllMembershipCondition
  • System.Web.AspNetHostingPermission
  • System.Security.Policy.FirstMatchCodeGroup
  • System.Security.NamedPermissionSet
  • System.Security.Permissions.SecurityPermission
  • System.Security.Policy.StrongNameMembershipCondition
  • System.Security.Policy.UnionCodeGroup
  • System.Security.Policy.UrlMembershipCondition
  • Microsoft.SharePoint.Security.WebPartPermission
  • System.Security.Policy.ZoneMembershipCondition

The first thing you will notice is that you do not have permissions to the SharePoint itself, basically you cannot open something like a SPWeb object with these permission levels. All you basically have permissions to do is some basic .NET code and you cannot do anything integrated with SharePoint, databases, the file system, etc.


Now let us look at the WSS_Medium file. In addition to the ones above, the following have been included:

  • System.Net.DnsPermission
  • System.Security.Permissions.EnvironmentPermission
  • System.Security.Permissions.FileIOPermission
  • System.Security.Permissions.IsolatedStorageFilePermission
  • System.Drawing.Printing.PrintingPermission
  • Microsoft.SharePoint.Security.SharePointPermission
  • System.Net.Mail.SmtpPermission
  • System.Data.SqlClient.SqlClientPermission
  • System.Security.Permissions.UIPermission
  • System.Net.WebPermission

As you can see running under medium level trust now you have the ability to run SharePoint calls but it opened up several other things like FileIO, Smtp and SQL. Now many might think that is harmless to change the permission level to Medium but think about that for a moment. What this basically means is if you change the web.config file from minimal to medium just so you can run a SharePoint API command, you now open up all these things throughout the entire SharePoint web application. So now malicious code could be uploaded into your SharePoint site, and use the permissions levels to do harm or access resources that they should not have access to!!!


This is unacceptable when there is the ability within SharePoint to be granular. I have seen too many people say, change the permission level to Medium. The real solution is to use CAS policies within your maninfest.xml give your web part the proper permissions to execute code. In the manifest.xml file you have the ability to create CAS policies that specific to the web part dll only. If the web part only needs to have access to the SharePoint API, we can create a policy stating that WSSDistillery.Deploy.WebPart.dll can only run Microsoft.SharePoint.Security.SharePointPermission and nothing else. This is highly secure and ensures that no other resources are inadvertently opened.

Modify Simple Web Part


Now let us modify the web part to use an object for which it does not have permission to when running under minimal trust. In this case, lets change the Simple Web Part to reference SPContext.Current.Web. In the code below, I simply want to modify the web part to show a list of sub sites below the current site.

public class SimplePart : System.Web.UI.WebControls.WebParts.WebPart
{
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);

try
{
StringBuilder webUrls = new StringBuilder();

using (SPWeb currentWeb = SPContext.Current.Web)
{
foreach (SPWeb web in currentWeb.Webs)
{
webUrls.AppendFormat("<a href=\"{0}\">{1}</a><br>", web.Url, web.Title);
web.Dispose();
}
}

writer.WriteLine(webUrls.ToString());
}
catch (Exception ex)
{
writer.WriteLine(ex.Message);
}
}

}

If the code were to be redeployed as is, you will receive the following error.


The "SimplePart" Web Part appears to be causing a problem. Request for the permission of type 'Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' failed.


Many new SharePoint developers will see this as a harmless code and get frustrated that this has been locked down. Well this is because SharePoint wants to make sure that things are secure and controlled which is not a big deal; you must always account for security in your implementations.


In the next few steps I will give you a more detailed understanding of how CAS works and how your SharePoint environment will be changed as a result of adding the CAS instructions. In my opinion is very important to know how this works.


PermCalc Tool


There is a tool called PermCalc.exe which we will use to help us generate the CAS permissions. I had suggestion to try to use as a way to quickly figure out permissions for a web part. After playing with it for several hours and putting a lot of thought into it, I could not come up with a simple best practice on how to use this for the "everyday" developer.


I was able to get it to generate permission sets which will be discussed shortly, however it did not make things any easier. This is because if you do run this against your web part it will not give you any suggestions on how to add SharePointPermissions like what we had to do above. The only way was to decorate the class, constructor or methods with the following:


[SharePointPermission(SecurityAction.Demand, Unrestricted=true)]

Then if you run the PermCalc tool it would generate the correct IPermission.


So you pretty much have to anticipate the errors; which is not a bad thing either. Really, I think the approach I will explain below is easier and you do not need to add in these decorations to give your web parts these permissions to get them to work in a secure fashion.


Adding CAS

To resolve the issue we need to add CAS instructions to the deployment.

First let's go to the wss_minimaltrust.config file that is \\12\config\. You will see a PermissionSet named SPRestricted. Copy this into your manifest XML file.

<PermissionSet   class="NamedPermissionSet" version="1" Name="SPRestricted">
<IPermission class="AspNetHostingPermission" version="1" Level="Minimal" />
<IPermission class="SecurityPermission" version="1" Flags="Execution" />
<IPermission class="WebPartPermission" version="1" Connections="True" />
</PermissionSet>

Now all I need to do is add an IPermission for SharePoint. Next I opened the wss_mediumtrust.config in \\12\config\. In there under the SPRestricted you will find this

<IPermission class="SharePointPermission" version="1" ObjectModel="True" />

Now the class SharePointPermission is not part of the minimal trust file, so what you do is change this to be:

<IPermission class="Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" version="1" ObjectModel="True" />

All you need to do is scroll up to the SecurityClasses node within the wss_mediumtrust.config.


Done. You can do this for other things like SQL, IO, DNS, etc. permission.


This is how the final modified manifest.xml file will look like.

<Solution xmlns="http://schemas.microsoft.com/sharepoint/" SolutionId="052C7D59-7DCF-4782-93B4-51C3F6DECF3B">
<FeatureManifests>
<FeatureManifest Location="WSSDistillery.Deploy.WebPart\Feature.xml"/>
</FeatureManifests>
<Assemblies>
<Assembly Location="WSSDistillery.Deploy.WebPart\WSSDistillery.Deploy.WebPart.dll" DeploymentTarget="WebApplication" >
<SafeControls>
<SafeControl Assembly="WSSDistillery.Deploy.WebPart.SimplePart, WSSDistillery.Deploy.WebPart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=aa9a1a08fbf22452"
Namespace="WSSDistillery.Deploy.WebPart"
Safe="True"
TypeName="*"/>
</SafeControls>
</Assembly>
</Assemblies>
<CodeAccessSecurity>
<PolicyItem>
<Assemblies>
<Assembly PublicKeyBlob="00240000048000009400000006020000002400005253413100040000010001
00ad51a9cdfbe7db0a0f6d16a257d874a19993707b1bb0e873bda1c18c5b1592e24070f57b637
e0be2b00790fd67bc3e0c61205af5c0e5753780f257acb2c0b4b4830e23ace37be05c7ab52478
2731f85786d7648e6e14a99a83dc474081d5cf5e1fc1b20da22fc3b5a94b44ad3903aa8f081cc
0508e71b5606927824114f2ecd1"
/>
</Assemblies>
<PermissionSet class="NamedPermissionSet" Name="WSSDistillery.Deploy.WebPart" version="1" Description="WSSDistillery.Deploy.WebPart">
<IPermission class="AspNetHostingPermission" version="1" Level="Minimal" />
<IPermission class="SecurityPermission" version="1" Flags="Execution" />
<IPermission class="WebPartPermission" version="1" Connections="True" />
<IPermission version="1" class="Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" ObjectModel="True" />
</PermissionSet>
</PolicyItem>
</CodeAccessSecurity>
</Solution>

Notice the following changes:

  • A new CodeAccessSercurity node has been added.
  • Within it you see the new permission sets.
  • Noticed that I renamed the PermissionSet to match the name of my webpart dll.
  • As well, you see an assemblies node. Notice that I use the PublicKeyBlob which is a value I got from the sn command line earlier when generating a strong file name. Since I am deploying to the bin, this seems to be the only way I can the permissions to pick up. Every time I try to reference the DLL name it does not work.

Deployment of Web Part with CAS


We can deploy the web part in the same fashion as earlier however, add -allowCasPolicies switch to the stsadm.exe -o deploysolution command line, otherwise you will get an error during the deployment.


Affects of the CAS Deployment


Here is how your SharePoint environment changed as a result of this. Go to the web.config file for the SharePoint site. First you will see that the truest level has changed to WSS_Custom. You will that a new treustLevel node has as well been created called WSS_Custom. You will see that SharePoint took the WSS_Minimal file and then created a new file from it. When you go to this new file in the \\12\config\, you will see the permission set that was added to the manifest.xml file has been added here.

Why is this Important


This is all very important from a security perspective:

  • First the web part is deployed to the SharePoint meaning it is only accessible to SharePoint and is not in the GAC. Deploying to the GAC makes the library accessible to any application running on that server.
  • Second only the web part deployed has permissions to SharePoint resources. No other future web parts can tap into resources that is should not have access to. In this case, our custom web part has access to SharePoint Object model and nothing else.

Closing


Again, I know this may seem like a lot of work given all of the CodePlex tools but still having a fundamental understanding of how this all works under the hood is extremely important.


References


Friday, May 1, 2009

Error Update WF Task

The Error

I ran into a rather interesting error as a result of me finding this error. I was getting the following:

This task is currently locked by a running workflow and cannot be edited

What I was trying to do was update a task list item that was generated from a WF workflow. I had a situation where the Assigned To field had not been properly set, and I wanted to modify the existing item. I could get this error in numerous ways. For instance, you cannot access the Assigned To field by going to task itself but if you select Edit in Datasheet you have the ability to update it.

As well I had written a little a small code snippet to update the task as well.

private static void UpdateTaskAssignedTo(string siteURL, string web,
string taskListName, string loginName, string email, int itemID)
{
using (SPSite site = new SPSite(siteURL))
{
using (SPWeb web = site.OpenWeb(web))
{
SPListItemCollection listItems = web.Lists[taskListName].Items;
SPListItem listItem = listItems.GetItemById(itemID);

web.AllowUnsafeUpdates = true;

SPUser user = web.AllUsers.GetByEmail(email);
SPFieldUserValue fieldUser = new SPFieldUserValue(web, user.ID, user.LoginName);
listItem["Assigned To"] = fieldUser;
listItem.SystemUpdate(false);

web.AllowUnsafeUpdates = false;
}
}
}

What would happen if did either the task would get updated as expected, however if I tried to update it again I would get the above error.

WORSE – the workflow task item will not start up the workflow again – for all intensive purposes it will seem that the workflow is stuck.

The Issue

I found this blog, among a few others discussing the issue. Several claimed this as a "bug" for WF and MOSS. It is not once I sat down and started thinking about it. The issue has to do with the task correlating to the WF workflow instance.

If you have built a custom workflow in MOSS, you will know that if you put in a OnTaskChanged event in your workflow basically the WF workflow is waiting for task to be updated. When you go to update the task by clicking on the task directly you will be taken to Workflow Form (InfoPath in most cases) which will submit back to the WF server within MOSS to start back up the workflow. However, if you access the task list item by other means (examples above), you are changing the state of the task itself and the WF instance gets out of sync.

The Resolution

It is pretty simple; all you need to do is add an action to the state for an Update. So put a dropdown on your IP form with the actions that a user can do like Approve, Deny or Update. Approve or Deny will move the workflow to a completely different state however, the Update will call back to the current state. The task will be recreated (so it is good practice to delete the task in the StateFinializationActivity. This is why I like to use K2 blackpearl instead to avoid this……

SharePoint 2007 SP2 Released

SharePoint 2007 SP2 has been officially release. It "includes all the fixes prior to SP2, and also several enhancements to improve server farm performance, availability and stability. Plus, a new stsadm operation has been added to help customers prepare for the upgrade to the next version of SharePoint."

You should start planning on how to deploy this into your QA, Staging and Production environments…

The things I was happy to see for MOSS where (KB953334) improvements with Microsoft Office Forms Server (InfoPath) given I use it a lot with K2 blackpearl:

  • Improves performance of large browser forms on InfoPath Form Services, both for memory usage and for page load time.
  • Improves reliability of Forms Server and of Microsoft Office SharePoint Server by addressing the previous behavior in which an upgrade of an administrator-approved form template triggers an IIS reset.
  • Improves the Digital Signature functionality for InfoPath Forms Services.

K2 May User Group

Anthony Petro (Technical Product Manager for K2) will be presenting on the 90x releases for K2 blackpearl and blackpoint. Please tune in and ask questions.

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

Tuesday, May 12, 11am-1pm central time

Phillip Knight from Merit Energy will be hosting the K2 user group meetings at Merit Energy, located at 13727 Noel Road, 2nd Floor Conference room, Tower 2, Dallas, Texas 75240. Parking information is included in the linked map below. Remote attendance information is included at the bottom of this message.

Link to map: http://www.meritenergy.com/content/MeritMap.pdf. Reminder: Merit Energy is on the 5th floor, but the meeting will be held in a 2nd floor conference room. Once off the elevator, go to the reception area and we will bring you back to the conference room.

Please RSVP to me via email
whether you are attending via live meeting or if you will be attending in person (so that we can plan for the number of people to order food for).

Check out the K2 Underground site and our user group at http://k2underground.com/k2/InterestGroupHome.aspx?IntGroupID=11. We are posting webexes/live meetings from our meetings at this site.

06/9/2009 11am – 1pm
07/14/2009 11am – 1pm
08/11/2009 11am – 1pm
09/8/2009 11am – 1pm
10/13/2009 11am – 1pm

Meeting Agenda:
11-11:15 Networking/Refreshments
11:15-11:30 Announcements/Intros of New people
11:30-11:45 Tips & Tricks
11:45-12:45 Technical Presentation
12:45-1:00 Meeting Wrapup

The Announcements section of the meeting will include any information regarding K2 upcoming events and user group events as well as brief introductions of our presenter and refreshment provider.

The Tips & Tricks Presentation is when we as members can pose questions to each other on projects that we are working on and having difficulty with. It is also a time when if we have learned something that we feel will be helpful to others, we can share it with the group. Bring yours to share/ask.

Meeting Presentation & Company & Sponsor:

We thank Anthony Petro from K2 for
presenting at our May K2 user group meeting. Anthony will be giving our K2 User group a sneak peak at the new features coming up in the 90x release of K2 BlackPearl. Don't miss this meeting, you will be the first of the K2 user community to know what's new.


We thank K2 for providing our presenter and Joe Bocardo from K2 for sponsoring our refreshments at our May meeting.


K2 blackpoint, a subset of K2 blackpearl features, provides unparalleled capabilities and affordability. It also offers an upgrade path so that organizations can grow their investment and add complexity over time, if needed.


For more information, go to http://www.blackpoint.k2.com.


The K2 platform is for delivering process-driven applications that improve business efficiency. Visual tools make it easy for anyone to assemble reusable objects into applications that use workflow and line-of-business information.


K2-based solutions are deployed by a growing number of the global Fortune 100. K2 is a division of SourceCode Technology Holdings, Inc. based in Redmond, Washington, and has offices all over the world.


For more information, contact Joe Bocardo at joeb@k2.com.



Meeting Presenter:


Anthony Petro resides in Silverthorne, Colorado and is the Technical Product Manager for K2. He started his professional career in the consulting world 14 years ago and has always remained focused on Microsoft technologies and solutions. He joined Microsoft in 2001 and spent the next 5 years heavily immersed in SharePoint Joint Development Programs bridging the gap between the product development teams in Redmond and the enterprise customers around the world. He was a strong contributor to the SharePoint community in its infancy focused on teaching the masses about the complexities of search and enterprise scale and using products such as K2 filling the enterprise workflow gaps of SharePoint. He joined K2 in 2006 to help bring K2 blackpearl to market through early adopter programs that spanned the alpha and beta cycles through to RTM. Anthony remains actively involved in early adopter programs for K2 blackpoint and K2 connect and planning the future releases of all product lines.


For Virtual Attendees:

Note: please keep your phone on mute until you are ready to speak.

Audio Information

Telephone conferencing
Choose one of the following:

Start Live Meeting client, and then in Voice & Video pane under Join Audio options, click Call Me. The conferencing service will call you at the number you specify. (Recommended)

Use the information below to connect:
Toll: +1 (719) 867-1571

Toll-free: +1 (877) 860-3058

Participant code: 914421

First Time Users:

To save time before the meeting, check your system to make sure it is ready to use Microsoft Office Live Meeting.
Troubleshooting
Unable to join the meeting? Follow these steps:

Copy this address and paste it into your web browser:

1. Copy this address and paste it into your web browser:
https://www.livemeeting.com/cc/scna1/join

2. Copy and paste the required information:

Meeting ID: RPFR8T
Entry Code: M~`5~pd!p
Location: https://www.livemeeting.com/cc/scna1


If you would like to provide refreshments at an upcoming meeting or present at an upcoming meeting, please contact me.


Our next meeting announcement will be sent out next Tuesday.


Let me know if you have any questions prior to the meeting.

WF Error When Missing Assigned To

The Error


I recently came across some strange errors which caused me to lose a lot of time on and could not find anything on. I had a Publishing with a WF Workflow and I was getting errors like this whenever I would access my page after initiating the workflow.


Application error when access /_layouts/Workflow.aspx, Error=Object reference not set to an instance of an object. at Microsoft.SharePoint.Publishing.Internal.WorkflowUtilities.GetIsTaskAssignedToUserOrGroup(CachedWorkflowTask task, Int32 userId) at Microsoft.SharePoint.Publishing.Internal.WorkflowUtilities.GetCurrentUserApprovalTask(CachedListItem listItem) at Microsoft.SharePoint.Publishing.Internal.WorkflowUtilities.IsApprovalWorkflowTaskActiveForUser(CachedListItem listItem) at Microsoft.SharePoint.Publishing.WebControls.ConsoleNode.CurrentState(Page currentPage, Boolean cacheResult) at Microsoft.SharePoint.Publishing.WebControls.ConsoleNode.CurrentState(Page currentPage) at Microsoft.SharePoint.Publishing.WebControls.ConsoleDataSource.PopulateDataSource() atMicrosoft.SharePoint.Publishing.WebControls.XmlConsoleDataSource.PopulateDataSource() at Microsoft.SharePoint.Publishing.WebControls.ConsoleDataSource.GetHierarchicalView(String viewPath) at Microsoft.SharePoint.Publishing.WebControls.PublishingSiteActionsMenuCustomizer.OnPreRender(EventArgs e) at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Control.PreRenderRecursiveInternal() at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)


I was also seeing "unexpected" errors on SharePoint and in the logs I would find the following:


ERROR: request not found in the TrackedRequests. We might be creating and closing webs on different threads. ThreadId = 13, Free call stack = at Microsoft.SharePoint.SPRequestManager.Release(SPRequest request) at Microsoft.SharePoint.SPWeb.Invalidate() at Microsoft.SharePoint.SPWeb.Close() at Microsoft.SharePoint.SPSite.Close() at SourceCode.SharePoint.Common.SPObject.System.IDisposable.Dispose() at SourceCode.SharePoint.Workflow.Proxy.Common.GetK2IntegrationListItem(SPWorkflowActivationProperties properties) at SourceCode.SharePoint.Workflow.Proxy.MultipleTasksAndUsers.ActionK2WorkListItem(SPUser user, Boolean isFinished, String actionResult, String serialNr, String actionResponse, SPWorkflowTaskProperties afterProperties) at SourceCode.SharePoint.Workflow.Proxy.MultipleTasksAndUsers.OnTaskChanged_Event(Object sender, TaskEventArgs e) at System.Workflow.ComponentModel.Activity.RaiseGenericEvent[T](DependencyProperty dependencyEvent, Object sender, T e) at SourceCode.SharePoint.Workflow.ActivityLibrary.TaskActivity.onTaskChanged_Invoked(Object sender, ExternalDataEventArgs e) at System.Workflow.ComponentModel.Activity.RaiseGenericEvent[T](DependencyProperty dependencyEvent, Object sender, T e) at System.Workflow.Activities.HandleExternalEventActivity.RaiseEvent(Object[] args) at System.Workflow.Activities.HandleExternalEventActivity.Execute(ActivityExecutionContext executionContext) at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext) at System.WorkflowComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext) at System.Workflow.ComponentModel.ActivityExecutorOperation.Run(IWorkflowCoreRuntime workflowCoreRuntime) at System.Workflow.Runtime.Scheduler.Run() at System.Workflow.Runtime.WorkflowExecutor.RunScheduler() at System.Workflow.Runtime.WorkflowExecutor.RunSome(Object ignored) at System.Workflow.Runtime.Hosting.DefaultWorkflowSchedulerService.WorkItem.Invoke(WorkflowSchedulerService service) at System.Workflow.Runtime.Hosting.DefaultWorkflowSchedulerService.QueueWorkerProcess(Object state) at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state) at System.Threading.ExecutionContext.runTryCode(Object userData) at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack) at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state) , Allocation call stack (if present) null


This was pretty frustrating because every time I would go to the publishing page I would get this error. However the minute that I cancel the workflow or finish by completing the tasks in the task list the error would go away and I could access the publishing page.


The Issue


I was able to narrow down the issue to the following scenario:

  • I had a Site with the Publishing Feature Turned on.
  • In the Pages Library I had it configured such that all content had to be approved.
  • I had configured a custom WF Workflow on Pages Library configured to "Start this workflow to approve publishing a major version of an item".

The issue was that even though the workflow could be started I would only get this error if I had it configured to "Start this workflow to approve publishing a major version of an item". I manually initiated the workflow I would not get the error.


What I found out is the Assigned To field in the task was missing! Now if you have an OOB Approval workflow or do something in SharePoint Designer it is impossible to create this error. However if you are writing a custom workflow in Visual Studio and do not set the Assigned To field in the task you will get this error.


This error will ONLY occur if you are using it for a Publishing Page too. I was able to run this same workflow with the exact configuration on a MS Word document and would not receive the error. I do not know exactly what to call this menu system (below) but you will see it when using publishing pages.



Basically when you have a workflow started when a major version is published, the WF workflow will be "daisy chained" into the approve/deny workflow that is turned on when you require content approval. This menu system is validating against the Assigned To field in the Task that is associated to the running workflow instance. If the Assigned To is blank, you will get the errors above when trying to access the page.


The Resolution


Make sure to set the Assigned To field of tasks being created for your workflows.

Deploy Publishing Page and IgnoreIfAlreadyExists

For a project I was working on, I wanted to do a deployment of a SharePoint publishing page through a WSP and a Feature. I found a few blogs that assisted with me getting this started. Creating the actual Feature was not too hard but I run into a few issues.

The following is the Feature that I created.

<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="64A687A4-B4DD-4742-8FE9-66191929D8BD"
Title="ML Demo KB Publishing Page Template"
Description="This feature will load the KB Publishing Page Template."
Version="1.0.0.0"
Scope="Site">
<ElementManifests>
<ElementManifest Location="Elements.xml"/>
<ElementFile Location="KBArticle.aspx"/>
</ElementManifests>
<ActivationDependencies>
<!--ML.Demo.KBMgt.WSS.CTypes-->
<ActivationDependency FeatureId="15A053C6-59DB-4a14-9CB6-3DB667634493" />
</ActivationDependencies>
</Feature>


<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="KBPageLayoutsModule" Url="_catalogs/masterpage" RootWebOnly="True" Path="">
<File Url="KBArticle.aspx" Name="KBArticle.aspx" IgnoreIfAlreadyExists="TRUE" Type="GhostableInLibrary">
<Property Name="Title" Value="KB Article"></Property>
<Property Name="ContentType" Value="KB Article"></Property>
<Property Name="PublishingAssociatedContentType" Value=";#KB Article;#0x010100C568DB52D9D0A14D9B2FDCC96666E9F200
7948130EC3DB064584E219954237AF3900
242457EFB8B24247815D688C526CD44D00
DECC999E401842A08C5F380CB73E0191;#"
></Property>
</File>
</Module>
</Elements>

In this feature I am doing several things:

  • I am deploying to the Master Page Gallery because the Feature has Site scope, the Module Url is set to _catalogs/masterpage, File Type is set to GhostableInLibrary.
  • I have a Feature dependency on another Feature that is responsible for deploying a custom content type. I could have combined these all into one Feature however I like keeping them separate.
  • When creating the publishing page I simply created it through SharePoint Designer, I then viewed the code and copy and pasted it into an aspx within my solution in Visual Studio. I really do not want to rely on SharePoint Designer over the long term.
  • In the File element I specify Properties which are used to set values into the columns within the Master Page Gallery library. For instance I set the title and content type association for the publishing page.

Remember that if you cannot use Features to do a production deployment, you should really rethink your deployment and configuration management procedures for your SharePoint environment over the long run. Otherwise your environment will become a mess of one-off solutions with no way to redeploy solutions across development, quality assurance, staging and production. In this case I know I could have simply gone into the SharePoint user interface, manually create the content type and then associate a publishing page to the content type via SharePoint Designer – however that is not really that repeatable from a deployment perspective.

The first challenge I ran into was trying to understand how the IgnoreIfAlreadyExists attributed actually worked. According to the documentation on MSDN, if IgnoreIfAlreadyExists is set to TRUE, it will suppress any errors if the file has already been loaded into the Master Page Gallery and continue uploading. I found several blogs providing incorrect information claiming that the IgnoreIfAlreadyExists does not work as intended. My experience was it works. When I did the deployment the new Publishing page does get pushed up. I say this because I would look at pages that were referencing the publishing page and the changes would be reflected. HOWEVER the deceiving thing is if you look at the entry in the Master Page Gallery, neither the time date stamp nor version will be updated making you think that the change was not applied.

Another interesting thing was if you deactivate this Feature the publishing aspx page will remain in the Master Page Gallery even if the publishing page is not being referenced anywhere. I had initially assumed SharePoint was keeping the publishing page there because it is not possible to delete a publishing page it is referenced. The only way you remove the publishing page is to create a Feature event handler for ondeactivation to delete it manually from the Master Page Gallery. Remember if you do this you can get an exception saying "This item cannot be deleted because it is still referenced by other pages" if the publishing page is being used anywhere.

Another issue I ran into was trying to update the properties of the publishing page in the Master Page Gallery. No matter what I do, if I change the file properties in the Feature, the changes would not take be visible in the Master Page Gallery. This is some related to the first issue I had. Anyways, I determined the only way to update the entry the Master Page Gallery is to put an onactivation event handlers onto the Feature.

References:

Wednesday, April 29, 2009

Publishing Site Provisioning

I recently ran into an issue with creating a site template that where I wanted to use the Publishing Feature. My end goal was to create a real site definition but I first wanted to try to create template directly in SharePoint. I quickly found out that if you have the "Office SharePoint Server Publishing" Feature turned, on the "Save site as template" will not be available. The most common solution that many suggested was to simply turn off the Publishing Feature, create the site template and then manually turn on the Pushing Feature.


However this would not work well because I am building an automated site provisioning processes using K2 blackpearl. Basically in this K2 blackpearl process I use an InfoPath form, get approval on the site request, and then dynamically generate the site with custom SharePoint groups. We are trying to drive SharePoint Governance with K2 blackpearl which will ensure that the site topology is organized well, SharePoint groups and users are managed in a repeatable process and system administrators can be less involved with creating sites. As well, I want to use customized site templates to ensure that all sites are presented in the same manner instead of being a hodge-podge mess of content. We are even going as far as adding standardized content types into the site templates with K2 workflows mapped to the content types to ensure that publication of the content always goes through a standard process.


Back to the original problem at hand – knowing that I am creating an automated process to site provisioning I cannot expect users to go in and manually turn on the Publishing Feature on the site. The options I came up with were the following:

  1. Create my own site template and in the ONET.xml add a dependency to turn on the Publishing Feature.
  2. Create stapling Feature that would turn on the Publishing Feature.
  3. Write some code that would turn on the Publishing Feature.
  4. There are more – but will stick to this for now…

Option 1 – Did not work as intended. I wanted to use the STS template. I followed best practices, created my own site template and then added <Feature FeatureId="94C94CA6-B32F-4da9-A9E3-1F3D343D7ECB" /> to the <WebFeatures> element in the onet.xml file. I also modified the <Modules> to have several custom web parts displayed on the default.aspx. Doing this made sure that the Publishing Feature was turned on when the site was provisioned by SharePoint. However the Publishing Feature would completely wipe out my home page (default.aspx) and all the changes I made to default.aspx in the onet.xml file were gone!!!


Now, if a manually create my site template and then manually turn on the Publishing Feature the default.aspx will not get wiped out. Since the Publishing Feature is being turned during the actual site creation process within SharePoint, SharePoint is allowing the Publishing Feature to take over the homepage. So this would not work for me.


Option 2 – My next solution was to create the following Site Stapling Feature:

<Feature Id="13F62CC1-22DE-4719-AA44-1BCACD9E2D50"
Title="ML Demo KB Site Staple"
Description="Associates publishing and content type binding to Site Template."
Version="1.0.0.0"
Scope="Site"
Hidden="FALSE"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="SiteStaple.xml" />
</ElementManifests>
</Feature>


<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Web Publishing -->
<FeatureSiteTemplateAssociation Id="94C94CA6-B32F-4da9-A9E3-1F3D343D7ECB" TemplateName="KB#0" />
<!-- KB Content Type Binding -->
<FeatureSiteTemplateAssociation Id="AD644A91-BA8B-45ff-89FD-F96BCBEDC3BD" TemplateName="KB#0" />
</Elements>

As you can see the FeatureSiteTemplateAssociation is used to turn the custom features I need, including the Publishing Feature. The result was the exact same as Option 1. Again because the Publishing Feature is being activated during SharePoint's site provisioning process the default.aspx page is being overridden.

Option 3 – My ultimate solution was to add some code into my K2 blackpearl process to activate the Feature. I was pretty happy to say to that point I had no code yet in my process – however that is just not possible some days but that is why K2 blackpearl is so great. I basically added the following lines of code into my site provisioning process and I was able to completely replicate what I was able to do manually as a user within SharePoint.


The K2 Process






Code from "Activate Site Features"

//Activate the Features...
using (SPSite site = new SPSite(K2.StringTable["SharePoint Site Collection URL"]))
{
string webURL = K2.StringTable["KB Collaboration Site Logical Path"] + "/KB" +
K2.ProcessInstance.DataFields["New KB Number"].Value;

using (SPWeb web = site.OpenWeb(webURL))
{
SPFeatureCollection features = web.Features;

features.Add(new Guid(K2.StringTable["Publishing Feature"]), true);
features.Add(new Guid(K2.StringTable["Web Publishing Feature"]), true);
features.Add(new Guid(K2.StringTable["KB Content Type Binding Feature"]), true);
}
}