Welcome to MSDN Blogs Sign in | Join | Help

Recently a customer came up with an issue and wanted to create a custom submit action button handler in CMS 2002 and send email to the editor/moderator. The requirement was that the user should be able to select which editor or moderator the email needs to be sent. The customer was following the article available at http://msdn2.microsoft.com/en-us/library/ms964282.aspx

 

But the code given in the article does not do Posting.Submit() nor does it handles the PostBack events in JavaScript. It just uses the redirect which do not solve our purpose. Researched a little and dig deeper into the MCMS belly and created the following code to achieve customer’s requirements.

 

Just compile this code into a Control and replace the CMSConsole:SubmitAction (Microsoft.ContentManagement.WebControls.ConsoleControls.SubmitAction) from the ConsoleContext.ascx with the below given SubmitAction control and you are all set to go !

   1: using System;
   2: using System.Data;
   3: using System.Configuration;
   4: using System.Web.UI;
   5: using System.Web.UI.WebControls;
   6: using System.Web.Mail;
   7: using Microsoft.ContentManagement.Publishing;
   8: using Microsoft.ContentManagement.WebControls;
   9: using Microsoft.ContentManagement.WebControls.ConsoleControls;
  10:  
  11: namespace MySubmitEmail
  12: {
  13:     /// <summary>
  14:     /// Summary description for MSSubmitEmail
  15:     /// </summary>
  16:     public class MSSubmitEmail : SubmitAction
  17:     {
  18:         protected override void PerformActionBehavior()
  19:         {
  20:  
  21:             string sEmail = this.Page.Request.QueryString.Get("email").ToString();
  22:  
  23:             if(CmsHttpContext.Current.Mode != PublishingMode.Update)
  24:             {
  25:                 if(this.Page.Request.QueryString.Get("email") == null)
  26:                 {
  27:                     this.Page.Response.Redirect(CmsHttpContext.Current.Posting.UrlModeUpdate + "&email=someid@email.local",true);
  28:                 }
  29:                 else
  30:                 {
  31:                     this.Page.Response.Redirect(CmsHttpContext.Current.Posting.UrlModeUpdate,true);
  32:                 }
  33:             }
  34:  
  35:             CmsHttpContext.Current.Posting.Submit();
  36:             CmsHttpContext.Current.CommitAll();
  37:             base.PerformActionBehavior();
  38:             
  39:             this.Page.Response.Redirect(CmsHttpContext.Current.Posting.UrlModeUnpublished,true);
  40:         }
  41:  
  42:         public override string Text
  43:         {
  44:             get
  45:             {
  46:                 return "Submit with email";
  47:             }
  48:             set
  49:             {
  50:                 base.Text = value;
  51:             }
  52:         }
  53:  
  54:         public override string ActionJavascript
  55:         {
  56:             get
  57:             {
  58:                 string sJavaScript;
  59:                 string sURL = "";
  60:  
  61:                 sJavaScript = "var sDLGResult;"
  62:                     + "sDLGResult = window.showModalDialog"
  63:                     + "('/select.html'," + "'','scroll:no;dialogHeight:250px;"
  64:                     + "dialogWidth:500px;edge:sunken;"
  65:                     + "center:Yes;help:No;resizable:No;"
  66:                     + "status:No;');"
  67:                     + "if (sDLGResult!='Cancelled')"
  68:                     + "{ alert(sDLGResult); ";
  69:  
  70:                 sURL = this.UrlPostback;
  71:  
  72:                 if(this.Page.Request.QueryString.Get("email") == null)
  73:                 {
  74:                     if(! (this.UrlPostback.ToString().IndexOf("&email=") > 0) )
  75:                         sURL += "&email=";
  76:  
  77:                     sJavaScript += "CMS_preparePostbackUrl('" + sURL + "' + sDLGResult);" + this.Page.GetPostBackEventReference(this, "");
  78:                 }
  79:                 else
  80:                 {
  81:                     sJavaScript += "CMS_preparePostbackUrl('" + sURL + "');" + this.Page.GetPostBackEventReference(this, "");
  82:                 }
  83:  
  84:                 //We dont need to do the navigate, we need a postback
  85:                 //sJavaScript += "window.navigate(window.location + '&chan=' + sDLGResult);"
  86:  
  87:                 sJavaScript += "}";
  88:                 return sJavaScript;
  89:             }
  90:         }
  91:     }
  92: }

 

