March 2010 - Posts

I had the honor of being the first speaker at the new OKC SharePoint Users Group (OKCSUG) last night.  Wendy Schopf did a great job organizing the location and event and there was a pretty decent turnout.  I’m very excited to see the SharePoint community growing in OKC.  Joy Earles was also there doing an excellent job at filling out nametags. :-)  I updated my existing Enterprise Search talk to run on SharePoint 2010 and explained to users the basics of setting up search.  If you want to view the slides from last night, I’ve put them up on SlideShare.  Thanks again.

Since I’m talking about Enterprise Search tonight at OKCSUG, I thought I would write up a quick post on the steps you need to follow in order to get People Search working in SharePoint 2010.  The critical piece of course is getting the User Profile Synchronizations service working.  There are plenty of posts about it, so I’m not going to go into the details of how to get that part of it work.  SharePointDevWiki and MSDN do a pretty good job of getting you started.  The main thing is you want your Forefront Services started and looking something like this.

PeopleSearchForefrontServices

In Central Administration, on Services on Server, you want your User Profile services to show that they are started.

PeopleSearchUserProfileStarted

If your server doesn’t look like what you have above, then you probably have a problem.  With Beta 2, if you have made any changes to these services or so much as looked at them, you will need to reinstall SharePoint. Don’t bother trying to get it to work, because it will never happen.  Just ask twitter, it’s true. Don’t waste your time, just reinstall now.

If everything is running correctly, you need to set up a profile synchronization connection.  This is somewhat similar to the process you used in MOSS 2007, but they updated it a bit to make it a bit more user friendly.  For example, you don’t have to specify LDAP queries to get the objects you want any more.  Just specify an account on the domain, and then pick the object you want to import using a tree viewer.  The SharePointDevWiki article has some good screenshots on setting this up for the beta.  However, Harbar by far has the most definitive guide on getting User Profile sync working (follow it exactly).  If you have any issues with JavaScript errors on the screens, hit F12 and use IE7 mode and it will probably work.  Once you have configured your connection, start a new synchronization job and wait for it to finish.

At this point you have user profiles, but you can’t search them yet.  You’re dying to see all of the new features in People Search, so we have to do a crawl to make that happen.  At this point, it’s much like MOSS 2007.  In fact it uses the same protocol handler, sps3.  Hopefully, you know where they moved Search Administration to now that they have gotten rid of the SSP.  If not, it’s under Central Administration –>Service Applications –> Search Service Application –> Manage button.  Once you are here, go to Content Sources.  You should see one content source called Local SharePoint Sites.  Depending on how you installed, this may be correct or it may not be.  For start addresses, you should see something like http://server-name and sps3://server-name.  Here is what mine looks like.

PeopleSearchStartAddress

Sometimes, the sps3 address won’t be there, so you need to add it.  You can add it to this content source or create another one.  It really doesn’t matter.  Before we crawl, we need to check some new settings to make sure our crawl account has the necessary permissions.  Sometimes, SharePoint will take care of some of this for you other times it won’t.  The first thing you need to do is go to Central Administration –> Security –> Specify web application user policy.  Here, you need to make sure that your default content access account has Full Read permission.  I’ll take this as yet another time to remind you that your crawl account should not have administrator permissions.  It should look something like this.

PeopleSearchFullRead

The next thing we need to do is grant this account permissions to the User Profile Service Application.  To do that, go to the Service Applications page, click on User Profile Service Application (click next to it to highlight it, don’t click on the link).  When its highlighted, click on the Administrators button at the top.  This gives you a window that looks like this.

PeopleSearchUserProfileAdministrators

If your default content access account (crawl account) is not listed, add it and then select the checkbox Retrieve People Data for Search Crawlers.  If you don’t complete this step, you will likely get an access denied error message when you crawl.

Once it’s created, you need to do a full crawl.  If you haven’t created an Enterprise Search Center yet, now is a good time to do so.  Once you create it, click on the People tab and search for someone you know.  That’s all there really is to it.  If you have User Profile sync working, then the rest is easy.

This post has been updated for SharePoint 2010 RTM.

I can’t tell you how many SharePoint ECM projects I have seen where there has been a requirement to upload a document somewhere and then move it somewhere else based upon metadata or other criteria.  People have written a good amount of code in the past to make this happen.  Now with the SharePoint 2010 Content  Organizer, the need to write a lot of this code will be gone. Simply put it allows us to create rules that examine the content type and site columns of a document that was uploaded and move it some place else.  Surprisingly, I really haven’t heard very many people talking about this.  This post will show you how to get started with the Content Organizer using the UI and then my next post will show you how to deploy content organizer rules via CAML.  After all, I feel that it is my obligation to always show you how to create things that can easily be moved to your production environment later.

