Molnar Tibor

blog

  Home  |   Contact  |   Syndication    |   Login
  21 Posts | 10 Stories | 35 Comments | 2 Trackbacks

News

Archives

Post Categories

Image Galleries

Wednesday, July 30, 2008 #

When registering the Windows Rights Management Services (RMS) in Central Administration in MOSS, I received the "The required windows rights management client is present but the server refused access. IRM will not work until the server grants permission", even if I followed the 'To add SPS-SRV to the RMS Certification Pipeline' step from 'Deploying Windows Rights Management Services with Microsoft Office SharePoint Server 2007 Step-By-Step Guide'.

Solution: the Central Administration web application looks up in Active Directory the registered RMS service connection point and tries to access the ServerCertification.asmx web service which is in the C:\Inetpub\wwwroot\_wmcs\Certification folder. Give to the account of the Cenral Administration's application pool Read&Execute rights on the ServerCertification.asmx file.


When a new timer job is created for Sharepoint, it has to be programatically installed in a SPWebApplication.JobDefinitions of type SPJobDefinitionCollection. If the timer job have to receive some settings during run time, one option would be to use the .Net application configuration feature.

First you might think to copy the configuration section from the app.config of the dll where the timer job resides into the web.config of the web application where the timer job is registered. The first catch: the timer job is loaded by the timer service, which is the 'Windows SharePoint Services Timer' Windows service on the web front end servers and runs the "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN\OWSTIMER.EXE" executable, so we have to configure this service.

This can be done by creating the OWSTIMER.EXE.config file near the exe file, and put there the timer job configuration section.

Don't forget to restart the timer service :-).


Sunday, July 27, 2008 #

I don't enter in the details of how to install a timer job. It you are interested, take a look here http://www.andrewconnell.com/blog/articles/CreatingCustomSharePointTimerJobs.aspx .

After doing the coding, during the testing, I had 2 issues:

  1. During feature activation I had: 'System.Data.SqlClient.SqlException: The EXECUTE permission was denied on the object 'proc_putObject', database 'Intranet_Config', schema 'dbo''. After doing a profiling on the database, I saw that the SPJobDefinition.Update() code is executed with the web application's application pool account. This account has to have access to the configuration database. I did this by setting the necessary rights in SQL Management Studio.
  2. After doing the thing above, the SPJobDefinition.Delete code throws a weird exception: Access to the path 'C:\Documents and Settings\All Users\Application Data\Microsoft\SharePoint\Config\360c4621-fccb-4c2a-9182-b3c75ae80cf3\cache.ini' is denied. Solution: give full control to the WSS_WPG user on the folder where the cache.ini file is searched.

This happened on a development machine, simple farm installation on a single machine. Hope this helps.

 


Monday, June 02, 2008 #

WSS (Windows Sharepoint Services) 3.0 simple installation uses the Windows Internal Database (for the configuration database, contrent databases). This database doesn't have size limitation. When Search Server Express is installed without having a previous WSS 3.0 installation to extend, WSS 3.0 is installed b by default (Search Server is based on WSS 3.0) but also SQL 2005 Express is installed and the configuration and content databases will be hosted in SQL Express. There is one issue here: SQL Express has a 4 GB limitation.

If you need more space for you content databases, a new WSS 3.0 web application can be created from Central Administration with the content database hosted in the Windows Internal Database. This way Search Server will use SQL Express 2005 and one or more webs will use the Windows Internal Database.


Saturday, May 24, 2008 #

Versioning of Workflows with InfoPath forms in MOSS is not supported out of the box. How to do it is described in Dan Herzog's blog ( https://blogs.pointbridge.com/Blogs/herzog_daniel/Pages/Post.aspx?_ID=4).

I would add a few words about how to handle the Info Path forms versioning. In the workflow.xml file the namespaces of the InfoPath forms are referenced using Task{Nr}_FormURN elements. From one version of a workflow to another not necessarily all forms will change. The forms which are not changed from one workflow version to another can be taken out from the installation procedure. They are already were saved in the form templates list when the previous versions of the workflow were deployed, you can reach them by Central Adminisration -> Application Management -> Manage Form Temlates. The changed forms can be versioned using their namespace. Changing an InfoPath form namespace is not straightforward Just changing it from the properties menu will not work. Here are the steps I use:

  • save the Info path document as source file
  • close the form
  • search for the 'my' namespace in the manifest.xsf
  • replace that namespace in all the source files from the source files with the new namespace (basically the version number will differ in between names)
  • right click manifest.xsf -> Design
  • Save As -> owerwrite original xsn file set the path to the code publish the doc get the ID of the PUBLISHED doc and put in workflow.xml