Happy Coding…

Sometime back a customer came up with an issue. He has written a code in FeatureActivated event handler to add an entry into Web.Config file by using SPWebConfigModification API. The same entry was being removed in FeatureDeactivating event handler.

The code is working perfectly fine in the test machine but failed to add or remove then entry when used in their QA environment. Now this QA environment was having 4 WFE’s and SQL server on different box. Rest of the SharePoint configuration was pretty much the same as test machine. But the test environment was a single machine environment, with everything on a single machine.

The code being tested was:

   1: public void AddEntry
   2: { 
   3:             SPSite siteCollection = new SPSite("http://MOSSServer/");
   4:             SPWebApplication webApp = siteCollection.WebApplication;
   5:             
   6:             string value = @"<role DisplayName='MyWeb Financial Controllers' Role='MyWeb Financial Controllers'/>";
   7:             string strName = "role[@DisplayName='MyWeb Financial Controllers']";
   8:  
   9:             SPWebConfigModification modification = new SPWebConfigModification(strName, "configuration/PortalRole");
  10:  
  11:             modification.Value = value;
  12:             modification.Owner = "ExampleOwner";
  13:             modification.Sequence = 0;
  14:             modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
  15:  
  16:             webApp.WebConfigModifications.Add(modification); 
  17:  
  18:             webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
  19: }

After trying for sometime and debugging, found that there seems to be an issue with getting to the Farm instance using SPWebApplication object. So instead of using SPWebApplication.Farm, we used SPWebService.ContentService to add the changes and call its ApplyWebConfigModifications method to push the changes to the Web.Config file.

The code was updated to:

   1: public void AddEntry
   2: { 
   3:                 string value = @"<role DisplayName='MyWeb Financial Controllers' Role='MyWeb Financial Controllers'/>";
   4:                 string strName = "role[@DisplayName='MyWeb Financial Controllers']"; 
   5:  
   6:                 SPWebConfigModification modification = new SPWebConfigModification(strName, "configuration/PortalRole");
   7:  
   8:                 modification.Value = value;
   9:                 modification.Owner = "ExampleOwner";
  10:                 modification.Sequence = 0;
  11:                 modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
  12:  
  13:                 SPWebService.ContentService.WebConfigModifications.Add(modification);
  14:                 SPWebService.ContentService.Update();
  15:                 SPWebService.ContentService.ApplyWebConfigModifications();
  16: }

Instead of adding modifications to WebConfigModifcations of SPWebApplication object, we are using SPWebService.ContentService to call ADD and UPDATE methods. Whenever required, it is always advised to use SPWebService.ContentService to make the modifications rather than accessing Farm instance through SPWebApplication.

Happy Coding…

A customer came up with an issue. WSRPConsumerWebPart’s userContextKey is always sending user’s display name when communicating with the producer. They were having a requirement that the userContextKey have to be a unique value to identify the user.

Upon a little digging found that when you are using the WSRPConsumerWebPart, the userContextKey have 2 options to pass to the WSRP Provider Service.

  • Anonymous
  • Current User Name

When option 2 is used, it always passes the Display Name of the user as in the SharePoint user profile (SPUser.Name). The SharePoint user profile is usually populated from the AD fields. The Display Name is not guaranteed to be Unique for the list of users unlike SPUser.LoginName which is unique in AD.

 

Why does this happen?

When using "Current User Name" option is selected in the WSRPConsumerWebPart, SharePoint hard codes it to SPWeb.CurrentUser.Name property which effectively uses SPUser.Name property, which in turn returns the user's display name and NOT the actual user ID or samAccount information of the user.

The DisplayName can be duplicate in the the AD and is not guaranteed to be unique.


As this is the behavior of WSRPConsumerWebPart and SPUser.Name is hard coded in to the WSRPConsumerWebPart to be returned for userContextKey, only the workarounds can be applied.

 

Workaround 1

You can update the display name field in the AD properties of the user to something unique and re-import the profiles into SharePoint
 

Workaround 2

