Saturday, April 16, 2011

Change default location of Sharepoint management console

In this post I want to share small tip which will help you to run Sharepoint management console (or management shell) in any custom folder. If you work with automatization of Sharepoint administrative tasks with PowerShell, then this tip may be helpful for you. The problem is that when you run OTB Sharepoint management console in runs in default home folder (e.g. “c:\users\myuser”). But in real life you may need to reopen it frequently (e.g. if you consume some code written on C# in assembly which is located in GAC. If this code is changed you need to reinstall assembly into GAC. Also if you want your changes to affect management console you need to reopen it because otherwise console will use old cached version of the assembly). After restart console will be again use default location, so you will need again perform “cd c:/projects/myscripts” command in order to go to the folder with your scripts.

In order to avoid this, go to the folder “ C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\CONFIG\POWERSHELL\Registration” and edit “SharePoint.ps1” file. Find the following line:

   1: Set-location $home

and replace $home with your folder:

   1: Set-location "c:\projects\myscripts"

After that once you will start Sharepoint management console – it will be launched in your folder and you will be able to start working with your scripts immediately. But note that this change will affect all users of the management console, so I recommend to revert changes when you will finish your development.

Saturday, April 9, 2011

Add custom AD attributes to People search results in Sharepoint

In one of my previous posts (see Create custom AD attribute and map it to Sharepoint user profile property) I showed how you can extent your AD schema with custom attributes and how to add them into User profiles properties. Here I want to show how to use these custom attributes in standard People search and how to customize People search results for displaying them.

Once you mapped attributes, customization of People search results is fairly easy. First of all you need to create Managed properties in Search service application and map them to properties of user profiles (see also my post AD attributes used in standard configuration of People search in Sharepoint where I showed what attributes are used in standard configuration of People search and their mappings). In order to do this go to Central Administration > Manage service applications > Search service applications > Metadata properties and create new Managed property for your custom attributes. E.g. if you follow by the same example which I used in the post mentioned above, you can create new Managed property with name “BusinessUnit” and add map to the Crawled property (User profile property) “People:BusinessUnit” (you can filter crawled properties by categories). After that perform full crawl.

Now you can go to your site and search for people using custom BusinessUnit property. E.g. if you need to find people which belongs to Russian business unit you can specify BusinessUnit:Russia in the search box – search results will contain people for specified unit.

The remaining problem is that business units themselves are not displayed on the search result page. In order to display them we need to customize OTB CoreResultsWebPart on People search page in the Search center site. Go to People search page and edit it, then choose Edit web part for Core results web part. Here uncheck “Use Location Visualization” checkbox and add the following columns into “Fetched Properties” textbox:

   1: <Column Name="BusinessUnit"/>

Name attribute should contains name of Managed property which we created earlier. After this you need to modify XSLT template – add custom property into template. Click XSLT editor in web part properties and make the following changes:

find the following line:

   1: <xsl:variable name="hasonum"      select="string-length(officenumber) &gt; 0"/>

and insert this code after it:

   1: <xsl:variable name="hasbusunit"   select="string-length(businessunit) &gt; 0"/>

After this search for the following statement:

   1: <xsl:if test="$hasonum">...</xsl:if>

and add similar code after it:

   1: <xsl:if test="$hasbusunit">
   2:   <li id="BusinessUnit">
   3:     <xsl:apply-templates select="businessunit" />
   4:   </li>
   5: </xsl:if>

Save changes and try to search for people which contains some value in Business Unit property. Search result page will now display value of this custom property near each user.

Wednesday, April 6, 2011

AD attributes used in standard configuration of People search in Sharepoint

Sharepoint 2010 uses CoreResultsWebPart on People search page in standard Search center. You can control what properties can be shown by this web part using SelectColumns and PropertiesToRetrieve properties (or via web part properties in UI: Display Properties > Fetched Properties). Here you need to specify names of Managed properties (you can find them in Central Administration > Manage Service Applications > Search Service Application > Metadata properties). This properties are mapped on Crawled properties (available on the same page – if you will click appropriate link on top of the page) which are used by Search crawler. There are several categories of Crawled properties. For People search we are interested in People category. Crawled properties from People category are in associated with User Profile Properties (Central Administration > Manage Service Applications > User Profile Service Application > Manage user properties). Some User Profile Properties are in turn mapped to AD attributes (and some are editable by users).

So we have the following mapping chain: Managed property > Crawled property > User profile property > AD attribute. I created a table where summarized all properties and their mappings:

Managed property

Crawled Properties

User Profile Properties

AD attribute

WorkId

-

-

-

UserProfile_GUID

People:UserProfile_GUID(Text)

Id

-

AccountName

People:AccountName(Text)

Account name

sAMAccountName

PreferredName

People:PreferredName(Text)

Name

displayName

YomiDisplayName

People:SPS-PhoneticDisplayName(Text)

Phonetic Display Name

msDS-PhoneticDisplayName

JobTitle

People:SPS-JobTitle(Text), People:Title(Text), ows_JobTitle(Text), ows_Job_x0020_Title(Text)

Job Title

title

Department

People:Department(Text), ows_Department(Text)

Department

department

WorkPhone

People:WorkPhone(Text), ows_WorkPhone(Text), ows_Work_x0020_Phone(Text)

Work phone

telephoneNumber

OfficeNumber

People:Office(Text)

Office

physicalDeliveryOfficeName

PictureURL

ows_PictureURL(Text), People:PictureURL(Text)

Picture

-

HierarchyUrl

People:ProfileHierarchyViewUrl(Text)

-

-

WorkEmail

People:WorkEmail(Text), ows_EMail(Text), ows_EMail_x0020_(Text)

Work e-mail

mail

Path

Basic:11(Text), Basic:9(Text), Web:2(Text)

-

-

HitHighlightedSummary

No mapping

-

-

HitHighlightedProperties

No mapping

-

-

Responsibility

People:SPS-Responsibility(Text)

-

-

Skills

People:SPS-Skills(Text)

Skills

-

SipAddress

People:SPS-SipAddress(Text)

SIP Address

proxyAddresses

Schools

People:SPS-School(Text)

Schools

-

PastProjects

People:SPS-PastProjects(Text)

Past projects

-

Interests

People:SPS-Interests(Text)

Interests

-

OrgNames

People:OrganizationNames(Text), ows_Company(Text)

-

-

OrgUrls

People:OrganizationURLs(Text)

-

-

OrgParentNames

People:OrganizationParentNames(Text)

-

-

OrgParentUrls

People:OrganizationParentURLs(Text)

-

-

Memberships

People:QuickLinks(Text)

Quick links

-

AboutMe

People:AboutMe(Text), ows_Notes(Text)

About me

-

BaseOfficeLocation

People:SPS-Location(Text)

Office location

-

ServiceApplicationID

No mapping

-

-

SocialDistance

No mapping

-

-

As I said you can customize CoreResultsWebPart and use whatever properties you need. But this table may e useful if you need to know what AD attributes are used in standard configuration of People search in Sharepoint 2010.

Tuesday, April 5, 2011

Simple custom error handler for webHttpBinding in WCF

When use standard webHttpBinding in WCF (e.g. for RESTful endpoints) you may face with the following problem: standard error handler WebErrorHandler which ships with WebHttpBehavior returns generic “Internal Server Error” message for any exception you throw in the WCF service. Or to be more precise it returns response message which contains exception message like this:

   1: HTTP/1.1 500 Internal Server Error
   2: ...
   3: Content-Type: application/xml; charset=utf-8
   4:  
   5: <Fault xmlns="http://schemas.microsoft.com/ws/2005/05/envelope/none">
   6:   <Code>
   7:     <Value>Receiver</Value>
   8:     <Subcode>
   9:       <Value xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher">
  10:         a:InternalServiceFault
  11:       </Value>
  12:     </Subcode>
  13:   </Code>
  14:   <Reason>
  15:     <Text xml:lang="en-US">
  16:       Product with the same Name already exists
  17:     </Text>
  18:   </Reason>
  19:   <Detail>
  20:     <ExceptionDetail xmlns="http://schemas.datacontract.org/2004/07/System.ServiceModel" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  21:       <HelpLink i:nil="true"/>
  22:       <InnerException i:nil="true"/>
  23:       <Message>
  24:         Product with the same Name already exists
  25:       </Message>
  26:       <StackTrace>
  27:         at WcfService.Save(Product p)&#xD;
  28:         at SyncInvokeSave(Object , Object[] , Object[] )&#xD;
  29:         at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]&amp; outputs)&#xD;
  30:         at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&amp; rpc)&#xD;
  31:         at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&amp; rpc)&#xD;
  32:         at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc&amp; rpc)&#xD;
  33:         at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc&amp; rpc)&#xD;
  34:         at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc&amp; rpc)&#xD;
  35:         at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc&amp; rpc)&#xD;
  36:         at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
  37:       </StackTrace>
  38:       <Type>
  39:         System.Exception
  40:       </Type>
  41:     </ExceptionDetail>
  42:   </Detail>
  43: </Fault>