The name of the new version of a form also should be changed because will be installed in the Form Templates galery and the names are requested to be unique. I use something like NewEmployeeDatav1.0.xsn, NewEmployeeDatav1.1.xsn etc.


Saturday, March 01, 2008 #

Context
In a production environment we started to receive, apparently randomly this error message. We have a complex workflow using InfoPath forms developed in Visual Studio, we applied multiple patches by regac-ing new versions of the workflow assemblies, retaining of course the fully qualified names of the assemblies. Also we use a test environment, where regression testing was done by dedicated test engineers.

Shooting in the dark
We found this blog http://geek.hubkey.com/2007/09/locked-workflow.html, which suggested to set the task item workflow version attribbute to 1 (task[SPBuiltInFieldId.WorkflowVersion] = 1;). It solved only temporary the problem, the following access to the task locked again the workflow.
Digging into the sharepoint assemblies, I found out that in Microsoft.SharePoint.dll assembly in the Microsoft.SharePoint.Workflow.SPWinOEItemReceiver class in the ItemUpdating is created the exception causing the "This task is currently locked by a running workflow and cannot be edited" message:

properties.ErrorMessage = SPResource.GetString("WorkflowTaskLocked", new object[0]);

The exception was thrown if the "WorkflowVersion" property of the task created by the workflow was not equal with the value of the PBuiltInFieldId._UIVersion property. In the same method we have:

properties.AfterProperties["WorkflowVersion"] = (int) itemById[SPBuiltInFieldId._UIVersion];

This doesn't make too much sense, the workflow version is set to the the ui version of the task item. The UI version is equal with (512 * major version + minor) version and beacuse in the task list we don't have minor versions, then the ui version is 512 * the version number. I created an ItemUpdating event handler on the task list.

For the from workflows not having this problem (receiving the error message above) the event handler was called two times, first time from the due to the postback from the client browser and second time by the workflow persistence job having elevated privileges. For the workflows having the problem, the second call was not done immediatelly, it was done only on any following user postback by submitting or saving an Info Path form attached to any workflow instance. More than that, we had in the workflow an OnTaskChanged Sharepoint Workflow activity, for which the 'invoked' event was not fired. It was very confusing.

The lucky guess
After hours of digging I restored the workflow assemblies to an older version. Suddenly, some workflows started to run!

In the Wofklows table in the Sharepoint content database, each workflow has a corresponding row, and one of the columns contains the serialisation of that workflow, I think it's a binary serialisation and the rule of thumb is that we deployed patches were the new version of the workflow classes didn't have anymore a 100% binary serialisation match with the already running workflows. We didn't had these problems in the test environments because all the worklows were newly created.

Conclusions

  • the message "This task is currently locked by a running workflow and cannot be edited" didn't suggested at all what was the root cause of the problem, it was totally confusing
  • don't apply patches to the workflow assemblies only if you are sure that the binary serialisation will be 100% compatible with the prevoius versions from the workflow assemblies (no attributes are added or removed from classes etc.)
  • leave in the test environment workflows running from previous versions of patches to catch this kind of exceptions
  • if is possible, version you workflows

Tuesday, February 19, 2008 #

I was playing around with SharePoint Services 3.0 to build a demo site for a customer. I had a document library having the 'Require documents to be checked out before they can be edited? ' set to true. When I started a workflow manually on a document, I got 'Error ocurred', form the logs: 'Unexpected Microsoft.SharePoint.SPException: The file "abc.docx" is not checked out.  You must first check out this document before making changes.'.

Fine, I checked out a doc and tried to start the workflfow again. Now I got in the browser an alert: 'The workflow requires that the document is checked in. You must first check this document in and then start the workflow.' Interesting:-))).


Monday, February 18, 2008 #

If during the installation of a workflow feature using InfoPath forms you receive a warning message in the form 'The assembly assmbl1.dll from form template templ1.xsn(urn:....) conflicts with a same named assembly in form template templ2.xsn(urn:....). ' the reason is that one of the Info Path forms being deployed contains in the .xsn file an assembly which has the same fully qualified name as one of the already deployed assemblies. One reason could be that the assembly version property of the code behind project of an InfoPath form is hardcoded and not dynamically generated and you install the second version of the form.


Thursday, February 07, 2008 #