Use the code to update the SPUser properties in SharePoint. You can get more details on the SPUser.Name property at http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spuser.name.aspx. This article also contains an example of how to modify/update SPUser object.
 

Workaround 3

You can edit user profile property (Name) in the SSP (Central Administration –> Shared Services Administration –> User Profile and Properties –> View Profile Properties –> Edit User Profile Property (Name))

By default "Name” property is mapped to displayName property of AD.

You can map it to an AD property which is Unique in the AD like mail which contains the Email address of the user. Import the profiles again and your Name, property will be a unique field and can be used in WSRPConsumerWebPart’s userContextKey.

 

Hope this helps someone out banging head for unique value of userContextKey.

 

Happy Coding

A team mate of mine was stuck with a curious issue. She was using SharePoint’s in-built Approval workflow but was using a custom ASP.net application to talk to a custom webservice hosted inside SharePoint server. That particular web service was having a method to complete the Approval workflow’s task using SPWorkflowTask.AlterTask method.

Here is the code snippet that was being used to update the task from within the web method.

   1: SPList timesheets = web.Lists["SampleList"];
   2: string listID = "";
   3: SPListItem item = timesheets.Items[0];
   4:  
   5: SPWorkflowTask taskedit = null;
   6: SPWorkflowTask task = item.Tasks[0];        
   7: taskedit = task;
   8:  
   9: if (taskedit == null)   // no matching task
  10:     return;
  11:  
  12: // alter the task
  13: Hashtable ht = new Hashtable();
  14: ht["Status"] = "Complete";
  15: ht["PercentComplete"] = 1.0f;
  16:  
  17: SPWorkflowTask.AlterTask((taskedit as SPListItem), ht, true);

 

Now no matter how it was being updated, the workflow task was being updated but the workflow itself was stuck in “In Progress” state and never completes.

This was a curious thing as I had earlier also worked with similar issue but with a custom workflow created for SharePoint using Visual Studio. I had a sample code which was working with it and the customer workflow was getting completed.

Now what was different here?

After some troubleshooting and debugging, found that Approval workflow requires a property “TaskStatus” to be set in the hashtable being passed to SPWorkflowTask.AlterTask method. The valid values for “TaskStatus” property are:

 

ht["TaskStatus"] = "#";   // This would mean that the task has been Approved
ht["TaskStatus"] = "@";   // This would mean that the task has been Rejected

 

So finally the code in the web method was updated to update the TaskStatus property as below:

   1: SPList timesheets = web.Lists["SampleList"];
   2: string listID = "";
   3: SPListItem item = timesheets.Items[0];
   4:  
   5: SPWorkflowTask taskedit = null;
   6: SPWorkflowTask task = item.Tasks[0];        
   7: taskedit = task;
   8:  
   9: if (taskedit == null)   // no matching task
  10:     return;
  11:  
  12: // alter the task
  13: Hashtable ht = new Hashtable();
  14: ht["TaskStatus"] = "#";    // Mark the entry as approved
  15:  
  16: SPWorkflowTask.AlterTask((taskedit as SPListItem), ht, true);

 

After this update, the tasks where updated properly and workflow completes as expected.

 

As always… Happy Coding

I have seen this error on various forums for quite sometime and wondered what is that SharePoint is doing around this?

It usually happens when you add Asyc="True" attribute to a SharePoint page and try to run the page, you get Parser error "The async attribute on the page directive is not allowed in this page."

Upon further debugging and researching, found that SharePoint’s SPPageParserFilter class blocks some page directive attributes. Checked the details of the SPPageParserFilter and found that the following are the only Page attributes which allowed by SharePoint Page Parser. If you use any other attribute which is not in the list given below, SharePoint will throw exception that "The 'xxx' attribute on the page directive is not allowed in this page."

  1. autoeventwireup
  2. buffer
  3. classname
  4. codebehind
  5. codepage
  6. compileroptions
  7. contenttype
  8. culture
  9. debug
  10. description
  11. enabletheming
  12. enableviewstate
  13. enableviewstatemac
  14. errorpage
  15. explicit
  16. inherits
  17. language
  18. lcid
  19. lineparagmas
  20. maintainscrollpositiononpostback
  21. masterpagefile
  22. responsencoding
  23. smartnavigation
  24. strict
  25. stylesheettheme
  26. targetschema
  27. theme
  28. title
  29. clienttarget
  30. uiculture
  31. warninglevel

