AWG Blogs

Wednesday, January 11, 2012

SharePoint Workflows on 64bit server

This is a quick start on how to get a functioning workflow compiled, installed, and running to completion. It will simply log a message to the workflow history; that's it.

Assumes Visual Studio 2008 is installed on Windows Server 2003 64bit with WSPBuilder.

File, New Project, WSPBuilder Project with Workflow

Right-click project in Solution Explorer and choose Workflow, Sequential Workflow (code) - note: I tried using Sequential Workflow with definition expressed as Xaml and user code in a separate code file, but I got errors such as "RunWorkflow: System.Workflow.Activities.EventDeliveryFailedException: Event "OnWorkflowActivated" on interface type "Microsoft.SharePoint.Workflow.ISharePointService" for instance id "405ff0ae-d08d-41b8-ab69-be69ae4ccde1" cannot be delivered. ---> System.Workflow.Runtime.QueueException: Event Queue operation failed with MessageQueueErrorCode QueueNotFound for queue 'Message Properties Interface Type:Microsoft.SharePoint.Workflow.ISharePointService Method Name:OnWorkflowActivated CorrelationValues: '. "

Drag a onWorkflowActivated control onto the designer
Set its CorrelationToken property to anything, e.g. "mytoken"; then expand CorrelationToken and for OwnerActivityName, choose Workflow1 (e.g.)

Drag a logToHistoryListActivity onto the canvas under the onWorkflowActivated1 control.
Double click its MethodInvoking property, and enter the following code into the body of the method: var workflowProperties = new Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties();

this.logToHistoryListActivity1.HistoryOutcome = "Here is a comment " + workflowProperties.InitiationData;


Build Solution

Right click property and choose WSPBuilder, Copy to GAC

Create a folder called MyLoggingWorkflowFeature under C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES\

Create two xml files in this folder:
Feature.xml:
<?xml version="1.0" encoding="utf-8"?>

<Feature Id="genkey id here"
Title="Workflow to create a log something to history"
Description="Logs something to history"
Version="12.0.0.0"
Scope="Site"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="workflow.xml" />
</ElementManifests>
</Feature>

workflow.xml:

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

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Workflow
Name="New Task Workflow"
Description="Creates a new task"
Id="yourId here from genkey tool"
CodeBesideClass="NewTaskWorkflow.Workflow3"
CodeBesideAssembly="NewTaskWorkflow, Version=1.0.0.1, Culture=neutral, PublicKeyToken=yourkeytokenhere"
></Workflow>

</Elements>

Thursday, December 29, 2011

Style "Newsletter, no line" and the DVWP

Tried converting (via "convert to XSLT Data View" in Sharepoint Designer 2007) a ListViewWebPart whose style was set to "NewsLetter, no line" and it would not work. The resulting XSLT produced mangled HTML tables. So trying my luck I tried converting the plain Newsletter style using the same approach. This had better results, although, there was an odd table cell on the left, which appeared due the condition ddwrt:GetVar('NumColumns')='1' being true. This is odd, because NumColumns in my list view should be four not one. Further searching the code showed that NumColumns is set as the string length of "Columns," i.e. ddwrt:SetVar('NumColumns', string(string-length(ddwrt:GetVar('Columns')))) and Columns is set to a lone period (dot).
After analyzing the CAML of the LVWP, which should be the basis for the XSLT, I found that in the CAML version, the assignment of Columns is set in a Fields element, which according to the View Schema on MSDN contains a loop over the view fields. This explains the difference: At the end of the loop, Columns is set to "....".
So what I did to patch the XSLT was simply replace the single dot with four dots in the assignment of Columns, which took care of the odd cell, and probably some other anomalies.
There is still some more fixing up to do, e.g.:
- delete the header cell for the field that displays on its own line
- setting column headers to be sortable.
- comment out the ms-alternating class assignment because it's applied unevenly to tr tags, unless you want to convert to "No line" (instructions below)
- explicitely set the class for content cells. Designer sets them incorrectly, e.g. TD Class="{$IDACAVMC}, which is a variable that is based on a List View CAML property, which no longer applies, apparently. So, icon cell would be ms-vb-icon; title cell would be set to ms-vb-title, user would be ms-vb-user; and the text cell on its own row has already been hard coded for us by the Designer ListViewWebPart to DataFormWebPart conversion process.