I am involved, between others, in a PS 2003 -> PS 2007 migration project. We are close to the end, however, we had a request from the end users to have on the 'My tasks' page a web part having the folowing features:

  • displays in a grid for a selectable month and year all the tasks assigned to the current user were the user reported work effort
  • tasks are grouped by projects
  • for every task and every day of the selected month the reported work is displayed with different color depending on the status of the reported data: saved, submitted for approval, approved

The big challenge was the last requirement above, to find out the status of the task timephased data (data reorted per day); I will peresent to you the steps I took to solve that problem.

1. The programmability of PS 2007 goes through the PSI web services. The classes from the SDK should be touched only if you create Project Server extensions. The first step was to use the ReadResourceAssignments method of the Resource PSI web service to obtain the assignment for the current user.

2. Iterate though all the assignments and get the timephased data for every of them for each day of the current month using the ReadStatusTimephasedData method of the Statusing PSI web service, then feed a DataTable which will be the datasource of a Asp.Net data grid with the timpehased data. For every of the assignments call the ReadAssignmentHistory method of the Statusing PSI web service.

3. And here is the hard part: I tried to iterate through the transactions of the assignment history for the assignments obtained in teh iteration above and call the ReadTransactionDetails method of the Statusing PSI web service, in order to try find out the status of the timephased data for every day. The problem is that for the majority of the history entries (transactions), this web service method call fails with a general unhandled exception. I figured out, that for those transations represented by AssnHistoryDataSet.HistoryRow instances the TASK_CHANGE_DATA attributes is null. This might be or not the reason for the failure.

I started to crawl through the code of Project Server assemblies using reflection, and I figured out that an AssnHistoryDataSet.HistoryRow instance represents all the timephased data submitted using the Save button from the MyTasks page from the project web access and the ASSN_CHANGE_DATA attribute contains all the timephased changes which came with that save. The changes are in the form of binary serialisation of the Microsoft.Office.Project.SvrDataEdit.ChangeLog internal class which contains a list of the Microsoft.Office.Project.SvrDataEdit.Change internal class instances, which contains in the Value attribute a Microsoft.Office.Project.SvrDataEdit.PeriodData internal class instance which in turn has the Start, End and Value attributes. Additionally, the ASSN_TRANS_ACTION_ENUM of the history object means 'Submitted' if 0, 'Approved' if 1 and 'Rejected' if 2. If ASSN_TRANS_ACTION_ENUM is 0, then the ASSN_CHANGE_DATA contains all the unapproved submitted data on the date of creation of the history entry, in PeriodData instances. If 1, then contains all the approved data in the momentof creation of the history entry.

To make it more complicated, if a user saves multiple changes on My Tasks page using the Save button and then submits them for approval, all those changes are logged as Microsoft.Office.Project.SvrDataEdit.PeriodData instances for every changed day.

See below the code to deserialise the change log for a transaction and save it in a custom list containing custom HistoryEntry classes.

Object obj = fomBytesMethod.Invoke(null, new object[] { new ByteArray(byteData) });
int count = (int)changeLogType.InvokeMember("Count", BindingFlags.Default | BindingFlags.GetProperty, null, obj, null);
List<HistoryEntry> assignmentTimephasedData = new List<HistoryEntry>();


for (int idx = 0; idx < count; idx++)
{
  object change = changeLogType.InvokeMember("Item", BindingFlags.Default |   BindingFlags.GetProperty, null, obj, new object[] { idx });
  object changeValue = changeType.InvokeMember("Value", BindingFlags.Default | BindingFlags.GetProperty, null, change, null);
  DateTime startDate = (DateTime)periodDataType.InvokeMember("Start", BindingFlags.Default |   BindingFlags.GetProperty, null, changeValue, null);
  DateTime endDate = (DateTime)periodDataType.InvokeMember("End", BindingFlags.Default | BindingFlags.GetProperty, null, changeValue, null);
  double changeValueValue = (double)periodDataType.InvokeMember("Value", BindingFlags.Default |  BindingFlags.GetProperty, null, changeValue, null);


  HistoryEntry historyEntry = new HistoryEntry(startDate, endDate, changeValueValue);
  assignmentTimephasedData.Add(historyEntry);
}