This message if very verbose and contains a lot of useless xml noise. In order to solve these problems simple custom error handler can be used:

   1: public class CustomWebHttpErrorHandler : IErrorHandler
   2: {
   3:     bool IErrorHandler.HandleError(Exception error)
   4:     {
   5:         return true;
   6:     }
   7:  
   8:     void IErrorHandler.ProvideFault(Exception error, MessageVersion version, ref Message fault)
   9:     {
  10:         var statusCode = HttpStatusCode.InternalServerError;
  11:         if (error is WebException)
  12:         {
  13:             statusCode = ((WebException) error).Status;
  14:         }
  15:         string body = ErrorBodyWriter.GetBody(statusCode, error.Message);
  16:         fault = Message.CreateMessage(version, null, 
  17:             new ErrorBodyWriter {Code = statusCode, Message = error.Message});
  18:         
  19:         var rmp = new HttpResponseMessageProperty();
  20:         rmp.StatusCode = statusCode;
  21:         rmp.StatusDescription = string.Format("{0} {1}", statusCode, error.Message);
  22:         fault.Properties.Add(HttpResponseMessageProperty.Name, rmp);
  23:     }
  24: }

Here I use 2 external classes: WebException from WcfRestContrib project and ErrorBodyWriter which realization I found in one of the forum threads. WebException is very easy: is just allows you to specify HTTP status when throw exception (as far as I know in .Net 4.0 there is standard equivalent of this class for WCF):

   1: public class WebException : Exception
   2: {
   3:     public WebException(HttpStatusCode status, string friendyMessage, params object[] args) :
   4:         base(string.Format(friendyMessage, args))
   5:     {
   6:         Status = status;
   7:     }
   8:  
   9:     public WebException(Exception innerException, HttpStatusCode status, string friendyMessage, params object[] args) :
  10:         base(string.Format(friendyMessage, args), innerException)
  11:     {
  12:         Status = status;
  13:     }
  14:  
  15:     public HttpStatusCode Status { get; private set; }
  16:  
  17:     public virtual void UpdateHeaders(WebHeaderCollection headers) { ... }
  18: }