The first place to start is by activating the Content Organizer Site Feature.

ContentOrganizerFeature

Once you activate this feature, a document library called DropOffLibrary is created along with a list called RoutingRules.  You will also find two new links in Site Settings: Content Organizer Settings and Content Organizer Rules.

ContentOrganizerSiteAdministrationLinks

The settings link allows you to configure various things about the content organizer such as forcing all libraries on the site to redirect to the drop-off-library, and allowing you to target rules with destinations on other sites.  You can also enable folder partitioning to automatically create subfolders when one container gets too large (user specified limits).

The rules link takes you to the list where you specify the rules.  It’s pretty simple really.  All rules are based off a content type and then you can specify zero or more conditions based upon site columns on that content type.  You can use the priority option to give one rule more importance over another.  In this case here, I am creating a rule for the Image content type.

ContentOrganizerNewRule1

Scrolling down, I can set the condition based upon specific site columns in the content type.  In this case, I want to move any image that has a copyright date from last year or earlier into an asset library called Old Assets

ContentOrganizerNewRule2

It also has the ability to automatically create subfolders based upon metadata.  This is pretty cool.  For example I could have it create subfolders for each copyright year.  Unfortunately, in Beta 2 this interface has some issues.  The dropdownlist never allows me to select a property.  Also, selecting certain content types causes it to crash.  I also can’t edit an existing rule either.  I’m sure all of that has been fixed by now, but it’s something to be aware of if you are trying this out.

Now we need to try it out, I go to the Drop Off Library and use the Add new document link.  This library looks a little bit different on a few of the screens.

ContentOrganizerUploadDocument

The user gets a warning that his or her document will be moved.  When you add a rule to the organizer that involves a content type, it automatically adds it to the Drop Off Library. 

ContentOrganizerEditDocumentProperties

Once we submit, we get a notification of where the document got moved to.  Notice that it put it in the Old Assets document library.  If there was an issue moving the document or no rules matched, it will leave the document in the Drop Off Library informing the user that it could move in the future.

ContentOrganizerUploadComplete

The only thing I wish it did here was provide a link to the folder of the document as opposed to the document itself.

I really like the content organizer because it allows the user to just upload a document without having to think about where it should go.  This makes it much easier for user’s to get their job done and it helps you keep things organized.  As someone who does mostly ECM work, this is going to save me a lot of time in the future.  Pair this with the new Document Id service and we’ve got a very nice way of dealing with documents.  As I mentioned above, I’ll post on how you can import a set of rules using CAML.  It’s actually pretty simple.

This,my friends, is bad.

EnterpriseSearchContentAccessAccountAdministrator

I see issues caused by this all the times in the forums, so I thought I would write something up on it.  You do not want your default content access account (aka crawl account) to have administrator privileges.  Besides obvious security reasons, there are others.  The main reason is that if the account is an administrator, it can crawl things that you simply don’t want included in your index.  The last thing you want is sensitive information from some list or document library showing up in your search index.  Yes, SharePoint does security trimming, but when you use an admin account, things just get weird.  This also applies to file shares as well.

There are other reasons you don’t want to do this as well.  If you use an administrator account, things that are not checked in may be indexed.  Also, you may run into issues where regular users cannot get any search results at all.  It effectively seems to mess up security trimming.  I’m sure there are many other reasons I’m not thinking of, but the bottom line is if you are using an administrator account, go change it now.  Of course, test before you make any changes.  You may need to assign permissions to your new account.  This could apply to permissions in SharePoint, on a file share, or in a database (if you’re using the BCS/BDC).

Once you change accounts, you need to perform a full crawl on all of your content sources so that inappropriate items get removed.  You might even go as far as resetting all crawled content first.  You should especially consider this if sensitive information is in your search index and you need to get it out fast.

In MOSS 2007, people often extending the search results experience by using the Faceted Search Web Parts.  People really liked these so it looks like Microsoft decided to implement their own version called the RefinementWebPart (also known as the Refinement Panel).  If you are familiar with the Faceted Search Web Parts at all, you will notice there are a lot of similarities in the way things are implemented.  If you’re not familiar with what I’m talking about, it’s this web part that allows users to drill down into a set of search results based upon managed properties and other criteria.  Let’s take a look at a quick example.

RefinementDefault

On the left there, you will see a set of refinements that we get out of the box.  I didn’t have to do anything to configure these at all.  There are a number of refinements built in including file type, site, author, modified date, and taxonomy.  You can also easily create your own based on managed property which we will see here shortly. 