In order to obtain the status of a reported timpehased data for an assignment (for example, I have 4 hours reported actual work for 8th of Feb for the 'Create Users Page' assignment) we have to iterate through all transactions (history) for that assignment, in chronological order, get the latest PeriodData data instance for that day, and depending on the ASSN_TRANS_ACTION_ENUM value we can say that we have an 'Approved' or 'Submitted' status. If there is no value for that day in the transaction history or the latest value obtained from the transaction history is different then the one obtained from the timephased data returned via the ReadStatusTimephasedData web method, then the status of the timephased data is 'Saved'. All the logic doing this kinda staff in the Project Server assemblies are obfuscated, it was challenging to sort all these things out. Hope this helps!


Wednesday, February 06, 2008 #

Update: g. mipa gave me a much simplier solution:

PSSvcResource.Resource myRes = new PSSvcResource.Resource();
GUID currUserID = new GUID(myRes.GetCurrentUserUid());

My bad. Thank you!

Obsolete:

 Can be done by:

  • Get the current user from using the WSS 3.0 API
  • Build the Microsoft.Office.Project.Server.Library.Filter instance using the current user's name
  • Call the ReadResources method of the Resource PSI web service

PSSvcResource.ResourceDataSet resourceDs = new PSSvcResource.ResourceDataSet();

// Create Filter
PSLibrary.Filter resourceFilter = new Microsoft.Office.Project.Server.Library.Filter();
resourceFilter.FilterTableName = resourceDs.Resources.TableName;
resourceFilter.Fields.Add(new PSLibrary.Filter.Field(resourceDs.Resources.TableName,resourceDs.Resources.RES_UIDColumn.ColumnName, PSLibrary.Filter.SortOrderTypeEnum.None));
resourceFilter.Fields.Add(new PSLibrary.Filter.Field(resourceDs.Resources.TableName, resourceDs.Resources.RES_IDColumn.ColumnName, PSLibrary.Filter.SortOrderTypeEnum.None));

// Obtain context of current site and user.
SPWeb web = SPContext.Current.Web;
SPUser user = web.CurrentUser;

//Get the user data from the PS database PSLibrary.Filter.FieldOperator existingResource = new PSLibrary.Filter.FieldOperator(PSLibrary.Filter.FieldOperationType.Equal, resourceDs.Resources.RES_NAMEColumn.ColumnName, user.Name);
resourceFilter.Criteria = existingResource; string filterXml = resourceFilter.GetXml();
resourceDs = PSSvcResource.ReadResources(filterXml, false);
if (resourceDs.Resources.Count >= 1)
{
 return resourceDs.Resources[0].RES_UID;
}
else throw new ApplicationException("Current user was not found in project server database");


Sunday, January 27, 2008 #

We installed first time in a stressy condition the SP1 for a production server (a simple farm installation). There was an error during running a custom workflow (developed in Visual Studio 2005) attached to a custom list; we googled out that it was a MOSS bug solved by SP1, which was not installed yet. The plan was to install SP1 and restore a backup from previous midnight for the content database in order to regain the status of a workflow which crashed due to the MOSS bug mentioned above.

What we did:

  • install SP1
  • test that the installation was succesfull
  • restore the content database

Errors we made:

  • we didn't use a test environment to test
  • we read only superficially the installation guide

The result: Server error http://go.microsoft.com/fwlink?LinkID=96177 when trying to access one of the sites. After reading (with care :-)) the article 'Deploy software updates for Windows SharePoint Services3.0' (http://technet2.microsoft.com/windowsserver/WSS/en/library/91649a7e-6b5a-4e5a-9ee5-51951f4b857f1033.mspx?mfr=true) we figured out that the problem was that the restored database was created before applying the service pack and there was a version mismatch between the restored database and the installed web services extensions installed with SP1 (information held in the registry). Shame :-)).

Solution: use stsadm to detach the restored database and attach to it again. This triggered an upgarde process which solved our problem.


Tuesday, January 22, 2008 #

Recently I created a web part for Project Server 2007 and I had to use PSI which is a set of web services which are coming withe the PS 2007 installation.

When a PSI web service is addded in Visual Studio to a project, the chosen web service url has the form of http://{machinename}/pwa/_vti_bin/psi/{webservicename}.asmx but in the app.config file and in the settings class of the project, for a reason unknown by me the url of the web service is set to http://{machinename}/_vti_bin/psi/{webservicename}.asmx, the /pwa part is left out from the url.

When accessing the web service, a WebException with the 'Object moved' message is thrown, which is not really meaningfull. The solution is to modify manually the generated url both in the app.config and in the settings file (Settings.Designer.cs).


Wednesday, January 09, 2008 #