Now, to transform this into a "Newsletter, no line" style, do the following:
- comment out the TR tag containing the TD class="ms-nlline"
- replace the html encoded starting TR tags (all <xsl:text disable-output-escaping="yes">&lt;tr&gt;</xsl:text>) with actual TR tags; note: must add the closing TR tag, because it was not added during conversion. To find out where to insert the closing TR for the main row, select the row in SPD, backtrack to the last <xsl:choose> that is directly above the two repeated field name comments for the field that will appear on its own line. Place the closing TR above the two field name comments.
- add the conditional ms-alternating attribute to those TR tags.
Note: There should be two xsl:text replacements (the second one being in the vicinity of "ms-nlline" below the @Remarks=) and three conditional (if statements)
ms-alternating attributes added based on a four column table with one field on its own line: two are added to the replaced TRs, and one added to the TR above the removed ms-nlline row.

Sunday, December 25, 2011

Copying Discussion Items to Document Libary

The Requirement is to copy discussion items to another document library.

Code ideas borrowed from: http://stackoverflow.com/questions/468469/how-do-you-upload-a-file-to-a-document-library-in-sharepoint, http://sharepoint.stackexchange.com/questions/20216/iterate-through-discussion-list, http://www.sharepoint-tips.com/2011/11/event-handler-to-archive-items-when.html

Sample code containing the general idea:
using System;
using System.Runtime.InteropServices;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;

using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;

namespace WebPart3
{
[Guid("26d38752-d4ce-456e-88f6-496d213627d4")]
public class WebPart3 : System.Web.UI.WebControls.WebParts.WebPart
{
string msg1;
Button saveTitle;
TextBox newTitle;
Label label;

public WebPart3()
{
SPSite site1 = SPContext.Current.Site;

msg1 = "(1.03)" ;

}

public void saveTitle_click(object sender, EventArgs e)
{
//this.Title = newTitle.Text;
string tempprint = "";
SPWeb myweb = SPContext.Current.Web;
SPListCollection lists = myweb.Lists;
SPList mylist;
SPListItem sli;
string labeltext = "";
SPList targetList;

try
{

// SPList targetList = lists["Discussion Archive"];
// SPListItem newItem = targetList.Items.Add();

mylist = lists[newTitle.Text];
targetList = lists["Discussion Archive"];
// targetList = lists["DiscussionDocs"];

// SPQuery query = new SPQuery();
//query.RowLimit = 10;
// SPListItemCollection posts = mylist.GetItems(query);

//this. .SaveProperties = true;
try
{
labeltext += " entering foreach";
foreach (SPListItem item in mylist.Folders)
{
SPListItem newItem = targetList.Items.Add();
//copy the list item to the target
foreach (SPField f in item.Fields)
{
if (!f.ReadOnlyField && newItem.Fields.ContainsField(f.InternalName))
newItem[newItem.Fields.GetFieldByInternalName(f.InternalName).Id] = item[f.Id];
}
//copy "special" read only fields that can be written to
newItem["Created By"] = item["Created By"];
newItem["Modified By"] = item["Modified By"];
newItem["Modified"] = item["Modified"];
newItem["Created"] = item["Created"];
newItem.SystemUpdate(false);
CopyAttachments(item, newItem);


labeltext += item["Body"] + ",";
sli = item;
tempprint = (string)sli["Created By"];
}
labeltext += " existing foreach";
try
{
this.Title = "yo" + mylist.ItemCount + " by: " + tempprint;

}
catch (Exception ex)
{
this.Title = "Error3: " + ex.Message;
label.Text = ex.StackTrace;

}
}
catch (Exception ex)
{

this.Title = "Error2: " + ex.Message;
label.Text = ex.StackTrace;
}
}
catch (Exception ex)
{
this.Title = "Error: " + ex.Message;
label.Text = ex.StackTrace;
}
label.Text += labeltext + "the end";
// this.SetPersonalizationDirty();
}

private void CopyAttachments(SPListItem sourceItem, SPListItem targetItem)
{
SPWeb web = SPContext.Current.Web;

SPFolder myLibrary = web.Folders["DiscussionDocs"];
Boolean replaceExistingFiles = true;

try
{
//get the folder with the attachments for the source item
SPFolder sourceItemAttachmentsFolder = sourceItem.Web.Folders["Lists"].SubFolders[sourceItem.ParentList.Title]
.SubFolders["Attachments"].SubFolders[sourceItem.ID.ToString()];
foreach (SPFile file in sourceItemAttachmentsFolder.Files)
{
byte[] binFile = file.OpenBinary();
targetItem.Attachments.AddNow(file.Name, binFile);
this.label.Text += file.Name + " attached to target ";
SPFile spfile = myLibrary.Files.Add(file.Name, binFile, replaceExistingFiles);
SPListItem newfileitem = spfile.Item;
newfileitem["Created"] = sourceItem["Created"];
newfileitem["Modified By"] = sourceItem["Modified By"];
newfileitem["Modified"] = sourceItem["Modified"];
newfileitem["Created By"] = sourceItem["Created By"];
newfileitem["Title"] = sourceItem["Title"];
//newfileitem.SystemUpdate(false);
newfileitem.Update();

myLibrary.Update();
}
}
catch (Exception ex)
{
this.Title = "Error: " + ex.Message;
}
}

protected override void CreateChildControls()
{
base.CreateChildControls();

// TODO: add custom rendering code here.
label = new Label();
label.Text = "Hello World" + msg1;
this.Controls.Add(label);

//Create text box
newTitle = new TextBox();
newTitle.Text = "myxmdiscuss";
this.Controls.Add(newTitle);

//Create Button
saveTitle = new Button();
saveTitle.Text = "Set Web Partt Title";
saveTitle.Click += new EventHandler(this.saveTitle_click);
Controls.Add(saveTitle);
}
}
}