Let’s take a look at some of the options on this web part.  If we edit the page and then edit the web part, we will be able to see the options.  If you’re on a small screen (or a small window in the case of a  VM), you will have to scroll right to see the web part properties.

RefinementWebPartProperties1

Since this is a search web part, you will see the familiar Cross-Web Part query ID.  This should be synced up to whatever else you are using on the page.  Usually it is set to User query.  The next section is where we can configure the refinement.  Make note of the Use Default Configuration checkbox.  If you don’t uncheck this, anything you customize will not be saved.  Each thing that you can filter on is called a category by the RefinementWebPart.  The Filter Category Definition property is an XML field where each category is specified.  We’ll also look at that later in the post.  Other properties to note here.  The Accuracy Index is the number of results it looks at to determine things to refine.  I assume this it mainly there to keep things performing well.  What this does mean is that if there is something unique to refine on but it doesn’t occur until result number 51, then it will not be included.  You can also configure how many categories to display. This is set to 6 by default.  The last thing I will point out is that you can configure how the web part displays its information using XSLT (not shown in the screenshot above).

Now let’s look at how we can add our own managed property to the category list.  You should of course confirm the managed property works and that you can query on it first (do a full crawl if necessary).  We then, just need to take a look at the XML for the Filter Category Definition.

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

<FilterCategories>

  <Category    Title="Result Type"    Description="The file extension of the item"    Type="Microsoft.Office.Server.Search.WebControls.ManagedPropertyFilterGenerator"    MetadataThreshold="5"    NumberOfFiltersToDisplay="4"    MaxNumberOfFilters="0"    SortBy="Frequency"    SortDirection="Descending"    SortByForMoreFilters="Name"    SortDirectionForMoreFilters="Ascending"    ShowMoreLink="True"    MappedProperty="FileExtension"    MoreLinkText="show more"    LessLinkText="show fewer">

    <CustomFilters MappingType="ValueMapping" DataType="String" ValueReference="Absolute" ShowAllInMore="False">

      <CustomFilter CustomValue="Adobe PDF">

        <OriginalValue>pdf</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Email">

        <OriginalValue>eml</OriginalValue>

        <OriginalValue>msg</OriginalValue>

        <OriginalValue>exch</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Excel">

        <OriginalValue>odc</OriginalValue>

        <OriginalValue>ods</OriginalValue>

        <OriginalValue>xls</OriginalValue>

        <OriginalValue>xlsb</OriginalValue>

        <OriginalValue>xlsm</OriginalValue>

        <OriginalValue>xlsx</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Image">

        <OriginalValue>tif</OriginalValue>

        <OriginalValue>tiff</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Lotus Notes">

        <OriginalValue>nsf</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="One Note">

        <OriginalValue>one</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="PowerPoint">

        <OriginalValue>odp</OriginalValue>

        <OriginalValue>ppt</OriginalValue>

        <OriginalValue>pptm</OriginalValue>

        <OriginalValue>pptx</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Publisher">

        <OriginalValue>pub</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Text">

        <OriginalValue>odt</OriginalValue>

        <OriginalValue>txt</OriginalValue>

        <OriginalValue>url</OriginalValue>

        <OriginalValue>csv</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Visio">

        <OriginalValue>vdw</OriginalValue>

        <OriginalValue>vdx</OriginalValue>

        <OriginalValue>vsd</OriginalValue>

        <OriginalValue>vss</OriginalValue>

        <OriginalValue>vst</OriginalValue>

        <OriginalValue>vsx</OriginalValue>

        <OriginalValue>vtx</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Webpage">

        <OriginalValue>ascx</OriginalValue>

        <OriginalValue>asp</OriginalValue>

        <OriginalValue>aspx</OriginalValue>

        <OriginalValue>htm</OriginalValue>

        <OriginalValue>html</OriginalValue>

        <OriginalValue>jhtml</OriginalValue>

        <OriginalValue>js</OriginalValue>

        <OriginalValue>mht</OriginalValue>

        <OriginalValue>mhtml</OriginalValue>

        <OriginalValue>mspx</OriginalValue>

        <OriginalValue>php</OriginalValue>

        <OriginalValue></OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="XML">

        <OriginalValue>xml</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Word">

        <OriginalValue>doc</OriginalValue>

        <OriginalValue>docm</OriginalValue>

        <OriginalValue>docx</OriginalValue>

        <OriginalValue>dot</OriginalValue>

        <OriginalValue>nws</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Zip">

        <OriginalValue>zip</OriginalValue>

      </CustomFilter>

    </CustomFilters>

  </Category>

  <Category    Title="Site"    Description="Which site this document is from"    Type="Microsoft.Office.Server.Search.WebControls.ManagedPropertyFilterGenerator"    MetadataThreshold="5"    NumberOfFiltersToDisplay="4"    MaxNumberOfFilters="20"    SortBy="Frequency"    SortByForMoreFilters="Name"    SortDirection="Descending"    SortDirectionForMoreFilters="Ascending"    ShowMoreLink="True"    MappedProperty="SiteName"    MoreLinkText="show more"    LessLinkText="show fewer" />

  <Category    Title="Author"    Description="Use this filter to restrict results authored by a specific author"    Type="Microsoft.Office.Server.Search.WebControls.ManagedPropertyFilterGenerator"    MetadataThreshold="5"    NumberOfFiltersToDisplay="4"    MaxNumberOfFilters="20"    SortBy="Frequency"    SortByForMoreFilters="Name"    SortDirection="Descending"    SortDirectionForMoreFilters="Ascending"    ShowMoreLink="True"    MappedProperty="Author"    MoreLinkText="show more"    LessLinkText="show fewer"    />

  <Category    Title="Modified Date"    Description="When the item was last updated"    Type="Microsoft.Office.Server.Search.WebControls.ManagedPropertyFilterGenerator"    MetadataThreshold="5"    NumberOfFiltersToDisplay="6"    MaxNumberOfFilters="0"    SortBy="Custom"    ShowMoreLink="True"    MappedProperty="Write"    MoreLinkText="show more"    LessLinkText="show fewer" >

    <CustomFilters MappingType="RangeMapping" DataType="Date" ValueReference="Relative" ShowAllInMore="False">

      <CustomFilter CustomValue="Past 24 Hours">

        <OriginalValue>-1..</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Past Week">

        <OriginalValue>-7..</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Past Month">

        <OriginalValue>-30..</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Past Six Months">

        <OriginalValue>-183..</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Past Year">

        <OriginalValue>-365..</OriginalValue>

      </CustomFilter>

      <CustomFilter CustomValue="Earlier">

        <OriginalValue>..-365</OriginalValue>

      </CustomFilter>

    </CustomFilters>

  </Category>

  <Category    Title="Managed Metadata Columns"    Description="Managed metadata of the documents"    Type="Microsoft.Office.Server.Search.WebControls.TaxonomyFilterGenerator"    MetadataThreshold="3"    NumberOfFiltersToDisplay="3"    MaxNumberOfFilters="20"    ShowMoreLink="True"    MappedProperty="ows_MetadataFacetInfo"    MoreLinkText="show more"    LessLinkText="show fewer" />

  <Category    Title="Tags"    Description="All managed metadata of the documents and social tags"    Type="Microsoft.Office.Server.Search.WebControls.TaxonomyFilterGenerator"    MetadataThreshold="3"    NumberOfFiltersToDisplay="3"    MaxNumberOfFilters="20"    ShowMoreLink="True"    MappedProperty="ows_MetadataFacetInfo,popularsocialtags"    MoreLinkText="show more"    LessLinkText="show fewer" />

</FilterCategories>

I went ahead and posted the entire XML because it’s worth seeing.  For the most part the schema is pretty easy to follow.  The Category element defines each thing to refine and then it has some basic configuration items such as the number of filters to display.  You specify the name of the managed property to use in the MappedProperty attribute.  The MetadataThreshold property is the number of results that have to be returned with that property in order to do refinement.  If you are familiar with the schema used by Faceted Search, you will see a similar concept with the CustomFilter elements.  These allow you map a value into something more readable.  For example instead of display xlsx, it displays Excel. 

One more thing I will point out is that the Category element has a Type attribute.  So far I have seen ManagedPropertyFilterGenerator and TaxonomyFilterGenerator.  These both inherit from RefinementFilterGenerator.  None of its sealed surprisingly which means you could actually write your own custom filter for the refinement web part.  That’s pretty cool.  I’m not sure why I would need to yet, but you never know what you might want to be able to customize.

I want to add my own managed property, so I just add a category to the end like this.

<Category    Title="Color"    Description="Use this filter to restrict results by color" Type="Microsoft.Office.Server.Search.WebControls.ManagedPropertyFilterGenerator"    MetadataThreshold="5"    NumberOfFiltersToDisplay="4"    MaxNumberOfFilters="20"    SortBy="Frequency"    SortByForMoreFilters="Name"    SortDirection="Descending"    SortDirectionForMoreFilters="Ascending"    ShowMoreLink="True"    MappedProperty="Color"    MoreLinkText="show more"    LessLinkText="show fewer"    />

