Extending WSS3 task lists to support recurring reminders - sharepoint

WSS 3.0 will let me send an email to a group when a new task is added to a task list. What I would like to do is to run a weekly task that sends out reminders of tasks due within certain periods, i.e. 2 days, 7 days, 14 days etc. I thought the simplest way would be to build a little C# app that sits on the WS2K3 box and queries the WSS database. Any ideas on which tables I should be checking? More generally is there an overall schema for the WSS3 database system?
If anyone is aware of an existing solution with code please let me know.
Thx++
Jerry

My suggestions:
don't create a console app, create a class that inherits from SPJobDefinition.
set SPJobLockType.Job to this timer, this will grant that the job is executed only once in the whole farm, even if you are running multiple front-end servers
in the, timer job, open the SPSite, SPWeb objects you need, then find the SPList\
Using SPQuery filter out only the items you need - I believe, you will have to filter out the ones where Status!=Complete
Loop through the results collection (which will be of type SPListItemCollection, apply your rules, checking the DueDate and Datetime.Now, send the e-mails
Since a task is simply a SPListItem, it has a Properties property, which is actually a property bag - you can add whatever properties you need. So, add a property My_LastSentReminderDate. Use this property to check if you are not sending too much of "corporate spam" :-)
To install your SPJobDefinition in a SharePoint farm, you can use a PowerShell script. I can give you examples, if needed.
Don't forget to Threading.Thread.CurrentThread.CurrentCulture = Your_SPWeb_Instance.Locale, otherwise date comparisons may not work if the web has a different locale!
EDIT: This is how a typical reminder looks like in my applications:
Public Class TypicalTimer
Inherits SPJobDefinition
Public Sub New(ByVal spJobName As String, ByVal opApplication As SPWebApplication)
'this way we can explicitly specify we need to lock the JOB
MyBase.New(spJobName, opApplication, Nothing, SPJobLockType.Job)
End Sub
Public Overrides Sub Execute(ByVal opGuid As System.Guid)
'whatever functionality is there in the base class...
MyBase.Execute(Guid.Empty)
Try
Using oSite As SPSite = New SPSite("http://yourserver/sites/yoursite/subsite")
Using oWeb As SPWeb = oSite.OpenWeb()
Threading.Thread.CurrentThread.CurrentCulture = oWeb.Locale
'find the task list and read the "suspects"
Dim oTasks As SPList = oWeb.Lists("YourTaskListTitle")
Dim oQuery As New SPQuery()
oQuery.Query = "<Where><Neq><FieldRef Name='Status'/>" & _
"<Value Type='Choice'>Complete</Value></Neq></Where>"
Dim oUndoneTasks As SPListItemCollection = oTasks.GetItems(oQuery)
'extra filtering of the suspects.
'this can also be done in the query, but I don't know your rules
For Each oUndoneTask As SPListItem In oUndoneTasks
If oUndoneTask(SPBuiltInFieldId.TaskDueDate) IsNot Nothing AndAlso _
CDate(oUndoneTask(SPBuiltInFieldId.TaskDueDate)) < Now().Date Then
' this is where you send the mail
End If
Next
End Using
End Using
Catch ex As Exception
MyErrorHelper.LogMessage(ex)
End Try
End Sub
End Class
To register a timer job, I typically use this kind of a script:
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Administration")
[System.Reflection.Assembly]::LoadWithPartialName("Your.Assembly.Name.Here")
$spsite= [Microsoft.SharePoint.SPSite]("http://yourserver/sites/yoursite/subsite")
$params = [System.String]("This text shows up in your timer job list (in Central Admin)", $spsite.WebApplication
$newTaskLoggerJob = new-object -type Your.Namespace.TypicalTimer -argumentList $params
$schedule = new-object Microsoft.SharePoint.SPDailySchedule
$schedule.BeginHour = 8
$schedule.BeginMinute = 0
$schedule.BeginSecond = 0
$schedule.EndHour = 8
$schedule.EndMinute = 59
$schedule.EndSecond = 59
$newTaskLoggerJob.Schedule = $schedule
$newTaskLoggerJob.Update()

Any time you need something in sharepoint that is executed periodically, 99 times out of a 100 you'll need to build a TimerJob. These are scheduled tasks that run inside SharePoint and you can create your own, then using a feature + featurereceiver to actually "install" the timoerjob (definition) and assign it a schedule.
For more info: see Andrew Connell's article on TimerJobs.
P.S. Never query /update the databases related to SharePoint directly! This will make you "unsupported", i.e. if anything happens microsoft will charge (a lot of) money to come and fix it, instead of being able to ask for regular support. (if you are say an MSDN subscriber you get up to 4 free support calls a year).

Don't bother trying to go directly to the database. You will have a very hard time because it's undocumented, unsupported, and not recommended. SharePoint does in fact have a full featured object model though.
If you reference Microsoft.SharePoint.dll (located in the Global Assembly Cache of a machine with SharePoint installed on it) you can access the data that way. The objects you'll want to start with are SPSite, SPWeb, SPList, SPQuery, and SPListItem. All of which you can find very easily on http://msdn.microsoft.com in a search.
Another less-flexible but code-free possibility you could try is creating several different views that include upcoming tasks then via the GUI set up an alert for when items are added to that view.

Related

Sharepoint task list and Outlook sync

I am trying to sync Sharepoint task list with Outlook. When the users connect the task list to outlook, the task for all users are visible in outlook. Rather than applying filtering in Outlook, can I provide a filtering at the source itself? There are considerable number of users for my application, it wouldn't be good to ask all users to apply filters on their own.
Any other suggestions?
Thanks.
I`v asked the same question: Sync list with outlook only with items in current view.. In this case it was possible to use stssync protocol to do whatever you want. It takes much effort (luckily someone already wrote an implementation)
But there was another solution i ended up using - implementing a wrapper for Lists.asmx webservice and rewriting outlook requests (by using custom Global.asax file) to use this new webservice instead of Lists.asmx, that only queries specific view in a list.
if (ctx.Request.UserAgent.Contains("Microsoft Office Outlook") && path.ToLower().IndexOf("_vti_bin/lists.asmx") >= 0)
{
ctx.RewritePath("/_layouts/OutlookLists.asmx");
}
I'm not sure you would want a solution like this. If you do, you may ask and i may publish the solution source for the webservice, however i'm not using this webservice myself anymore. And you could use it as a draft, not a production ready code.
The source has been published on CodePlex.
Regarding to the script problem
I don't know why list id isn't being replaced by view id. I tried to run the function within script console (F12 for IE8/9)
>> var menuItems = document.getElementsByTagName('ie:menuitem');
for (var i = 0; i < menuItems.length; i++) {
itm = menuItems(i);
if (itm.id.match('OfflineButton') != null) {
console.log('listName:' + ctx.listName.toLowerCase() + 'viewName:' + ctx.view.toLowerCase());
if (ctx != null && ctx.listName != null && ctx.view != null) {
console.log('Inside if block');
//Replace listId to viewId being used so outlook will query only items in current view.
//Must have custom web service in place to handle that request, because it iwll not work OOTB.
console.log("Before: " + itm.onMenuClick);
itm.onMenuClick = itm.onMenuClick.replace(ctx.listName.toLowerCase(), ctx.view.toLowerCase());
console.log("After: " + itm.onMenuClick);
break;
}
}
}
LOG: listName:{fe89e809-7de4-4f43-9bc2-7e8ce6624ed0}viewName:{7364a843-c7f2-47d8-b4a3-5dc7381b6248}
LOG: Inside if block
LOG: Before: javaScript:ExportHailStorm('tasks','https:\u002f\u002fserver\u002fsapulces\u002fdarbu_parskata','{fe89e809-7de4-4f43-9bc2-7e8ce6624ed0}','Uz\u0146\u0113muma darbu p\u0101rskata sapulce','Uzdevumi','\u002fsapulces\u002fdarbu_parskata\u002fLists\u002fUzdevumi','','\u002fsapulces\u002fdarbu_parskata\u002fLists\u002fUzdevumi');
LOG: After: javaScript:ExportHailStorm('tasks','https:\u002f\u002fserver\u002fsapulces\u002fdarbu_parskata','{7364a843-c7f2-47d8-b4a3-5dc7381b6248}','Uz\u0146\u0113muma darbu p\u0101rskata sapulce','Uzdevumi','\u002fsapulces\u002fdarbu_parskata\u002fLists\u002fUzdevumi','','\u002fsapulces\u002fdarbu_parskata\u002fLists\u002fUzdevumi');
As you can see, the function argument (third one) has been replaced with a view id instead of list id.
Don't forget to remove console.log statements before deploying, because if IE doesn't have web developer tools, javascript will crash there.
Were these tasks created from a workflow? this is a known issue with SharePoint 2007.
http://social.technet.microsoft.com/Forums/en/sharepointadmin/thread/64b3b124-085c-4d8e-8e85-8bd20736e0e7
http://blah.winsmarts.com/2007-4-SharePoint_2007__Fine_grained_permission_control.aspx
You could try setting the read/edit permissions to "only their own", but i think that breaks approval/alerts from working
I believe the problem is fixed in SharePoint 2010, i think tasks get created with fine-grained permissions per task.

c# call multiple webservices at same time

Here is the deal, i have a website that is required to search from multiple webservices, then join all the results returned from webservices and display them mixed. I've done the code for search a single place at one
WsPesquisa pesq = new WsPesquisa();
IEnumerable<Objecto> Resultados = pesq.PesquisaObjecto("URL TO SEARCH", "TEXT TO SEARCH");
now i need to use threads to search in multiple places at once but having doubts how to do so.
Can someone please provide a thread sample to call multiple times the code i've used above and then join the results from all threads in a single List of Objectos?
Thanks in advance
One way to do this is to use a standard LINQ query, and use PLINQ to parallelise it.
Assuming you have your query stored in query, a list of the websites you want to search stored in variable called sites, and you have a method SearchSite(string query, string site) that runs the search against a single site, the following should do the trick:
var searchResults = from site in sites.AsParallel()
select SearchSite(query, site);
var resultList = new List<object>();
foreach (var searchResult in searchResults)
{
// process result
resultList.Add(searchResult);
}
This assumes the search query is the same for each site, so:
AsParallel() indicates that you want your LINQ query to be run in parallel
select SearchSite(query, site) - takes your query and runs the SearchSite method on it
PLINQ takes care of waiting for all the results to come in, so you can just process them in a regular for loop

Critical Problem with Sharepoint Timer Job Properties

Some minutes ago I tried to create a time job
A added some properties like
this.Properties.Add("fileName", fileName);
this.Properties.Add("username", new NetworkCredential("username", "passworD");
After updating the job a get a critical error in the Timer Job list of the Central Administration occured.
The platform does not know how to deserialize an object of type System.Net.NetworkCredential. The platform can deserialize primitive types such as strings, integers, and GUIDs; other SPPersistedObjects or SPAutoserializingObjects; or collections of any of the above. Consider redesigning your objects to store values in one of these supported formats, or contact your software vendor for support.
Now Im unabled to delete or retract the job with SPJobdefinition's Delete() method or other classes within the SPObject model.
Ok. I got it.
I deleted the corresponding object in the SharepointConfigDatabase.dbo.Objects table

SharePoint 2007: How to check if a folder exists in a list using web services?

Using SharePoint 2007 webservices or even Webdav, how can I check if a folder exists in a list (not document library) in SharePoint.
I would also like to check for subfolders...
Anyone have any idea on how this is done? I've asked Microsoft, and their official stance is that Microsoft provide no documentation on this. so any help will be most welcome...
Thanks in advance...
I have this code that creates a folder, but not sure how to modify it to check if the folder exists, also not even sure if this will work with sub folders...
private void CreateFolderUsingWebService(string listName, string folderName)
{
//Check Databox Folder Exists
//string folderAddress = siteAddress + #"/lists/" + listAddress + #"/" + folderName;
//wsDws.CreateFolder(folderAddress);
var doc = new XmlDocument();
XmlElement batch = doc.CreateElement("Batch");
string item = "<Method ID=\"1\" Cmd=\"New\">" +
"<Field Name=\"ID\">New</Field>" +
"<Field Name=\"FSObjType\">1</Field>" +
"<Field Name=\"BaseName\">" + folderName + "</Field></Method>";
batch.SetAttribute("ListVersion", "1");
//batch.SetAttribute("ViewName", "{GUID of View, including braces}");
batch.InnerXml = item;
wsLists.UpdateListItems(listName, batch);
}
Ok - this info might help the next SharePoint developer:
The function above works, and will even create a directory structure. BUT you need to pass the list name, not the list URL, this means if you localize your code, you need to pass the localized list name to the function.
I didn't bother adding a check for ifExists, because it seems to NOT create duplicates or fail if the directory already exists. I know this isn't a great solution, but I just don't have 2-3 weeks to research how to do it properly, so if you have any suggestions, comments are welcome.
Lastly any Microsoft representation reading this - might want to consider why there isn't any really good documentation on this with how to's from MS? Mmmmm
I went as far as downloading the MOSS Web Services SDK, and it contains 1 very vague example of how to use 1 function in the Lists web service, this simply is not enough information for those of us trying to put together robust solutions in MOSS. We need way more documentation please...

COM Integration from ALBPM - Cannot find IDispatch for '{00020906-0000-0000-C000-000000000046}'

I am trying to use the Office COM components in order to create Word and Excel documents. Unfortunately I can not achieve this because I am getting an error.
Cannot find IDispatch for
'{00020906-0000-0000-C000-000000000046}'
in module
'{00020905-0000-0000-C000-000000000046}',
v8.3
I tried reinstalling Office, my application (ALBPM) and my interface (combsvc) but it is not working.
I want to know how can I install IDispatch, or how can I know if it is installed in the correct module. Some times the error says:
Cannot find IDispatch for
'{000209FF-0000-0000-C000-000000000046}'
... instead of
00020906-0000-0000-C000-000000000046
The code I'm using generate these errors is:
wordAppl.visible = false
wordDocs = wordAppl.documents
contratoTemplate = "C:\\albpmFiles\\mandatory\\aTemplate.doc"
// .doc template
convenioTemplate = "C:\\albpmFiles\\mandatory\\ConvenioModificatorio.doc"
// .doc template
saveContrato = "C:\\albpmFiles\\temp\\"
// where to save.
saveConvenio = "C:\\albpmFiles\\temp\\"
contratoName = "NewContact.doc"
wordDoc = open(wordDocs, fileName : contratoTemplate)
bookmark = item(wordDoc.bookmarks, index : "atrDescripcion")
insertAfter bookmark.range
using text = instSolicitud.atrDescripcion
bookmark = item(wordDoc.bookmarks, index : "atrObjProveedor_atrNombre")
insertAfter bookmark.range
using text = instSolicitud.atrObjProveedor.atrNombre
bookmark = item(wordDoc.bookmarks, index : "atrObjProveedor_atrDireccion")
insertAfter bookmark.range
using text = instSolicitud.atrObjProveedor.atrDireccion
filename = saveContrato + contratoName
end
// Extras - Fin
saveAs wordDoc
using fileName = filename
Any information you have about the IDispatch, or these registry entries, well be very appreciated, even if you can tell me where to find more info about this.
Thanks a lot.
Daniel.
From the error you get I assume that you are using Word 2003.
Have you made sure that the COM brigde service is correctly installed and running?
combsvc -install
combsvc -start
will register combsvc as service and then start it.
Please also have a look at the example for Word at the bottom of page 150 in the ALBPM Reference Guide.
The fact that it is sometimes working and sometimes could be an issue with ALBPM. Are you using the latest version and updates?
Another option - and quite frequent problem with Word automation - would be that the automated instance of Word is displaying a modal dialog box and is waiting for user interaction. You can switch of the display of modal dialogs by setting
Application.DisplayAlerts = 0
However, this will unfortunately not prevent all popups from being displayed.
Is there actually an instance of Word started? If so, can you make the Window visible and see if documents can be opened or if there is a popup blocking the application?
Daniel,
I'm taking a stab in the dark here. It looks like you're using BEA systems Aqualogic BPM which I have a feeling is a Java based tool. From digging about it looks like combsvc is actually a COM bridge service to allow ALBPM to speak to COM from Java:
http://edocs.bea.com/albsi/docs60/studio/index.html?t=studio/catalog/catalog_component/COM/c_COM_Bridge.html
I'm thinking this is your point of failure.
About your question on IDispatch, you don't actually install IDispatch. IDispatch is a interface used by COM to expose objects, methods and properties to late bound COM automation clients such as scripting languages (e.g. ASP or VBScript). It's part of the infrastructure of COM, if this was broken you'd see lots more problems with your machine.
I'd probably advise popping a question in here:
http://forums.oracle.com/forums/forum.jspa?forumID=560
To inspect installed COM Interfaces on your PC i suggest you download oleview.exe which is part of the Windows 2003 resource Kit
I actually have {00020906-0000-0000-C000-000000000046} but also no IDispatch interface and get a "Class not registered" error when trying to create an instance of it. My home PC doesn't have office installed just the Office tools which is most likely the cause.
In the past when automating Office Applications i was always able to talk to a version independent ProgID's such as "Excel.Application". Are you sure your referencing the right COM Objects ? Check it out in oleview or give us some more code to munch on :)
The code I am using is this, but I can not even see the first log, so I assume thereĀ“s an error with the conection, not with the code
wordAppl.visible = false
wordDocs = wordAppl.documents
contratoTemplate = "C:\\albpmFiles\\mandatory\\aTemplate.doc"
// .doc template
convenioTemplate = "C:\\albpmFiles\\mandatory\\ConvenioModificatorio.doc"
// .doc template
saveContrato = "C:\\albpmFiles\\temp\\"
// where to save.
saveConvenio = "C:\\albpmFiles\\temp\\"
contratoName = "NewContact.doc"
wordDoc = open(wordDocs, fileName : contratoTemplate)
bookmark = item(wordDoc.bookmarks, index : "atrDescripcion")
insertAfter bookmark.range
using text = instSolicitud.atrDescripcion
bookmark = item(wordDoc.bookmarks, index : "atrObjProveedor_atrNombre")
insertAfter bookmark.range
using text = instSolicitud.atrObjProveedor.atrNombre
bookmark = item(wordDoc.bookmarks, index : "atrObjProveedor_atrDireccion")
insertAfter bookmark.range
using text = instSolicitud.atrObjProveedor.atrDireccion
filename = saveContrato + contratoName
end
// Extras - Fin
saveAs wordDoc
using fileName = filename

Resources