ErrorBodyWriter is used in order to construct POX-style response body:

   1: internal class ErrorBodyWriter : BodyWriter
   2: {
   3:     public HttpStatusCode Code { get; set; }
   4:     public string Message { get; set; }
   5:  
   6:     public ErrorBodyWriter()
   7:         : base(true)
   8:     {
   9:     }
  10:  
  11:     protected override void OnWriteBodyContents(System.Xml.XmlDictionaryWriter writer)
  12:     {
  13:         string errorMsg = GetBody(Code, Message);
  14:         System.Xml.Linq.XElement xElement = System.Xml.Linq.XElement.Load(new System.IO.StringReader(errorMsg));
  15:  
  16:         xElement.WriteTo(writer);
  17:     }
  18:  
  19:     public static string GetBody(HttpStatusCode statusCode, string msg)
  20:     {
  21:         string format = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
  22:         format += "<error>";
  23:         format += "<code>{0}</code>";
  24:         format += "<msg>{1}</msg>";
  25:         format += "</error>";
  26:  
  27:         string errorMsg = string.Format(format, statusCode, msg);
  28:         return errorMsg;
  29:     }
  30: }

With this error handler you will have the following response body:

   1: HTTP/1.1 500 InternalServerError: Product with the same name already exists
   2: ...
   3: Content-Type: application/xml; charset=utf-8
   4:  
   5: <error><code>InternalServerError</code><msg>Product with the same name already exists</msg></error>