Update: The highlighted 3 new entries were found in the Microsoft.SharePoint.dll for build number 12.0.6504.5000, bring the total count to 31.

As always… Happy Coding

I was working with one of my colleague on an issue. It was a weird one, where customer was using SPWebConfigModification class to add the safe control entry into the web.config. The sample code being used was adapted from the SPWebConfigModification page on MSDN.

   1: static void Main(string[] args)
   2: {
   3:  
   4:     SPSite oSite = new SPSite("http://manpreet2");
   5:     SPWebApplication _webApp = oSite.WebApplication;
   6:     string strName = "SafeControl[@Assembly=\"MyCompany.Name.SamplePart\"]";
   7:  
   8:     SPWebConfigModification SafeControl = new SPWebConfigModification(strName, "configuration/SharePoint/SafeControls");
   9:     SafeControl.Owner = "OwnerName";
  10:     SafeControl.Sequence = 0;
  11:     SafeControl.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
  12:     SafeControl.Value = "<SafeControl Assembly=\"MyCompany.Name.SamplePart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cb3c72729d0875cd\" Namespace=\"MyCompany.Name\" TypeName=\"*\" Safe=\"True\" />";
  13:  
  14:     if (args[0] == "/add")
  15:     {
  16:         Console.WriteLine("Adding...");
  17:         _webApp.WebConfigModifications.Add(SafeControl);
  18:     }
  19:     else
  20:     {
  21:         Console.WriteLine("Removing...");
  22:         _webApp.WebConfigModifications.Remove(SafeControl);
  23:     }
  24:     _webApp.WebService.ApplyWebConfigModifications();
  25:  
  26:     Console.WriteLine("Done...");
  27:     Console.ReadLine();

Surprisingly when we were using this code to add the web.config entry, it was adding it properly but when we were trying to remove it, through web.config file was being modified but entry was not being removed.

This was really surprising, if we took the exact code from the MSDN page, it worked perfectly fine.

Then started the journey to find the root cause for this.

Digging into my older emails, I remember that something similar we had done earlier also but how it was resolved, was not known. Found that old email and check that code, that also worked perfectly fine.

That brought us to line by line comparison of the code and see what is happening and we found the culprit code!

In the following code at line 6, we are giving the assembly name with only the first part of the 4 part name of assembly.

   6: string strName = "SafeControl[@Assembly=\"MyCompany.Name.SamplePart\"]";

But the entry for the SafeControl was done with full 4 part assembly name at line 12.

   12: SafeControl.Value = "<SafeControl Assembly=\"MyCompany.Name.SamplePart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cb3c72729d0875cd\" Namespace=\"MyCompany.Name\" TypeName=\"*\" Safe=\"True\" />";

So when adding, it added the entry but when removing, SharePoint is not able to find any entry with only first part of the 4 part assembly name. That is why, it is not able to remove it.

Why the code at MSDN page is working fine?

It is because in SPWebConfigModification.Value and SPWebConfigModification.Name, both contain only the assembly name and NOT the full 4 part assembly name.

 

Finally 1 more issue resolved and got to understand to look at the code more closely and not to miss the obvious.

 

As always… Happy Coding

 

-Manpreet

I was working with custom email event handler in SharePoint using SPEmailEventReceiver and found that when using the default functionality of I am able to configure and get the attachments to be added to the document library. Here is how the Incoming Email settings look like when you have your SharePoint server configured to receive emails and you enable your document library to receive emails.

image

I took the exact code that was provided in the MSDN documentation of SPEmailEventReceiver and implemented in my SharePoint site.

   1: namespace Example_Namespace
   2: {
   3:     public class Email_Handler: SPEmailEventReceiver
   4:     {
   5:         public override void EmailReceived(
   6:             SPList oList,
   7:             SPEmailMessage oMessage,
   8:             string strReceiverData)
   9:         {
  10:             SPListItem oListItem = oList.Items.Add();
  11:             oListItem["Title"] = oMessage.Headers["Subject"];
  12:             oListItem["Body"] = oMessage.HtmlBody;
  13:             oListItem.Update();
  14:         }
  15:     }
  16: }

Now when I go to Incoming Email settings page for my Document library, here is what I see.

image 

Where have all the settings gone? 

Also when using the sample code from the MSDN article, I find that the attachments I am sending along with the email are not being captured in the document library. As my class is inheriting from SPEmailEventReceiver, my initial thought got me to that if I call base.EmailReceived  in my event handler, then SharePoint should be able to handle the attachments and permission checks etc. But no, even calling base.EmailReceived did not resolve the problem.

This had me dive into checking what SharePoint does with the default settings and when I do not have my custom email event handler.

After some digging, found that SharePoint implement its own internal Email handler class which takes care of checking permissions, checking email settings for document library and adding the actual items to the list/library. EmailReceived function for SPEmailEventReceiver is a blank method with no definition. That is why calling "base.EmailReceived" method from class (inheriting from SPEmailEventReceiver) will have no effect.

Which lead to me investigating SPEmailMessage class (object of which is passed to EmailReceived method of your customer email event receiver). It has got Attachments property which contain all the attachments that are there in the email. Now things seems to be more in perspective and finally wrote the code to dump the attachments in the document library.

   1: public override void EmailReceived( SPList list, SPEmailMessage message, string receiverData)
   2: {
   3:       // Your rest of code to handle the different aspects of the email message received
   4:       // goes here. This would include permission check, duplicate items etc.
   5:       // After that, we will call the code to dump all the attachments in the root folder
   6:       foreach (SPEmailAttachment attachment in message.Attachments)
   7:       {
   8:             byte[] attachmentArray = new byte[attachment.ContentStream.Length];
   9:             attachment.ContentStream.Read(attachmentArray, 0, (int)attachment.ContentStream.Length);
  10:             list.RootFolder.Files.Add(attachment.FileName, attachmentArray);
  11:       }
  12: }

Please note sample does not provide any functionality to check list settings for user permissions, duplicate files or saving the original email. This all this done by the default email handler for SharePoint. If you are planning to implement your own EmailHandler, you will need to take care of all the default functionality also provided by SharePoint.

Happy Coding…Happy Coding

-Manpreet

Recently while working with a customer, got a requirement which got me digging into the emails generated by SharePoint alerts.

When you setup alert in a SharePoint document library and get an alert email, you get an option to connect or subscribe to that particular Document Library from within Outlook.

image

Now customer wanted to have the alert emails sent, but did not wanted to have “Connect to this Document Library” connection button within the Outlook client.

As it is just like any other email and research done during a previous issue on SharePoint alert emails,  started by looking at the email headers being sent by SharePoint and started playing by removing them 1 by 1. This started crashing Outlook client whenever an email with modified header comes in.

Sometimes the email would come but I would not be able to open the alert email and just show a dialog “Cannot open this item”.

image

After playing around for quite sometime with different headers and crashing Outlook a number of times, found out that the Outlook connection to the SharePoint happens due to the Content-Class header. If Content-Class header exists and has got value of "sharing", then only Outlook with parse other "x-" headers and show you the "Connect to this Document Library" button in Outlook client.

image

If you delete the Content-Class header from the collection or set it to something else than "sharing", Outlook client safely assumes it to be a normal alert email and still show it as an Alert email but does not show you the “Connect to this Document library” button any more.

image

You can modify the headers of the alert emails by creating your own Alert notification handler by implementing IAlertNotificationHandler interface. You can follow the KB 948321 for instructions on how to write and implement a custom alert notification handler.

SmileTill then Happy Coding…

I am back after a long time and will now try to be a little more regular with my posts.

Recently we worked upon a curious problem. We were using UserProfileManager in a web part and wanted to get to the private properties of a user profile. Now as the web part would be available on the SharePoint site and not every user would have permissions to view the private properties of the profile, we decided to use SPSecurity.RunWithElevatedPrivileges. To get the private properties of a user profile, we need to use the following overloaded constructor of the UserProfileManager class:

UserProfileManager (ServerContext, Boolean)

The second “Boolean” parameter specifies whether to ignore user profile privacy policies. Now to set this parameter to “True” and see the private properties of the profile, the calling user should have explicit permissions as defined in the Central Administration site under SSP –> “User Profiles and My Sites” –> “Personalization services permissions”

image 

You have to make sure that the user have “Manage User Profiles” permission.

image

 

Now my concern was, when I am using RunWithElevatedPrivileges and already running with System Account rights, why I was getting error:

User must have manage user profiles administrator rights to use administrator mode

Funny enough, when I was calling the User Profile Web Service (_vti_bin/userprofileservice.asmx), the same code was working perfectly fine and no error cropped up.

This lead me to dig deeper into the belly of the beast to see what is going under the hoods. After tracing back the calls and digging into the code, finally discovered that SharePoint was using HttpContext.Current.User.Identity which gives the User security for the current HTTP request (more details in http://msdn.microsoft.com/en-us/library/aa480475.aspx) instead of calling System.Security.Principal.WindowsIdentity.GetCurrent() which would actually represent the impersonated user.

Now comes, why it is working when we are using the User Profile WebService call?

As we were already initiating the the web service request using the impersonated identity of Application Pool, the ASP.net request was being started by App Pool identity, which in-turn had the full permissions. That’s the reason why the web service calls works while the Object Model call from a Windows Application failed.

So in effect, you cannot impersonate a user and try to get the private properties of a user profile. You would need to give explicit permissions to every user who wants to access the private properties of the User Profiles stored in SharePoint.

OR

Use SharePoint’s UserProfile web service call within the impersonated context to get the data.

Hope that helps out some of you…

Happy Coding… Happy Coding

When the web application is created and content source crawled, it gets listed in the the crawl logs of search. But when this application is deleted, the logs still contain reference to the deleted web application.

Doing further incremental or full crawls does not remove the stale entries from the crawl logs. Only way it happens, is to reset all the crawled content and start a full crawl. But this would have a huge impact where the amount of data to the crawled is huge.

We can access the crawl logs using the Object Model classes available in Microsoft.Office.Server.Search.Administration namespace, located in the Microsoft.Office.Server.Search.dll

Crawl logs are purged automatically after 7 days (default value). According to http://msdn.microsoft.com/en-us/library/ms553190.aspx


You use the
LogViewer object to retrieve the crawl log data. The MaxDaysCrawlLogged property of the LogViewer object allows you to set the maximum numbers of days for the crawl log to keep data.

Though the article says that you can "set" the value of MaxDaysCrawlLogged to control the maximum number of days for the crawl log to keep the data, but seems like setting it does not have any effect. After checking with various resources and SharePoint internals, found that the property is available but is not working or does not have any effect on the crawl log. It is a dummy property which does not do anything.

This effectively means that you cannot make any changes to the max days a crawl log keeps the data and it remains to the default of 7 days only.

Happy Coding... 

[In nutshell, how to display a Page Viewer Web Part with 100% height of the available screen estate.]

A co-worker of mine was working with a customer and he was trying to put a page viewer web part in a Web Part page in MOSS to display his web site. On the whole of page, only 1 Page Viewer web part was there but was still sticking to it's default zone height and not expanding to 100% of height available.

I tried playing around with the settings, but default Page Viewer Web Part can only have absolute "height" parameter and cannot take height in percentage (I wanted to give 100% to make sure it scales to small and large screen sizes).

Then came in the tried and trusted HTML DOM. I took hold of my Developer Toolbar in IE and started digging into the DOM of the page and trying to figure out what all properties can be played along. Tried just changing the height of IFrame which renders the "outside" page, but no luck. Through hit 'n trial found that all the Table tags in the hierarchy need to be changed to 100% height. So came up with the JScript code to do it automatically every time the page is loaded. The JScript code below was added to a Content Editor web part.

The steps:

  1. Create a new Web Part page with "Full Page, Vertical" layout template.
    image
  2. Drop a Page Viewer Web Part on the page and in the settings, choose Web Page and in the Link, type in the external URL e.g. http://www.microsoft.com
    image
    Hit OK to let the Page Viewer Web Part load the URL.
  3. As you can see the Page Viewer Web Part loads the page in a restricted area in the screen although we have additional height available.
    image
  4. Drop a Content Editor Web Part just below the Page Viewer Web Part:
    image
  5. In the tool pane for Content Editor Web Part, click on Source Editor as we would write the JScript code in the source editor.
  6. Paste the following JScript code in the Source Editor and click on OK to close the Source Editor.
  7. <script language="javascript">
    
       1:  
       2:             var a = MSO_ContentTable.getElementsByTagName("table");
       3:             
       4:             MSO_ContentTable.setAttribute("height","100%");
       5:             
       6:             for(i=0;i<a.length;i++)
       7:             {
       8:                 
       9:                 a[i].setAttribute("height","100%");
      10:             }
      11:  
      12:             var z = document.getElementsByTagName("div");
      13:             
      14:  
      15:             for(i=0;i<z.length;i++)
      16:             {
      17:                 if(z[i].id.indexOf("WebPartWPQ",0) >=0 )
      18:                 {
      19:                     if(z[i].hasChildNodes())
      20:                     {
      21:                         if(z[i].firstChild.nodeName.toUpperCase() == "IFRAME")
      22:                         {
      23:                             z[i].firstChild.setAttribute("height","100%");
      24:                         }   
      25:                     }
      26:                 }
      27:             }
    </script>

  8. Click OK again to close the Content Editor Web Part's tool pane.
  9. Click on "Exit Edit Mode" link to see the actual effect of the JScript code.
    Now you should be able to see the Page Viewer Web Part taking the full available screen height:
    image

You can actually mark Content Editor Web Part as "Hideen" (Tool Pane -> Layout -> Hidden) and it would not show up on your page but still execute the JScript code and get your Page Viewer Web Part as a full height control.

Happy Coding... Smile

-Manpreet

Recently a customer was following the KB 948321 and customizing the Alert notification emails being sent. He was just modifying the HTML part of it and rest using the same code. But when the emails were being received in the Outlook, they were not being recognized as Alert emails.

A regular alert email from SharePoint would look like this:

image

And would have the "Bell" icon and the email as such would have "Create Rule" button at the top.

image

But if you copy the exact code from the KB 948321, the email sent would look just like a normal email sent from a user.

Why does this happen?

Digging through the emails sent using the custom IAlertNotificationHandler and the emails sent by out-of-box SharePoint alerts, found that SharePoint adds a few headers to the email message like:

  • X-AlertTitle
  • X-AlertId
  • X-AlertWebUrl
  • X-AlertServerType
  • X-AlertWebSoap
  • X-Mailer

Debugging the custom IAlertNotificationHandler code, found that these headers are being set in SPAlertHandlerParams.headers collection. But in the sample code given, only "To" header is being pushed on to the SPUtility.SendEmail method.

SPUtility.SendEmail(web, true, false, ahp.headers["to"].ToString(), subject, build);

Set a break point and did a quick watch on ahp.headers and found:

image

So everything is already there in the headers, then why to pass just 1 header value to the SendEmail method. Checked the over loaded methods and found 1 which allows you to send the full collection of headers and replaced my code with:

SPUtility.SendEmail(web, ahp.headers, build);

Now the alert email was being displayed as expected and fully connected with SharePoint list happy

This problem generally start when you are having an existing custom workflow and there are instances of workflow running. You do some changes to the workflow and reinstall the assembly in the GAC. The new assembly is basically overwriting the existing one (it has same signature). When you try to complete the existing workflow instances after updating the assembly, you may get "Task is locked by running workflow instance" error or it just hangs in "In Progress" state.

So what did happen in background?

The public interface of the workflow, which has been serialized in the database, have changed and now the new assembly does not know how to de-serialize the workflow data from the database. At this point the workflow will fail as it is unable to serialize the current state and looks like it hanged in "In Progress" state.

Then how should I upgrade the assembly for a workflow?

Upgrading running instances of a workflow is a tricky business because the workflow doesn't know, if it's current state is, before or after a change in the assembly. When anything changes in public interface of workflow assembly, serialization/de-serialization of workflow data fails. The new assembly of workflow does not know how to load the older serialized data.

If you are just changing the code logic within the methods, you should be able to update the workflow assembly without breaking the existing workflow instances. But if you add/remove/modify any activity or add/remove any properties/methods from the code and update the existing workflow assembly, that would break the existing instances.

In nutshell, any kind of changes to the interface (public code interface or workflow design interface, which is serialized) will break the workflow.

The suggested way to upgrade the workflow is to create a new version of workflow and install it in GAC and SharePoint. Mark the existing workflow to "No New Instance", which will not create any new instances of old workflow.

Use the new workflow assembly for new instances, and the old workflow assembly for old instances. To do this, you need to update the assembly version to the next one (e.g. 2.0 from 1.0) and adjust the workflow.xml file to point to 2.0, then keep both 1.0 and 2.0 in the GAC. When you reinstall, new instances will use that new version, and old instances will continue to use the old and when all the older workflow instances have finished, you can remove the 1.0 version from the server.

I had a custom list and had the OOB approval workflow attached to it and setup to run on the item update. Tried and tested the list through UI and everything seems to be as perfect as it could be.

Customer was actually trying to update the list through SharePoint Object Model and was using SPListItem.Update() method. The item column is updated but the workflow is never triggered. No matter what we were trying the workflow refused to start on item changed. Still working through the user interface of SharePoint, it was working as expected.

In mean time heard about another customer who is updating an item from a SharePoint Timer Job and the workflow is triggered properly

 

 

 

This got me thinking why it is working in Timer context and not in the console application. Both the ways we are using the exact same code:

using(SPSite site = new SPSite(url))
{
    using(SPWeb web = site.OpenWeb())
    {
        SPList list = web.GetList(url);

        SPListItem item = list.Items[1];
        item[updateField] = DateTime.Now.ToLongDateString();
        item.Update();
    }
}

After some research found that all the workflow's and event handlers run on a different thread than the main thread. You just cannot create another Console/Windows Application and have it trigger the workflow and just quit.

Quitting the application before the asyc worker threads finish their work, causes those threads to simply abort. In case of workflow, it would seem as workflow never fired !!

So what is the fix here? Use the following line of code

SPSite.WorkflowManager.Dispose();

 

after updating the item. So effectively the overall code becomes:

using(SPSite site = new SPSite(url))
{
    using(SPWeb web = site.OpenWeb())
    {
        SPList list = web.GetList(url);

        SPListItem item = list.Items[1];
        item[updateField] = DateTime.Now.ToLongDateString();
        item.Update();
        site.WorkflowManager.Dispose();
    }
}

Now when I ran the console application, as expected the item updates and the workflow is triggered 1

After creating the blog account and sitting on it for last few days, finally got to publish this first entry 1

I got a new laptop and decided to play around with Windows XP SP3, so hooked up to the network and installed it. Install went smooth and punched in http://update.microsoft.com to get the latest updates. Everything done and laptop up and running, nothing surprising. Same look and feel and same great power of Windows XP.

The very first thing I noticed when I plugged in my Bluetooth dongle. It was recognized and drivers installed and within a few of minutes my phone was hooked up! The same dongle required me to install the drivers from the disk and use the clumsy application that came along with it.

Everything went fine at office and came home. From home when I was trying to connect to my office machines over a terminal server gateway (which requires smart card authentication) I was surprised at the message I was getting in CredUI.

Incompatible Smart Card

First thought came that something gone wrong with the smart card. But it was working a couple of days back when the same machine had Vista Enterprise. Checked on another machine with Windows XP SP2 and it was working fine. So something wrong with this machine only. But what changed?

Searching on Live.com also did not revealed any useful information. There were some mentions of RDC 6.0 (KB925876) being the culprit. To test out, installed RDC 6.0 on my Win XP SP2 machine but still it was recognizing my smart card. RDC 6.0 was ruled out as the cause.

What else could be wrong?

But couple of blog entry comments on Terminal Services Team blog said that after removing RDC 6.0 and moving back to RDC 5.1 got it working for them. So could it really be RDC 6.0 or something changed with it from 5.1? In Win XP SP3 there is no option to uninstall RDC 6.0 and move back to 5.1.

I decided to investigate into the RDC 6.0 changes and found that CSP is mandatory on client also if it is installed on the server. Verified that I have CSP installed on my XP SP2 box and it was not there on XP SP3 machine. Connected XP SP2 machine to office VPN and got the CSP and installed it on XP SP3.

With fingers crossed, pressed in the smart card into the reader and click Connect on MSTC window.

Voilà ! It worked and now my smart card is recognized.

Finally got the terminal connection working after banging my head on it for 2 days and learnt the lesson to read the documentation properly 4

 
Page view tracker