All I did was copy the category that was used for author and it works great.  You can tweak the individual settings if you like.  Here is what my custom managed property looks like.

RefinementColor

How does this all work though.  Well it’s pretty simple.  Just copy any link that the web part displays and we’ll see that it makes use of the new r query string property.  It will be URL encoded but you can easily decode it and see the magic.  In this case if I want to view Red products, the query string has a r parameter of the following.

r=color="Red"

If I wanted to view files modified in the last 24 hours, it uses the new >= operator.

r=write>="3/14/2010”

Don’t you just love that you can easily query against dates now using the keyword query syntax?  I think the r keyword is very interesting and I think it opens the door for some very interesting customizations of the search results page in the future.

One common thing people have asked for in the MSDN Search Forums a lot (besides the obvious things like wildcard search) is the ability to get results that contain just documents.  In this case, people just want documents, no list items or folders, or sites, or anything like that.  It wasn’t terribly difficult to do in MOSS 2007 and I even wrote a post about it, but now there is another way this can be done.  We’ll be looking at the CRWP in more detail pretty soon, but I thought this would be something quick to mention.  Consider the following search results.

EnterpriseSearchResultsAccountingAll

From the screenshot above you should see that I have documents and list items returned in my search results.  We want to filter that down to just documents.  Before we had to create a scope to handle it.  Now we can use the new Append Text to Query property.  Take a look.

CoreResultsWebPartAppendQuery

This new textbox allows you to append whatever you want to what the user searched for.  In my case I am adding IsDocument:”1” to the query to restrict our search to documents, but you could add whatever you can think of to your query here.  After adding it, here is what my search results look like.

EnterpriseSearchResultsAccountingDocuments

My results only contain documents now.  This may just be an issue with Beta 2, but it causes the record count to be incorrect and the pager to think there are additional pages.  Anyhow, I think this property will be quite useful.  This functionality was actually already sort of present in MOSS 2007.  There is an undocumented query string parameter, a, that does this same functionality.  I stumbled upon it once when I was using reflector on the CRWP.

You might have seen my last post on People Search and were so excited to try it out only to find that you only have a Basic Search Center template available or the search center you have already created doesn’t have a People tab.  By default, on the New Site menu, you are only able to create a Basic Search Center.  This site template is functional but it doesn’t give you the ability to do a people search.  This effectively corresponds to the Search Center site template in MOSS 2007. 

NoSearchCenter

There are two new search center templates available though Enterprise Search Center and FAST Search Center.  However, to see those, you need to activate the Enterprise Features on your site collection.

EnterpriseSiteCollectionFeatureDeactivated

Once you activate them, you’ll see the two new site templates available on the New Site menu.  Don’t you just love that new Silverlight app to pick new sites?

NewSiteSearchCenters

The Enterprise Search Center is the one you want (unless you actually got FAST working).  This site template corresponds to the old publishing site template Search Center with Tabs back in MOSS 2007.  You also had to do the same thing in the last version of SharePoint as well.

Maybe you hadn’t noticed, but talking about Search is my favorite topic in SharePoint (with ECM closely following it). :)  This is why I am so excited about the new features we are seeing in Enterprise Search.  Wildcards and built in refinement were enough to bring a tear to my eye, but People Search is what I think a lot of people will be impressed by.  You see People Search is the gateway to many of the new social aspects in SharePoint 2010.  Your old stuffy CIOs are going to absolutely hate it (since they were probably the ones that blocked twitter and facebook at your organization).  The new hip CIOs that find value in social media are going to totally embrace it and organizational efficiency and the sense of community is really going to go up.  Not to tangent on the whole social thing though, let’s just take a look today at how it’s easier to find people in SharePoint 2010.

Let’s consider my great new fictitious company.  It’s not a big company but there are a number of employees.  Ok, so my fake company isn’t as complete as the one we have seen at the demos at SPC or anything, but hopefully you get the point.  Consider the following users stored in Active Directory.

PeopleSearchActiveDirectory

To work with People Search, you must first have successfully configured user profile synchronization, set up a connection, and then done a full crawl on your Local SharePoint Sites content source.  If your user profile synchronization service isn’t working, do yourself a favor and just reinstall because you will never get it to work.  The content source still uses the sps3 protocol to crawl user profiles.  If no people are returned when you search make sure you have a content source setup using that protocol handler.  I’ll probably write another post on how to set this up pretty soon.

To get started with People Search we start with an Enterprise Search center and click on the people link which gives us a plain search box like the one below.

PeopleSearchStart

Let’s say that I need to lookup the phone number for my IT Director, Michael Adams.  I can of course type his full name in like this and get a result (this is what we basically had in SharePoint 2007).

PeopleSearchResultsFullName

We get a result as expected, but you know, I really don’t call him Michael, I call him Mike.  Shouldn’t that return me a result too?

PeopleSearchResultsPhonetic

Absolutely!  In SharePoint 2007, if you asked for that people would look at you like you wanted the impossible.  Now it works beautifully.    Clicking on the user’s name takes you to the profile page where all of the cool social stuff happens.  I could do several posts on it alone, but here is a quick look at what it looks like.  It allows you to see the org chart along with recent activities along with a corporate style facebook.  Very cool.

PeopleSearchProfile

What else can this thing do though?  Say we want to look up our Team Lead, Richard Jackson.  He doesn’t go by Richard though.  He goes by *** (no jokes please).  Will that yield a result?

PeopleSearchResultsPhonetic2

How cool is that.  The search engine seems to understand nicknames.  So this really isn’t necessarily happening phonetically, it just has a nice thesaurus in it now, but at SPC, we did see search examples where things were spelled phonetically and it worked.  I haven’t had a ton of luck getting that to work.  I also saw it work with non-English names.  Hopefully I can demo that stuff too pretty soon, but I still thought these things were worth showing off.

Let’s do one more example though.  Let’s say I know the last name of the person I need to contact is Williams.  We’ll pretend I am lazy and I only type in Willi in the search box.  Will that give me a result?

PeopleSearchNoResults

The answer this time is actually no.  People Search does support wildcards now, but it is not on by default.  Change the query to Willi* and we’re in business.

PeopleSearchResultsWildcard

It returns matches in both the first and last name.  Very cool.  I’d love to be able to inherit from the PeopleCoreResultsWebPart to add an option to allow wildcards all the time, but Harneet Sidhana [MSFT] does not think we should be able to.  Apparently the web part is perfect and no one would possibly ever want to change it.  Ok, so the web part is pretty cool, but I would like to make changes and that’s just not going to be possible. :-(

Stepping aside from that tangent, I have to mention the Organization Browser.  It’s a cool use of Silverlight that makes it very easily to browser the organization. 

PeopleSearchOrganizationBrowser

This picture doesn’t do it justice.  It certainly would be a lot cooler if I had pictures uploaded for all of my users.  From all of this, hopefully you can see that it should be a lot easier for end users to find people in your organization.  I’m really excited about these new features.

This is kind of a weird one so I thought it was definitely worth posting.  Recently we were doing some integration testing where an external application was linking directly to specific folders.  The users would then upload a document in that folder and then they could not find their file.  Where was the file?  Well it was sitting in the root folder for some reason.  Consider the following URL that was being used.

http://moss-server/MySiteCollection/MySite/DocumentLibrary/SubFolder

This can also be expressed like this using the RootFolder parameter.

http://moss-server/MySiteCollection/MySite/DocumentLibrary/Allitems.aspx?RootFolder=/MySiteCollection/MySite/DocumentLibrary/SubFolder

The application linking to us was using a path very similar to the ones above.  However, when the user tried to upload the file after coming from that external application, the file they uploaded would be sitting in the root folder every time.  It was strange because, the user was in the correct folder when they followed the link, but when they clicked Upload I noticed the breadcrumb did not have the subfolder listed in it.  When I tried browsing to the folder in SharePoint and uploading it worked correctly.  Something strange was definitely going on.  I started examining everything and I took a look at the link they were using to get to us.  Here is what they were using.

http://moss-server/mysitecollection/mysite/documentlibrary/subfolder

Notice the difference?  Exactly, the URL they were using was all lower case. I thought surely case sensitivity wouldn’t be the cause.  I corrected the case in the application and sure enough files started going into the correct folder.  I couldn’t find anything on the internet about this at all until I discovered the case sensitivity and found this post that confirmed a similar issue.  If you don’t believe me, go to any document library on your server and try changing the case and see what happens. :)

What it comes down is that the case matters in the URL on everything after the server name.  I created the site collection as MySiteCollection (ok no I really don’t have a site collection named this, but this is an example :) ), so that means any time you have a URL the case better match.  The problem is you might not remember how you set the case up when you created the site, site collection, or document library.  If you don’t there is an easy way to figure it out, just browse to the subfolder you want and copy the URL from your browser.  If you URL decode the RootFolder querystring parameter you will see the casing that should be used.  I hope this helps in case you happen to run into it.