Note that instead of “Internal server error” we show actual error message also “InternalServerError: Product with the same name already exists”. We do this in the following line of code:

   1: rmp.StatusDescription = string.Format("{0} {1}", statusCode, error.Message);

Client proxy generated by VS uses exactly StatusDescription for initialization of Exception.Message property. So now we will have error message at the client.

The remaining thing is to add this error handler into our service. I will show how to make it via web.config:

   1: <system.serviceModel>
   2:   <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
   3:  
   4:   <extensions>
   5:     <behaviorExtensions>
   6:       <add name="customWebHttp" type="CustomWebHttpBehaviorExtention, WcfService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..." />
   7:  
   8:     </behaviorExtensions>
   9:   </extensions>
  10:   
  11:   <!-- bindings -->
  12:   <bindings>
  13:     
  14:     <webHttpBinding>
  15:       <binding name="webBinding">
  16:       </binding>
  17:     </webHttpBinding>
  18:     
  19:   </bindings>
  20:  
  21:   <!-- behaviors -->
  22:   <behaviors>
  23:  
  24:     <endpointBehaviors>
  25:       <!-- plain old XML with custom error handler -->
  26:       <behavior name="customPoxBehavior">
  27:         <customWebHttp/>
  28:       </behavior>
  29:     </endpointBehaviors>
  30:     
  31:     <serviceBehaviors>
  32:       <behavior name="defaultBehavior">
  33:         <serviceMetadata httpGetEnabled="true"/>
  34:         <serviceDebug includeExceptionDetailInFaults="true"/>
  35:       </behavior>
  36:     </serviceBehaviors>
  37:   </behaviors>
  38:   
  39:   <services>
  40:     <service name="MyWcfService" behaviorConfiguration="defaultBehavior">
  41:       <host>
  42:         <baseAddresses>
  43:           <add baseAddress="http://example.com/MyWcfService.svc" />
  44:         </baseAddresses>
  45:       </host>
  46:       <endpoint address="pox"
  47:                 binding="webHttpBinding"
  48:                 bindingConfiguration="webBinding"
  49:                 behaviorConfiguration="customPoxBehavior"
  50:                 contract="IMyWcfService" />
  51:     </service>
  52:   </services>
  53:  
  54: </system.serviceModel>

Here we defined custom behavior extension with custom behavior (customPoxBehavior). The following classes are used for this:

   1: public class CustomWebHttpBehavior : WebHttpBehavior
   2: {
   3:     protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
   4:     {
   5:         endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
   6:         endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new CustomWebHttpErrorHandler());
   7:     }
   8: }
   9:  
  10: public class CustomWebHttpBehaviorExtention : BehaviorExtensionElement
  11: {
  12:     public override Type BehaviorType
  13:     {
  14:         get { return typeof(CustomWebHttpBehavior); }
  15:     }
  16:  
  17:     protected override object CreateBehavior()
  18:     {
  19:         return new CustomWebHttpBehavior();
  20:     }
  21: }

After that you will be able to use custom error handler for your WCF service.

Update 2011-06-12: with HTTPS this method can cause protocol violation exception. Here I posted solution for this problem.