June 28, 2011 - 09:10, by Steven Van de Craen
Categories: SharePoint 2010, Workflow, Debugging
In my previous post “SharePoint 2010: Programmatically Approving/Rejecting Workflow” I mentioned the possibility of an issue when programmatically altering a workflow task. I have been testing it on several SharePoint 2010 farms with different parameters (different patch level, etc).
UPDATED July 2, 2011: RESOLVED
Set up
A simple SharePoint Designer 2010 workflow with the “Start Approval Process” action published to a document library. You start the workflow and a task is created for the approver.
The Approver is set to another test user, but all my code and script runs as the administrator (and thus System Account). Makes no difference though.
Note: when running the workflow multiple times (for testing), make sure to delete all tasks that are linked to your item. Or adapt the code below to not just take the first task in the collection…
Issue experienced
You alter the workflow task using a Visual Studio Console Application or Windows Application with the following (or similar code):
1 string url = "http://intranet/Shared Documents/Instructions.txt"; 2 using (SPSite site = new SPSite(url)) 3 { 4 SPWeb web = site.RootWeb; 5 SPListItem item = web.GetFile(url).Item; 6 SPWorkflowTask task = item.Tasks[0]; 7 8 Hashtable ht = new Hashtable(); 9 ht["TaskStatus"] = "Approved"; 10 11 SPWorkflowTask.AlterTask(task, ht, false); 12 }
After the code has run successfully you don’t see anything happen in the workflow status page. On some occasions you get “Due to heavy load, the latest workflow operation has been queued. It will attempt to resume at a later time” which could mean that the timer job hasn’t completed successfully yet. This message may or may not disappear for you.
Also, when trying to do a second alteration to the workflow task (by code or in the UI) you get “This task is currently locked by a running workflow and cannot be edited”.
So somehow your action was registered but not executed…
PowerShell to the rescue
Terminate the workflow (because it really hangs and there’s not much you can do with it) and kick off a new one.
Now open the SharePoint 2010 Management Shell (PowerShell) and run the following (or similar code):
$s = Get-SPSite("http://intranet")
$w = $s.RootWeb
$i = $w.GetFile("http://intranet/Shared Documents/Instructions.txt").Item
$t = $i.Tasks[0]
$ht = new-object Hashtable
$ht["TaskStatus"] = "Approved"
[Microsoft.SharePoint.Workflow.SPWorkflowTask]::AlterTask($t, $ht, $false)
This WILL work and correctly finish the approval cycle in any of my tested environments.
Notes
You can really test this over and over and over; it will work in PowerShell but not in the Visual Studio 2010 application. I have tested Release versus Debug, in and out Visual Studio, etc.
Solution (July 2, 2011)
The big difference in both techniques is that the PowerShell remains open and isn’t disposing any objects. This allowed the workflow engine to complete the task.
Option #1
1 SPWorkflowTask.AlterTask(task, ht, true);
From MSDN:
“If true is passed as the argument to fSynchronous, this method waits up to 30 seconds to determine whether the attempted update was accepted by the workflow schedule as valid. The method then returns true if the update was accepted and not rolled back, or false if the update was not accepted. If false is passed as the argument to fSynchronous, this method always returns true.”
http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.workflow.spworkflowtask.altertask.aspx
I have found this working most of the times, however some test runs still experienced the issue. Perhaps my test environment isn’t powerful enough or the action takes just a little too long to kick in ?
Option #2
Using the code below I learned it took about 21 seconds for the task to complete:
1 int i = 1; 2 while (!(bool)item.Tasks[taskId]["Completed"]) 3 { 4 Console.Clear(); 5 Console.Write("" + i + " " + task["Completed"]); 6 Thread.Sleep(1000); 7 i++; 8 }
That should give ‘option #1’ suffice time to complete the task, but still it didn’t in all occasions. If you’re experiencing this you could write up your own delay either as replacement to, or in combination with ‘option #1’.
1 SPWorkflowTask.AlterTask(task, ht, true); 2 // Additional delay 3 while (!(bool)item.Tasks[taskId]["Completed"]) 4 { 5 Thread.Sleep(1000); 6 }
Related
Big kudos to my colleagues Tom De Wilde and Timmy Gilissen for helping me out !!
March 11, 2011 - 18:17, by Steven Van de Craen
Categories: Debugging, SharePoint 2007, SharePoint 2010
Each SharePoint List can be altered with a custom DispForm.aspx, EditForm.aspx and NewForm.aspx to display, edit or create list items and metadata. This post is about restoring them to a working point.
Botched Forms
So one of these forms are edited to a loss state or deleted altogether. If this is the case it will have some tangible effects on the List display:
The above screen shows a botched Display Form where the Item Url shows http://moss/?ID=1
Behind the scene
All three forms are based on the same template {SharePointRoot}\TEMPLATE\Pages\form.aspx and have a List Form Web Part in the “Main” WebPartZone with specific settings to connect to the list and which function they have. Also, the ID property of the List Form Web Part must match the value of the __WebPartId attribute.
SharePoint 2007
|
SharePoint 2010 |
|
|
Recovery
In a perfect world you can reset the edited form to the Site Definition or restore the it from the Recycle Bin, however this is often not the case.
Here’s a quick reference to the Microsoft.SharePoint.PAGETYPE enum:
Member name
|
Description
|
PAGE_INVALID
|
Not used. Value= -1.
|
PAGE_DEFAULTVIEW
|
Default view. Value=0.
|
PAGE_NORMALVIEW
|
Normal view. Value=1.
|
PAGE_DIALOGVIEW
|
File dialog box view. Value=2.
|
PAGE_VIEW
|
View, including both default view and normal view. Value=3.
|
PAGE_DISPLAYFORM
|
Display form for list items. Value=4.
|
PAGE_DISPLAYFORMDIALOG
|
Display form for a file dialog box. Value=5.
|
PAGE_EDITFORM
|
Edit form for list items. Value=6.
|
PAGE_EDITFORMDIALOG
|
Edit form for a file dialog box. Value=7.
|
PAGE_NEWFORM
|
New form for list items. Value=8.
|
PAGE_NEWFORMDIALOG
|
New form for a file dialog box. Value=9.
|
PAGE_SOLUTIONFORM
|
Solution form. Value=10.
|
PAGE_MAXITEMS
|
Not used. Value=11.
|
(http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.pagetype.aspx)
I’ve highlighted the three related to this matter (applies to both SharePoint 2007 and SharePoint 2010).
Manually
1. It’s easiest if you take a corresponding form from another SharePoint List in the site and export it (SharePoint Designer) or just copy it to a text editor. This way, most of the settings of the List Form Web Part are already correct
2. Generate a new GUID and fill that in for the __WebPartId and properties of the List Form Web Part markup. It has to be the same GUID but the formatting is different. See the above screens for samples
3. Update the property with the List ID you’re targeting
4. Verify all other properties of the List Form Web Part and save the file as either DispForm.aspx, EditForm.aspx or NewForm.aspx
5. Import (SharePoint Designer) the page (or paste contents from the text editor)
Scripted
I had a botched list in SharePoint 2007 and I wrote a Console Application for it. It takes the markup for a Form and I’ve replaced some values with placeholders already. It connects to the List and replaces the placeholders with real values, then it saves the file to SharePoint.
1 string listUrl = "http://moss/Lists/Sample List"; 2 using (SPSite site = new SPSite(listUrl)) 3 { 4 using (SPWeb web = site.OpenWeb()) 5 { 6 SPList list = web.GetList(listUrl); 7 RestoreListForm(list, PAGETYPE.PAGE_DISPLAYFORM); 8 //RestoreListForm(list, PAGETYPE.PAGE_EDITFORM); 9 //RestoreListForm(list, PAGETYPE.PAGE_NEWFORM); 10 } 11 }
1 private static void RestoreListForm(SPList list, PAGETYPE ptype) 2 { 3 SPWeb web = list.ParentWeb; 4 5 // Create Form File 6 string formFilename = null; 7 SPControlMode formMode = SPControlMode.Invalid; 8 SPFileCollection files = list.RootFolder.Files; 9 Guid wpId = Guid.NewGuid(); 10 CalcFormInfo(ptype, out formFilename, out formMode); 11 12 byte[] formContents = Encoding.ASCII.GetBytes(String.Format(pagecontent, list.Title, list.ID.ToString("B"), formMode, (int)ptype, wpId.ToString("B"), String.Concat("g_", wpId.ToString("D").Replace("-", "_")).ToLower())); 13 14 try 15 { 16 if (files[formFilename].Exists) 17 files[formFilename].Delete(); 18 } 19 catch { } 20 21 SPFile formFile = files.Add(formFilename, formContents, true); 22 } 23 24 private static void CalcFormInfo(PAGETYPE type, out string fileName, out SPControlMode mode) 25 { 26 switch (type) 27 { 28 case PAGETYPE.PAGE_DISPLAYFORM: 29 fileName = "DispForm.aspx"; 30 mode = SPControlMode.Display; 31 break; 32 case PAGETYPE.PAGE_EDITFORM: 33 fileName = "EditForm.aspx"; 34 mode = SPControlMode.Edit; 35 break; 36 case PAGETYPE.PAGE_NEWFORM: 37 fileName = "NewForm.aspx"; 38 mode = SPControlMode.New; 39 break; 40 default: 41 fileName = null; 42 mode = SPControlMode.Invalid; 43 break; 44 } 45 }
Find the complete code here: Program.cs. Note that there’s a different page markup and additional properties for SharePoint 2010 and the code needs to be adapted to that.
There’s no magic in the code above, just automating some of the manual steps in a quick fix script.
Conclusion
As a general practice, don’t directly modify the default forms, rather create copies or blank forms and update the List or Content Type Settings (through SharePoint Designer or programmatically) to point to your custom forms.
If you do have a botched list, hopefully the above information can be of help.
February 6, 2011 - 17:51, by Steven Van de Craen
Categories: SharePoint 2010, IIS, Debugging
If you’re developing for SharePoint 2010 the Application Pool has a setting called Ping Maximum Response Time with a default value of 90 seconds. In effect, this means that debugging your Web Part, Page, etc. in SharePoint 2010 will only work for 90 seconds and then you get this:
You can increase the value of Ping Maximum Response Time in the Application Pool Settings in IIS:
You can also increase the value for future Application Pools via Set Application Pool Defaults (see Actions menu):
September 7, 2010 - 20:18, by Steven Van de Craen
Categories: .NET, Debugging, Event Receivers, SharePoint 2007, SharePoint 2010, SharePoint Foundation 2010, WSS 3.0, SharePoint Server 2010, Search Server 2010, Search Server 2008, MOSS 2007
I’m wrapping up a SharePoint 2007 to 2010 migration with custom development including programmatically copying files and folders, custom Event Receivers, Web Parts, etc.
Since the new setup uses a new AD domain, user accounts were mapped to new accounts and migrated using stsadm –o migrateuser.
After this we noticed errors like “User cannot be found”.
A bit of investigation showed that the ‘migrateuser’ operation didn’t update SPFile.Author or SPFile.ModifiedBy and querying those properties would throw the above exception.
A look at the ItemXml confirmed this:
ows_Modified_x0020_By="OLDDOMAIN\olduser" ows_Created_x0020_By="OLDDOMAIN\olduser" ows_File_x0020_Type="pdf" ows_ID="3" ows_Created="2010-06-28 15:20:48" ows_Author="133;#New User" ows_Modified="2010-09-07 18:41:07" ows_Editor=";#System Account" ows_File_x0020_Size="3;#1035567" ... />
Workaround
I didn’t want to script an update to all files (because that would trigger Event Receivers) so I avoided querying those properties directly and instead created an extension method to get those values through the SPListItem:
SPFieldUserValue fuv = new SPFieldUserValue(item.Web, (String)item["Author"]);
return fuv.User;
Scripting an update
There were a lot of Event Receivers in the custom solution so I didn’t take this path, mainly because I noticed in SharePoint 2010 the SPListItem.SystemUpdate() method will also trigger Event Receivers. Here’s a blog post on it:
http://blogs.msdn.com/b/mjsabby/archive/2010/01/24/disabling-events-in-sharepoint-2007-and-sharepoint-2010.aspx
This is different from SharePoint 2007 as far as I recall. Feel free to comment on this.
Other mentions
This blog post by Keith Richie mentions a solution for SPWeb.Author: http://blog.krichie.com/2008/09/12/resetting-the-author-on-a-sharepoint-site-or-wherefore-art-thou-author-redux/
Be sure to take this into consideration !
January 29, 2010 - 15:09, by Steven Van de Craen
Categories: .NET, Content Types, Debugging, MOSS 2007, Office 2003, Office 2007, Search Server 2008, SharePoint 2007, WSS 3.0
A while ago I stumbled upon a serious design limitation regarding Content Types and centralized document templates. What then followed was a series of testing, phone calls with Microsoft, finding alternative solutions and deep dive into Office Open XML.
Request from the customer
“We want to use MOSS 2007 to create a collaboration site per project for our 400+ projects. These collaboration sites all use the same Content Types and document templates. We want to centrally manage those document templates so that we don’t need to make the same change 400+ times.”
Approach
Due to sizing we architected a solution with a dozen of Site Collections that would each hold a collection of project sites. We ‘Feature’-ized our Content Types and Site Columns so that they could quickly be activated on all Site Collections and used by the child sites. Document templates would be stored in a central document library and we would link to them in the Content Types on the project sites.
First issue
Linking to document templates really doesn’t play well with the Document Information Panel (DIP). I have blogged about this here:
Centralizing Document Templates in a library- Document Information Panel shows incorrect properties
We proposed a solution where the document templates in the central library would be pushed to the Content Type resource folder on site level. The code to perform the push would have to connect to the Site Collections, copy the template to the resource folder (http://sitecollectionurl/_cts/contenttypename) and link template and Content Type together.
When a Site Content Type is associated with a List it will be a List Content Type inheriting from the Site Content Type and the document template will be copied to the List Content Type resource folder (http://sitecollectionurl/listurl/Forms/contenttypename).
Second issue
Did I tell you that the column values (metadata) have different values based on the project site ? So when a project site is created we automatically update the List Content Type Column default value with the values for that specific project site. Unfortunately this is not supported when working with Office 2007 file formats because they only react on changes to the Site Column.
Consider the following scenario:
1) Set up a document library with a Content Type that has a text column with a default value
2) Upload a new .doc or .docx as Content Type template
TEST 1) Create a new document:
.DOC: the DIP will contain the text column with the default value
.DOCX: the DIP will contain the text column with the default value
3) In SharePoint, modify the default value of the text column
TEST 2) Create a new document:
.DOC: the DIP will contain the text column with the updated default value
.DOCX: the DIP will contain the text column with the original default value
Microsoft confirmed that this is by design.
Third issue
When designing our document templates with Content Controls mapped to our SharePoint fields we didn’t know that internally in the DOCX file it uses a GUID for mapping the Content Control with the SharePoint Metadata XML. For fields (Site Columns) created in the UI or through API this is the SPWeb.ID of where they were created. For fields created declaratively through Features this is the SPList.ID of where their Content Type is associated to.
So some things to notice
- Creating a single document template with Content Controls mapped to your declaratively added Fields cannot be used in two different Document Libraries because the Content Controls lose the connection with Field (because the ID of the List is different and not updated in the Content Control)
- The solution here is to create your fields in the UI or through the API (this could be in a Feature Activating event)
- Copying a document template across Site Collections means different Web ID’s so it also affects fields created in the UI or the API
Finally
In the end we wrote some wrapper classes for Office 2007 file formats using System.IO.Packaging that would manipulate our document templates once they were copied over to a different Site Collection. We also rewrote our Features to create our Fields through the API (SPWeb.Fields.AddFieldAsXml()).
- Remove the SharePoint Metadata XML so that association of the Content Type to a List it would be regenerated automagically
- Loop through every Content Control and find to which Field they were mapped using information in it’s XPath. Then we would update the GUID’s in the Content Control to match the SPWeb.ID
Next time I’ll definitely take these design limitations into account. Lessons learned I’d say !
IE8 on Win7 RC: issues with OWA 2007
July 22, 2009 - 20:48, by Steven Van de Craen
Categories: Debugging, Exchange Server, General, Internet Explorer, Windows 7
I would have preferred if Win7 RTM came sooner so that I could avoid migrating from Beta to RC and then from RC to RTM but no point to keep on whining about it :) So I decided to install the 64 bit issue of Windows 7 Release Candidate. I love how smooth those Win7 installs are. Very little interaction is required and the system correctly detects and installs my hardware.
Once up and running I logged in into my Outlook Web Access (Exchange 2007) and noticed a lot of flickering and errors in javascript files. Here’s an example:
Exception Details
-----------------
Date: Wed Jul 22 20:41:09 UTC+0200 2009
Message: Unspecified error.
Url: https://webmail.company.be/owa/8.1.240.5/scripts/premium/cdayvw.js
Line: 181
Tried the compatibility mode, tried the no-addons approach, no luck. However then I noticed there was another link in the Start Menu for Internet Explorer that read “Internet Explorer (64-bit)”. I tried it and poof: OWA behaved as a perfect citizen ! Joy !
0 Comments
June 29, 2009 - 21:30, by Steven Van de Craen
Categories: SharePoint 2007, Debugging, Cross Site Query, MOSS 2007
If you have MOSS 2007 than you get the Site Manager for advanced management and with it the “Content and Structure Reports” (http:///Reports List).
You can easily display the results of a report through the Site Actions menu. You can also add custom reports since the underlying technology is SPSiteDataQuery and the syntax to use is CAML. For an overview see Vince’s blog post.
If you have issues displaying results then remember you can use the Diagnostic Logs feature in SharePoint to troubleshoot. If there’s an issue the default logging level will pick up the Unexpected entry, but if you need additional info you can configure the logging level in Central Administration. You will need to be looking into the “Site Management” event category.
For example; if you portal has more than 1000 lists then by default the Reports won’t return any results. Use the logs to diagnose the issue:
06/29/2009 20:17:40.34 w3wp.exe (0x0960) 0x0A3C CMS Site Management 622h Unexpected SMReportsData GetQueryResults - Query Execution threw SPException: The query cannot be completed because the number of lists in the query exceeded the allowable limit. For better results, limit the scope of the query to the current site or list or use a custom column index to help reduce the number of lists.
06/29/2009 20:17:40.34 w3wp.exe (0x0960) 0x0A3C CMS Site Management 6oz4 Medium SmtPerf: SMDataSource.PopulateQueryResultsDataSet(SHAREPOINT\system) - Elapsed time= 00:00:00.0500720
06/29/2009 20:17:40.34 w3wp.exe (0x0960) 0x0A3C CMS Site Management 6oz4 Medium SmtPerf: SmtMainPage.OnPreRenderComplete - accumulated(SHAREPOINT\system) - Elapsed time= 00:00:00.1802592
You can override the Lists settings by editing each report item and specifying the CAML List Type as follows:
(Make sure not to misspell the attribute: info)
Note that this does have a performance impact on your environment: Additional performance and capacity planning factors
Good luck !
December 15, 2008 - 16:08, by Steven Van de Craen
Categories: .NET, Search Server 2008, SharePoint 2007, Debugging, Event Receivers, MOSS 2007, WSS 3.0
Today I got into some code reviewing of an Item Event Receiver using Enterprise Library for data access. The problem occurred when registering the Event Receiver for a SharePoint List using the object model (SPList.EventReceivers.Add)
Exception has been thrown by the target of an invocation.
Here's a simplified view into the code:
public class MyEventReceiver: SPItemEventReceiver
{
MyDataAccess da = new MyDataAccess();
public override void ItemAdded(SPItemEventProperties properties)
{
...
}
}
public class MyDataAccess
{
public MyDataAccess()
{
Microsoft.Practices.EnterpriseLibrary.Data.DatabaseFactory.CreateDatabase("MyDB");
}
...
}
Apparently when the Event Receiver is registered it instantiates the MyDataAccess class which in turn calls the EntLib method in its constructor. This is what causes the exception.
A solution is to instantiate the MyDataAccess class in the methods rather than on event receiver initialisation:
public class MyEventReceiver: SPItemEventReceiver
{
public override void ItemAdded(SPItemEventProperties properties)
{
MyDataAccess da = new MyDataAccess();
...
}
}