A possible reason for this is that a group is created without any attached permission level. The Change Group Setting page allows to do this, the result is that the Groups collection of an SPWeb instance doesn't contain the created group. Solution: attach at least one permission level to the group on the group settings page :-) .


Tuesday, December 11, 2007 #

There is the following complex scenario: a workflow hosted in MOSS is listening in an OnTaskChanged activity which is in a While loop. The task change could happen if a user enters MOSS and manually updates the task (i.e. completes it) or by an external console application, which programatically looks up and alters the task. In the Invoked event handler of the OnTaskChanged activity a web service is called to obtain information which used to proceed further. The web service uses integrated authentication.

The second way doesn't works by default. When the SPWorkflowTask.AlterTask method is called, a new thread is strated by the MOSS API in the process of the console application, having
for the System.Threading.Thread.CurrentPrincipal.Identity the following attribute values: AuthenticationType = "", IsAuthenticated = False, Name = "", type = System.Security.Principal.GenericIdentity. This means that the Windows identity of the process is not used to set the principal of this thread. When accessing the web service, an HTTP unauthorised error code is returned.

Trying to set the network credentials of the web service proxy doesn't helps, the unauthorised error code persists.

The solution: impersonate the thread using a valid Windows account which will pass the integrated authentication.


Tuesday, October 02, 2007 #

I recently joined a project which automates different processes from a company using MOSS 2007 with Workflow Foundation and InfoiPath 2007 forms.

We have the following scenario: create a SharePoint task in a deployed workflow with the CreateTask activity, wait for its completion with the OnTaskChanged activity and then delete the task with the DeleteTask activity. We have to control the timeout, so we tried to use in a parallel branch a Delay activity.

In SharePoint 2007, if a workflow has a delay activity in a repeating control, the workflow goes idle and is persisted as designed but it is not awaked always as expected. We run several tests by puttting a one minute delay in an infinite While loop, sometime is working even for few hours but after that the workflow is awaked randomly (might happen that more that 1 hour passes, instead of the set 1 minute, before anything happens).

Beign a Gold Certified partner, we opened an incident at Microsoft, we found out that this is a known issue but right now it is not planned to be solved.

The workaround:

  • create a console application (the timer service) which iterates through a SharePoint web site's list to which the worklow is attached and for every item, for  the attached tasks, calls the SPWorkflowTask.AlterTask method just to trigger an update on the task
  • register this console application in the Windows Scheduling service
  • do not use a Delay activity in a parallel branch, put the OnTaskChanged in a While loop
  • any time the scheduled console app updates the corresponding task, check in the Invoked event handler of OnTaskChanged whether the task was completed or we have a timeout, if non of these happens, loop again, wait for the next update which can come from the scheduled app or the user to whom the task is assigned

The code below is the part of the timer service that iterates all subsites of a site and updates a any SPWorkflowTask attached to any item from a list with a given name. The code references the Microsoft.Office.Servers and Microsoft.SharePoint assemblies. You can change it easily upon your needs.

        using (SPSite site = new SPSite("your site url"))
        {
            ServerContext context =
            ServerContext.GetContext(site);

            SPWebCollection allSites = site.AllWebs;
            SPWorkflowManager SPWFM = site.WorkflowManager;

            foreach (SPWeb subSite in allSites)
            {
                SPListCollection allSiteLists = subSite.Lists;
                foreach (SPList subSiteList in allSiteLists)
                {
                    if (subSiteList.Title == "your list name")
                    {
                        SPQuery query = new SPQuery();
                        query.Query = "";

                        SPListItemCollection listItems = subSiteList.GetItems(query);

                        foreach (SPListItem listItem in listItems)
                        {
                            // active workflows
                            SPWorkflowCollection SPWFCollection = SPWFM.GetItemActiveWorkflows(listItem);

                            foreach (SPWorkflow SPWorkflow in SPWFCollection)
                            {
                                foreach (SPWorkflowTask SPWFTask in SPWorkflow.Tasks)
                                {
                                    Hashtable SPTaskExtendedProperties = SPWorkflowTask.GetExtendedPropertiesAsHashtable((SPListItem)SPWFTask);

                                    logger.Info(string.Format("Updating task with title {0}.", SPWFTask.Title));
                                     try
                                    {
                                        SPWorkflowTask.AlterTask((SPListItem)SPWFTask, SPTaskExtendedProperties, true);
                                    }
                                    catch (Exception exc)
                                    {
                                        logger.Error("Error altering task: ", exc);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }