Tuesday, 21 February 2012

Create a SiteCollection based on a WebTemplate from PowerShell

Hey! With SharePoint 2010 there is this cool new way of creating sites know as "WebTemplate". (see more about this here: http://msdn.microsoft.com/en-us/library/ms434313.aspx)

The thing is that as the "template" is only used at initialisation time then it is seen as created from the base template. So question: if I need to create the site from PowerShell what shall I specify as "-template" ?

The full command will be:
New-SPSite -Url $site_url -OwnerAlias $owner -Template "{Parent_Feature_GUID}#WebTemplate_Name"

IMPORTANT NOTE : If you have other WebTemplates enabled for creation from this WebTemplate that are based on native site definitions and if you have an OS with a different culture than the installed SharePoint the creation will fail.

Example:
I have a Win7 fr-FR with SharePoint 2010 en-US installed.
I created a WebTemplate for the root site based on "CMSPUBLISHING#0", then I created an other WebTemplate for the sub site that is also based on "CMSPUBLISHING#0".
I have a feature in the root site that enables the sub site template creation.
When I tried creating the site with the New-SPSite command it failed with the following message:
The SPWebTemplate named CMSPUBLISHING#0 cannot be found. The SPWebTemplate matching the BaseTemplateName and BaseConfigurationID of the WebTemplate declared by feature {GUID} could not be found.

Thanks to this guy, I managed to figure out it was due to a culture problem whereas SharePoint was pointing to "...\TEMPLATE\1036\..." in stead of "...\TEMPLATE\1033\..." to find the CMSPUBLISHING template.

Easiest solution found: Execute the command within a en-US (1033) culture context !
No more blah-blah here it is (credits for the Using-Culture here):

$site_url = "http://mysitecollection";
$owner = "me";


function Using-Culture (
  [System.Globalization.CultureInfo]
  $culture = (throw "USAGE: Using-Culture -Culture culture -Script {...}"),
  [ScriptBlock]
  $script = (throw "USAGE: Using-Culture -Culture culture -Script {...}"))
{
    $OldCulture = [Threading.Thread]::CurrentThread.CurrentCulture
    $OldUICulture = [Threading.Thread]::CurrentThread.CurrentUICulture    
    try {        
        [Threading.Thread]::CurrentThread.CurrentCulture = $culture        
        [Threading.Thread]::CurrentThread.CurrentUICulture = $culture        
        Invoke-Command $script    
    }    
    finally {        
        [Threading.Thread]::CurrentThread.CurrentCulture = $OldCulture        
        [Threading.Thread]::CurrentThread.CurrentUICulture = $OldUICulture    
    }
}

Remove-SPSite -Identity $site_url -GradualDelete -Confirm:$False;

$culture = [System.Globalization.CultureInfo]::GetCultureInfo(1033);

Using-Culture -culture $culture -script { 
    New-SPSite -Url $site_url -OwnerAlias $owner -Template "{FEATURE_GUID}#WebTemplateName";
};

Tuesday, 23 November 2010

Programmatically approve a SharePoint workflow task

I know they are several samples available out there, but here is a merge the best parts I found.

This example will show how to notify a sharepoint workflow to move to next step.

Context
I'm creating a custom workflow. I need to notify the wf from outside the process (list event, console app,etc...).

The Workflow
The basic is to create a "fake" task and monitor the OnTaskChange event, then programmatically approve the task from outside the process.


The wf example is pretty simple: create a task, wait for changes then log a message in the workflow history.

Bind the CorrelationToken of the CreateTask1 to a new token on that will be exclusive for this task. Apply the same token to the OnTaskChanged1.

The while loop could be omitted in this example, I left it to suggest that you can add a validation at this point using the while condition.
private void createTask1_MethodInvoking(object sender, EventArgs e)
{
  var ct = sender as CreateTask;
  ct.TaskId = Guid.NewGuid();
  var spTaskProperties = new Microsoft.SharePoint.Workflow.SPWorkflowTaskProperties();
  spTaskProperties.Title = String.Format("{0} Step1", this.workflowProperties.Item.Name);
  ct.TaskProperties = spTaskProperties;
}

No need to bind the OnTaskChanged Invoked event.
The CreateTask1_MethodInvoking provides the minimal infos to create the task, note that the AssignedTo property is left blank.

NOTE: In the feature that deployed the workflow I didn't specify an infopath form.
Therefore if you manually go to the workflow task list and click on the task you will get a message telling you that no xsn form is associated, nothing to worry about we'll do it through code :)

Notifying the workflow
In this example I chose to fire next step of the workflow from a console application.
static void Main(string[] args)
{
  using (var site = new SPSite("http://[site_url]"))
  {
    using (var web = site.OpenWeb())
    {
      // retreive the list where the workflow is running
      var list = web.Lists["myList"]; 

      // find the item with the task to complete (for the example my item's ID is 1)
      var item = list.Items.Cast<SPListItem>().First(c => c.ID == 1);
      
      // find the latest active workflow
      var wfs = site.WorkflowManager.GetItemActiveWorkflows(item).Cast<SPWorkflow>();
      var wfID = wfs.OrderByDescending(c => c.Created).First();

      // fetch workflow tasks
      var wfTasks = item.Tasks;
      
      // find the correct task
      var task = (SPListItem)wfTasks.Cast<SPWorkflowTask>().First(
        c => c.WorkflowId.Equals(wfID.InstanceId)
        && c.Title.Contains("Step1"));

      // build a hashtable with the values to be changed to mark the task as complete
      var ht = new Hashtable();
      ht[SPBuiltInFieldId.Completed] = true;
      ht[SPBuiltInFieldId.PercentComplete] = 1;
      string taskStatus = SPResource.GetString(new CultureInfo((int)task.Web.Language, false), "WorkflowTaskStatusComplete", new object[0]);
      ht[SPBuiltInFieldId.TaskStatus] = taskStatus;

      // alter the task using a trick to prevent task lock issue
      AlterTask(task, ht, true, 5, 100);
    }
  }
}


public static bool AlterTask(SPListItem task, Hashtable htData, bool fSynchronous, int attempts, int millisecondsTimeout)
{
  // check this link for more details: http://geek.hubkey.com/2007/09/locked-workflow.html

  if ((int)task[SPBuiltInFieldId.WorkflowVersion] != 1)
  {
    SPList parentList = task.ParentList.ParentWeb.Lists[new Guid(task[SPBuiltInFieldId.WorkflowListId].ToString())];
    SPListItem parentItem = parentList.Items.GetItemById((int)task[SPBuiltInFieldId.WorkflowItemId]);
    for (int i = 0; i < attempts; i++)
    {
      SPWorkflow workflow = parentItem.Workflows[new Guid(task[SPBuiltInFieldId.WorkflowInstanceID].ToString())];
      if (!workflow.IsLocked)
      {
        task[SPBuiltInFieldId.WorkflowVersion] = 1;
        task.SystemUpdate();
        break;
      }
      if (i != attempts - 1)
        Thread.Sleep(millisecondsTimeout);
    }
  }
  return SPWorkflowTask.AlterTask(task, htData, fSynchronous);
}



And voila!

Thursday, 4 November 2010

SharePoint PeoplePicker as required field

SharePoint's PeoplePicker has pain understanding the AllowEmpty="false" property.
I found all sorts of palliative solutions including complex javascript rubbish (and even JQuery solutions).
The following is the easiest way to achieve client side validation to set the picker as requierd.


[...]
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
[...]
<SharePoint:PeopleEditor ID="PeopleEditor1"
runat="server"
SelectionSet="User"
MultiSelect="true"
MaximumEntities="2"
AllowEmpty="false"
ValidatorEnabled="true"
ValidationGroup="myValidationGroup" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1"
runat="server"
ValidationGroup="myValidationGroup"
ControlToValidate="PeopleEditor1$downlevelTextBox"
ErrorMessage="This Field is required." />

Use a RequiredFieldValidator and set the ControlToValidate to "[ControlName]$downlevelTextBox".

Monday, 25 October 2010

SharePoint Debug Timeout

"The web server process that was being debugged has been terminated by IIS"

How painful is it to be kicked off the process you're debugging?
Having no solution until then I ended up doing ninja speed debugging => look at the values as quick as possible to release the debug mode and not crash IIS7. (I know, I felt sorry for me as well)

This time is over, ladies & gentlemen here is the answer!

1) click "Start", "Run..." type "inetmgr"
2) find the naughty application pool and right click on it -> "Advanced Settings"
3) under "Process Model" section switch "Ping Enabled" to "False"
4) allow your self a 5min coffee/tee break as you will now experience intensive debugging :)


ref: http://msdn.microsoft.com/en-us/library/bb763108.aspx