Ok, so the title may be overly dramatic, but the BCS is really freaking cool now.  With SharePoint 2010 just around the corner, now is the time to start learning how you can leverage it.  I’ll be talking about the Business Connectivity Services at the Tulsa SharePoint Interest Group on 4/12.  If you’re not familiar with everything yet, here is what has changed.  The Business Data Catalog from MOSS 2007 had morphed into the new Business Connectivity Services also known as the BCS.  To add to the confusion, there are places where you will still see term BDC lying around such as the case of the BDC entity model (although maybe the name has changed by now).  With 2010, we get the ability to easily and quickly create new application definitions using SharePoint Designer 2010.  On top of that the data we have abstracted through the BCS can be manipulated in something new called the external list.  It’s so cool that in fact you can edit and do all CRUD operations on that external list and the changes get persisted to the backend database (or web service, etc).  If you’re not familiar with the BCS yet, this is a must-see session.

If you are going to make it to the event, please RSVP.  Thanks.

When it comes to SharePoint deployments, I try to automate everything I can.  I don’t like manual steps especially when it comes to setting up security.  A common task when deploying any sites is setting up security in some manner.  Today I am going to cover how to easily store definitions your SharePoint security groups in an XML file.  We’ll use LINQ to XML to make reading the file a breeze, and then we’ll use the SharePoint object model to create the groups and add users (or AD groups).   I’ve blogged on how to create a group before, but we’re going to take this a step further by giving you code that you can easily add to a feature receiver or console application.  First let’s take a look at the XML file we’re going to use.

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

<Groups>

  <Group Name="My Custom Read Group" Owner="SHAREPOINT\GroupOwner" Description="Readonly Permission Group" PermissionLevel="Read">

    <User Name="SHAREPOINT\TestUser1" />

    <User Name="SHAREPOINT\TestGroup1" />

    <User Name="SHAREPOINT\TestGroup2" />

  </Group>

  <Group Name="My Custom Contributors Group" Owner="SHAREPOINT\GroupOwner" Description="Contributors Permission Group" PermissionLevel="Contribute">

    <User Name="SHAREPOINT\TestGroup3" />

  </Group>

</Groups>

In this file, I am defining two SharePoint groups.  One that will have read access and one that will have contribute access.  I store the required information needed by the Add method on the SPGroupCollection object.  I then have one or more User elements with the name of my Active Directory user or group.  I tried to keep my XML schema pretty simple.  You can customize it obviously how you want, you would just have to alter your LINQ queries. 

Let’s take a look at the code we need to make this happen.  I won’t go into as much detail of the object model since I went into it pretty well on my last post.  We’ll just focus on how we use LINQ to XML to read the information we need and then have it create our groups.  My method is called CreateGroups and it takes an SPWeb object and a string with the filename of the XML document.

private void CreateGroups(SPWeb currentSite, string groupsFilename)

{

    // get the xml document from the feature folder

    XDocument groupsXml = XDocument.Load(groupsFilename);

 

    // create a new anoynmous type with the group data

    var groups = from sharePointGroup in groupsXml.Root.Elements("Group")

                select new

                {

                    Name = sharePointGroup.Attribute("Name").Value,

                    Owner = sharePointGroup.Attributes("Owner").Any() ? sharePointGroup.Attribute("Owner").Value : null,

                    Description = sharePointGroup.Attributes("Description").Any() ? sharePointGroup.Attribute("Description").Value : string.Empty,

                    PermissionLevel = sharePointGroup.Attributes("PermissionLevel").Any() ? sharePointGroup.Attribute("PermissionLevel").Value : null,

                    Users = sharePointGroup.Elements("User").Any() ? sharePointGroup.Elements("User") : null

                };

 

    // iterate through the groups and create the groups

    foreach (var sharePointGroup in groups)

    {

        // only create the group if it does not exist

        if (!ContainsGroup(currentSite.SiteGroups, sharePointGroup.Name))

        {

            // add the owner to the web site users

            currentSite.EnsureUser(sharePointGroup.Owner);

 

            // add the group

            currentSite.SiteGroups.Add(sharePointGroup.Name, currentSite.SiteUsers[sharePointGroup.Owner],

                currentSite.SiteUsers[sharePointGroup.Owner], sharePointGroup.Description);

        }

 

        // add the users to the group

        AddUsersToGroup(sharePointGroup.Name, sharePointGroup.Users, currentSite, sharePointGroup.PermissionLevel);

    }

}

This seems like kind of a big method at first, but it’s really not that bad.  To keep things simple, I haven’t included any exception handling code.  We are really just querying the XML document, iterating through each group element inside of it, creating the groups, and then adding the users to the group.  The first line of code just creates an XDocument object.  We then construct a LINQ to XML query.  What we want is to return data from each Group element in the document.  The Add method doesn’t like nulls, so we check for them and use string.Empty if the value does not exist in the file.  The one case where I don’t do this is for the Name of the group.  If that is not present, I would rather the process throw an exception.  As for the Users assigned to the group, I grab all of them and add them to our anonymous type like this.