And here's the Powershell:


$error.clear()

[System.reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

$site = new-object Microsoft.SharePoint.SPSite("https://site")
$web = $site.OpenWeb()

$lists = $web.Lists;

$lista = $lists['Team Discussion']
$listb = $web.Folders['MyDocLib']

foreach ($item in $lista.Folders)
{
Try
{
$sourceItemAttachmentsFolder = $item.Web.Folders["Lists"].SubFolders[$item.ParentList.Title].SubFolders["Attachments"].SubFolders[$item.ID.ToString()];
foreach ($file in $sourceItemAttachmentsFolder.Files)
{
$binfile = $file.OpenBinary()
"Attached to target " + $item["Title"]
$spfile = $listb.Files.Add($file.Name, $binfile, 1)
$newfileitem = $spfile.Item
foreach ($f in $item.Fields)
{
if (!$f.ReadOnlyField -and $newfileitem.Fields.ContainsField($f.InternalName))
{
$newfileitem[$newfileitem.Fields.GetFieldByInternalName($f.InternalName).Id] = $item[$f.Id];
}

$newfileitem["Created By"] = $item["Created By"];

$newfileitem["Created"] = $item["Created"];

$newfileitem["Modified By"] = $item["Modified By"];

$newfileitem["Modified"] = $item["Modified"];

$newfileitem.Update();

}
}

}
Catch
{

"caught a system exception"
}

$error

}

Note: Minimum permissions to execute the ps1 is local server administrator + Site owner.

Friday, December 9, 2011

~SiteCollection in ParameterBinding

Here's another workaround. You would like to assign the ~SiteCollection token value (or any token) to a ParameterBinding so that your XSLT can see that value and use it say in image paths, etc.

After finding a tip <a href="http://stackoverflow.com/questions/609943/dynamically-set-the-defaultvalue-of-a-parameterbinding-in-a-dataformwebpart">here</a>, the solution may be as follows.

- Put a hidden html input control with a runat=server attribute in your content place holder, e.g. <input id="siteColVal">" type=hidden name=siteColVal runat="server">
(Note, an asp:TextBox control did not work for me).
- Add a parameterbinding like: <parameterbinding location="Control(siteColVal, value)" name="SiteColRoot" defaultvalue="">
- then of course a <?xml:namespace prefix = xsl /><xsl:param name="SiteColRoot">theooroot</xsl:param> in your XSLT

Worked for me!

Thursday, December 8, 2011

Sort DataFormWebPart by "Order" Field

The only way I know to do this SPD 2007 is through some manual steps. BTW, the "order" field is determined by the action taken by user, which is made available via the "Allow users to order items in this view?" option for the view.

There are actually a couple of ways:

- Drag your list from the Web Part List from the Web Parts Task pane onto a web part zone.
- Then right click on web part and "Convert to XSLT Data View" which will convert the ListViewWebPart to DataFormWebPart with all the desired attributes (such as sorting) preset.

Alternately, if you created the web part as a Custom List Form from the start (i.e. with XSLT embedded), then you can edit the CAML manually.

- First open Common Data View Tasks and set it to sort by a common field such as ID.
- Then edit the SPDataSource SelectCommand, by changing "<OrderBy><FieldRef Name="ID"" to "<OrderBy><FieldRef Name="Order""

Tuesday, November 22, 2011

Deploy List Definition

- Start SharePoint Solution Generator 2008 (comes with Visual Studio 2008 Extensions)

- Select List Definition


- click Next; if you get an error "VSeWSS Service Error: No SharePoint Site exists at the specified URL", you can ignore and click OK


- Then enter the URL, next, select the list and finish


- Open the resultant project in Visual Studio 2008 and Build - Package solution.


- Run the generated setup.bat from command line


- You may get an error if the current server does not have the Windows SharePoint Services Web application started, if so start it, and try again.


- Ignore the errors (which you might get if this is a farm with more than one web front end) concerning feature activation as we will deploy and activate next


- Open Solution management in Central Administration and Deploy the new solution (wsp file)


- another option is from the command line using the allcontenturls flag. Note the setup.bat sets the -local flag, which only applies to single server farms. However, you can use the -local flag to deploy to only the local WFE, if global deployment isn't working, e.g. because some WFEs are down.

If you intend to use the -local flag, you will need to edit setup.bat and add the following to the LDeploy section after the deploysolution operation



echo installing feature FeatureName ...


"%SPAdminTool%" -o installfeature -name "FeatureName"


- Now you can activate the lists included with the solution individually from the command line. You can verify that the solution was deployed by checking under C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\ for the names of your lists


- To activate the list/feature on a given site, do: "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\bin\stsadm.exe" -o activatefeature -id [get the id from the setup.bat file] -url http://urlofyoursite:port





That's it! Now the list is ready to be selected during list creation.





As far as activating the list/feature on all sites, there are some powershell solutions out there, e.g. http://bable.cybermarshall.com/2009/01/17/using-powershell-to-activate-a-feature-across-all-sharepoint-2007wss-30-sites-and-subsites/


(haven't tried it yet)







Sunday, November 20, 2011

Group Email in MOSS

There are a couple of gotchas in Sharepoint 2007 you have to watch for when configuring group emails.

First, if you are trying to configure alerts to an AD security group email, make sure that the security group appears under the permissions for the list, either individually or in a group. Then you can create the alert by replacing your email with that of the security group. Although, at this time, I wouldn't know where to find this alert, if I wanted to delete it.

Secondly, there a few things to watch for when trying to send an email to a Sharepoint group. First you have to email enable the sites; do it in CA under Operations, Incoming Email Settings. Then in your group's settings, create the e-mail distribution list. After submitting you will notice below "Distribution list e-mail address:" a message "Distribution list status: Creation request pending." This means you must approve the request in CA, Operations, Approve/reject Distribution Groups. When that's done, you can email the group. However, what I had to do was verify the actual email address used as shown in Active Directory Users and Computers under the OU where Sharepoint created the distribution group. In my case, the email as shown in AD was based on the domain, i.e. mygroup@mydomain.com. Whereas in Sharepoint in the group settings, was shown mygroup@server.mydomain.com. Make sure the Windows SharePoint Services Timer service is running or else the email you send to mygroup@mydomain.com (e.g. emailing it from Outlook) may never be sent.

There is still a problem with regard to adding this group email to an email action in a Sharepoint Designer built workflow. The email does not send. The NDA email contains "You do not have permission to send to this recipient." I have tried removing connection and relay restrictions in the SMTP virtual server in Exchange System Manager. I also tried configuring Sharepoint to send via a local SMTP server that relays to Exchange. But I get the same NDA error. I have had no luck getting group email to work in the three-state workflow as well, so maybe it's related to group functionality. If I select the group from the Select Users picker in Define E-mail Message SPD workflow, I do not get an NDA, but no email is sent. If I type in the group's email address, as it appears in AD, then I get the NDA. If I use the long form email address, no email is sent, nor an NDA.