Users = sharePointGroup.Elements("User").Any() ? sharePointGroup.Elements("User") : null

This gives us an IEnumerable<XElement> that we can pass to a method later to add each Active Directory user (or group) to the SharePoint group.  Once we execute the query, we iterate through each group element.  The first thing we have to do is make sure that the group does not exist.  Of course there is no way to do that other than using the try/catch technique.  I will usually wrap this in an extension method, but for today’s purpose, we’ll just call a method to check.

private bool ContainsGroup(SPGroupCollection groupCollection, string index)

{

    try

    {

        SPGroup testGroup = groupCollection[index];

        return true;

    }

    catch (SPException e)

    {

        return false;

    }

}

Lame I know.  I’m so happy there are ways to get around this in SharePoint 2010.  Then this starts to look like code from the previous post.  We call .EnsureUser to make sure the domain account of the group owner is registered with the site.  We then just call the Add method with the Name, Owner, default user, and description.  Again there is more info on the previous post about that method call.  Assuming the group is created, we can then add the users to the group.  We call a new method AddUsersToGroup which takes the groupName, the users element, an SPWeb, and the permission level. 

The first thing we do is query the names of the Active Directory users (or groups).  Here we are just grabbing it from the Name attribute of the User element.  I probably could have condensed this query, but at least it’s easy to read.  We then add each user (or group) from the User elements to the group.  If you are curious about the empty parameters, take a look at the previous post.  If you are going to run into an exception, it’s going to be here.  If the group failed to be created or if the user does not exist (i.e.: you typed it in the XML file wrong), this line will throw an exception.

private void AddUsersToGroup(string groupName, IEnumerable<XElement> users, SPWeb currentSite)

{

    // select the username from the xml document

    var userList = from user in users

                   select new

                   {

                       Name = user.Attribute("Name").Value

                   };

 

    // add the users to the sharepoint group

    foreach (var user in userList)

    {

        currentSite.SiteGroups[groupName].AddUser(user.Name, string.Empty, user.Name, string.Empty);

    }

}

Now, we’re almost done.  The last thing we need to do is set the permission level on the group.  This is where we specify whether the group has readonly, contribute, full control, etc access to the site.  Be sure and get the name on the permission level right otherwise you will get an exception.  I’ve also blogged about how to assign permission levels before.  Today’s post is really just a great practical use of putting together the things I have posted on before.

private void SetRoleDefinitionBinding(string groupName, SPWeb currentSite, string permissionLevel)

{

    // add the read role definition to the site group

    SPRoleAssignment roleAssignment = new SPRoleAssignment(currentSite.SiteGroups[groupName]);

    roleAssignment.RoleDefinitionBindings.Add(currentSite.RoleDefinitions[permissionLevel]);

    currentSite.RoleAssignments.Add(roleAssignment);

    currentSite.Update();

}

Effectively you create a new SPRoleAssignment by passing it a SPGroup object.  You then add a binding using the existing RoleDefinitions on the site.  You then add the assignment to the site and of course call .Update() so things get saved.

That’s really all there is to it.  This is a great use of combining information from my previous posts into something that you can use everyday to set security on your sites.  How you execute this code is up to you.  I’ve used it in a feature receiver and in a console application before.  Setting up security through the UI is very slow and painful.  Once you create it on one server, there is no way to move it to another server and that’s not a lot of fun.  This should help you with that and eliminate those nasty manual steps in your deployment process. 

SharePoint is hot in Oklahoma City right now with two new groups forming in the last month.  I am excited to be the first speaker at the new Oklahoma City SharePoint User Group on March 29th at 6pm.  I’ll be giving an introduction to SharePoint Enterprise Search where we will learn how to crawl content from SharePoint, File Shares, and maybe even line of business systems such as a database.  We’ll be demoing with SharePoint 2010, but most everything you will learn will also apply to MOSS 2007.  We’ll probably do a Q&A session afterwards as well.  I’m excited to see SharePoint users get together in OKC now and I’m looking forward to this meeting.

I am excited to be serving as Vice President this year for the Tulsa SharePoint Interest Group.  This looks to be a great year as we hope to grow the group and make it better than ever.  We don’t have all the plans laid out yet, but we do hope to be able to communicate more with the members and be more a part of your day to day SharePoint lives. :)  Here are the officers and roles for this year.

David McCollough – President

Corey Roth – Vice President

Chris McLean – Treasurer

Dennis Bottjer – Director of Member Services

Mohamed Yasuf – Director of Technology