Presentation: SharePoint 2010 Managed Metadata vs. SQL 2012 Master Data Services

Posting of a presentation I did for the local SharePoint Users’ Group (Orange County, CA) on May 16, 2012. This presentation was a comparison of two technologies that could be used as enterprise taxonomy stores.

What’s Faster? Querying a SharePoint List or Querying the Managed Metadata Service?

Scenario

What the doohickey am I talking about? Well, if you’re a SharePoint Developer that is tasked with using SharePoint to house referential metadata, you used to have 1 option. Prior to SharePoint Server 2010, SharePoint Lists were typically used to store this information. By using Lists as the datastore for referential information, we were able to easily provide end users with the ability to manage this information without having to spend any time creating a UI for them. This also enabled the end users to create and manage values which could then be consumed by various applications whether it be basic SharePoint List Forms via Lookup columns, InfoPath forms or custom Web Parts and ASP.NET forms.

SharePoint Server 2010 takes this concept to the next level by introducing the Managed Metadata Service that enables users ranging from Site Administrators to Enterprise Information Architects the ability to manage this information at an enterprise level. However, this blog post will not be used to delve into the features of the Managed Metadata Service. To learn more about the features of Managed Metadata Service and why you would want to use a metadata and taxonomy store in the first place, please see here. In this blog post, I will be discussing query performance between the two options (SharePoint Lists vs. Managed Metadata Service Term Stores).

Testing

So how did I run these tests… First I set out to create a simple Windows Form application as my testing vehicle. In this application, I have a variety of fields and buttons that let me toggle back and forth between querying a specific SharePoint list for values via CAML and being able to query the MMS Term Store for specific terms.

Here’s some sample code that I used to access the goodies:

List CAML Query

string queryString = "<Where><And><Eq><FieldRef Name=\"Platforms\" /><Value Type=\"LookupMulti\">" + 
InputTerm.Text + "</Value></Eq><Eq><FieldRef Name=\"Status\" /><Value Type=\"Choice\">Active</Value></Eq></And></Where></Query>";
bool found = false;
using (SPSite site = new SPSite("https://sharepoint/")) 
{  
   using (SPWeb web = site.OpenWeb())         
   {          
      SPList productsList = web.Lists["Products"];                 
      SPQuery query = new SPQuery();                 
      query.Query = queryString;                 
      SPListItemCollection products = productsList.GetItems(query);
      if (products.Count > 0)                 
      {
         found = true;
      }
      foreach (SPListItem product in products)
      {
         Results.Text += product["Title"].ToString() + "\n";
         Results.Text += found.ToString() + "\n";         
      }
   }
}

MMS Query  

using (SPSite site = new SPSite(siteURL))
{
   TaxonomySession session = new TaxonomySession(site);
   TermCollection terms = session.GetTerms(InputTerm.Text, true);
   foreach (Term term in terms)
   {
      Results.Text += term.Name + "\n";
      if (term.Terms.Count > 0)
      {
         found = true;
         foreach(Term childTerm in term.Terms)
         {
            Results.Text += childTerm.Name + "\n";
         }
      }
   }
}

As a side note, looking at the sample code above, we can see that it is much easier to query for a Term from a MMS Term Store when compared to querying a SPList via CAML because, simply, there’s no CAML involved! You may have also noticed that there’s no need to access the SPWeb objects, so that saves us some memory as well.

The results of these tests were performed against two datasets of relative size. In the first round of testing, I had an SPList with about 40 List Items up against a Term Store with also about 40 Terms. The result set size was configured for 10 each. In the 2nd round of testing, I had increased the number of objects in each container to almost 500 each with a result set size of 100.

Test Results

Small Query Sets
Query Objects =< 40
Result Set Size = 10

Medium Query Sets
Query Objects =< 500
Result Set Size = 100

Summary

As you can see from the test results above, retrieving metadata via the MMS is faster, uses less memory and the code is simpler in comparison to performing CAML lookups on lists. Query times against the MMS seem to be stable/unchanged when the datasets are increased from 40 to almost 500 objects. The CAML query times against lists with similar number of objects were roughly doubled or more on result sets of 100. So CAML query times increased as the list size increased vs. MMS query times did not increase with an increase in the number of terms in the term store. What do you think? :)

SPSLA Presentation: Managed Metadata and Taxonomies in SharePoint 2010

SharePoint Saturday Los Angeles 2012 was this past weekend in Long Beach and as promised to my session attendees, I’m posting the presentation below. Shout out to Chris McNulty as much of the content came from him. Also, thank you to all of the organizers, sponsors, speakers, volunteers, and attendees for making this event possible :)

You may want to view or download the PowerPoint from SlideShare for the complete content since I had embedded quite a few notes throughout the slides.

SharePoint Dev: Getting null TaxonomySession and ArgumentOutOfRangeException errors on TermStore objects

Off to a new project and today I’m exploring the SharePoint Managed Metadata and Taxonomy APIs. I had created a simple Windows Form application to test with and as I was trying to read some of terms out of the Managed Metadata Service Application, I came across a weird error that took me a while to figure out.

Code Snippet:

using (SPSite site = new SPSite(siteURL))             
{                 
   TaxonomySession session = new TaxonomySession(site);
   TermStore termStore = session.TermStores["Managed Metadata Service"];
   TermCollection terms = termStore.GetTerms(InputTerm.Text, true);
   foreach (Term term in terms)                 
   {                     
      Results.Text += term.Name + "\n";                     
      if (term.Terms.Count > 0)                     
      {                         
         foreach(Term childTerm in term.Terms)                         
         {                             
            Results.Text += childTerm.Name;                         
         }
      }
   }
}

Error Message:

ArgumentOutOfrangeException was unhandled. Specified argument was out of the range of valid values. Parameter name: index.

Cause:

Digging through the properties of the session object showed that it was null so for some reason I wasn’t even able to get a handle on a TaxonomySession object, which is the entry point to getting access to any of the information inside of your Managed Metadata Service. When cross-referenced with the Event Viewer, I see this message:

Event ID: 8089
Source: SharePoint Server
Task Category: Taxonomy
Level: Warning
The Managed Metadata Service 'Managed Metadata Service' 
is inaccessible. The web application does not have 
sufficient permissions.

Solution:

Since I was running Visual Studio and my test Windows Form application as myself (a user), I went to Central Administration to find out whether or not I had access to the service.

Lo and behold, it looks like only service accounts had access to the Managed Metadata Service. After I added my account to have at least Read Access to the Term Store, all was well and I was on my merry way again. :)

Case Study on Troubleshooting a Failed SharePoint User Profile/FIM Synchronization, Bad CMYK User Photos and Disappearing User Profiles

What a mighty doozy this one was… a couple weeks, a few hairs and 7.4 panic attacks later, I think I’ve had one of the most twisted SharePoint issues I’ve ever had to deal with. In my 6 years of working with SharePoint I’ve only had to open up a support case with Microsoft one other time. I take great pride in being able to solve stuff on my own but this was just one of those that had me going in circles. Hopefully this write up will help others in the future.

Environment

  • SharePoint 2010 Enterpise December 2011 Cumulative Update 14.0.6114.5000
  • 3 Load Balanced WFEs
  • 2 Application Servers
  • 1 User Profile Service Application
  • User Profile Service and User Profile Synchronization Service both running on APP1 server
  • User Profile Service Application has 4 Custom User Profile Properties and 1 Property set to export to Active Directory (Picture to thumbnailPhoto)

Since there were quite a few variables in troubleshooting the problems (which I haven’t mentioned yet), I’ll outline all the happenings in a timeline format.

February 2012: Users start to use My Sites to upload their pictures
We’re synching these pictures to Active Directory so that they can be re-used for the users’ Lync and Outlook profile pictures.

February 11, 2012: Deployed December 2011 Cumulative Update
This is a story for another day but in the end everything turned out OK. Lessons learned here: be wary of synchronizations of the User Profile Service after troubleshooting UPA issues after deploying CU. Make sure you disable the “My Site Cleanup Job” and follow the guidance from Joanne Klein here. I learned this the hard way.

February 16, 2012: Installed some Windows Updates
Everything seemed to be normal here.

February 22, 2012: First Reports of Users Not Seeing Their Pictures in Lync & Outlook
Upon inspection of Active Directory and comparing to pictures in SharePoint, there was indeed a mismatch. Photos has not been exported to AD since February 17, 2012. Uh oh, could it have been the Windows Update? Maybe a weekly Timer Job somewhere that regressed from the December 2011 CU? Maybe a combination of both? Errors reported in the FIM MIIS Client and Event Viewer pasted below:

FIMSynchronizationService Event ID 6126

The management agent “MOSS-63649d6d-ab5f-4eda-8c6a-6e2b65a419c7″ completed run profile “MOSS_DELTAIMPORT_51f827d4-b836-4851-89de-daf209327762″ with a delta import or delta synchronization step type. The rules configuration has changed since the last full import or full synchronization.

User Action
To ensure the updated rules are applied to all objects, a run with step type of full import and full synchronization should be completed.

FIMSynchronizationService Event ID 6801

The extensible extension returned an unsupported error.
The stack trace is:

“System.Net.WebException: The remote server returned an error: (404) Not Found.
at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request)
at System.Net.WebClient.DownloadData(Uri address)
at Microsoft.Office.Server.UserProfiles.ManagementAgent.ProfileImportExportExtension.DownloadPictures(ProfileChangeData[] profiles)
at Microsoft.Office.Server.UserProfiles.ManagementAgent.ProfileImportExportExtension.Microsoft.MetadirectoryServices.IMAExtensibleFileImport.GenerateImportFile(String fileName, String connectTo, String user, String password, ConfigParameterCollection configParameters, Boolean fFullImport, TypeDescriptionCollection types, String& customData)
Forefront Identity Manager 4.0.2450.34″

After trying to troubleshoot this for a whole day with no leads, I threw in the towel and phoned MSFT support.

February 23-27, 2012: Working with MSFT SharePoint Support Engineer Trying to Restore the UPA
In short, we worked on trying to create new UPAs in production as well as staging with restored Profile and Social Databases to no avail. There was one restore scenario that seemed to work when we left it on the Friday, but when I came in on Monday, the sync errors were happening with the new UPA as well. I also learned that there are quite a bit of URLs that are hard coded into the User Profile and Social Databases. For example, when we restored the databases to the QA environment, users in the QA environment were getting redirected to their Production My Sites. I once again thew in the towel with this Support Engineer when he suggested that there will be no way to recover the User Profile and Social DBs and that his seniors recommend that all of my users will have to recreate all of their profile information. This was completely unacceptable. By the way, if you find yourself on a support call with MSFT or anyone else for that matter, don’t always be so willing to do whatever they recommend. There were quite a few instances where I had to disagree with a troubleshooting step as it would have made my production environment unavailable to my users or result in data loss. So try to use your own best judgement and common sense when working with someone that doesn’t have to deal with your end users.

February 28-March 1, 2012: Waiting Around for Call from Escalation Engineer… But Had Some Revelations
Was supposed to hear back from someone after 48 hours, but did not. Instead, I had to go through some other routes to get some attention via my Technical Account Manager (TAM). In the meantime, I was sleuthing around in regards to the 404 error message and discovered that there was something awry with some of the user profile pictures. I recorded this finding/bug here. I didn’t have a chance to validate that the FIM Sync errors are related to the bad CMYK pictures, but that was the hunch…

March 2, 2012: Got another Support Engineer and… success! Sort of…
We spent about 4 hours in total and eventually reached a semi-conclusion. So this whole entire time, a lot of attention was being paid to the UPA as that was the most probable cause for failed syncs with Active Directory. This time, instead of spending too much time trying to recreate and restore the UPA in various stages, I was able to change the troubleshooting direction to focus more on the user profile pictures instead. With this lead, the Support Engineer suggested that we remove the Picture mapping to Active Directory and then perform a Full Synchronization. Before running the Full Sync, I made mention that the last time I did this, all of  the profiles got deleted. After disabling the My Site Cleanup Job, we ran the Full Sync and were indeed able to observe that all the user profiles were marked for deletion using

Select * from userprofile_full (nolock) where bDeleted = 1

on the User Profile Database. That was pretty nerve-racking. We then proceeded to run a few more syncs to confirm that the user profiles were flipped back to the do not delete state.We also confirmed that there were no more sync errors. Woohoo! User profile pictures were definitely the problem, causing the FIM sync to fail.

Resolving the bad CMYK pictures problem
This seems to be a bug with SharePoint and the workaround I’ve found is to delete the offending thumbnail (large thumbnail) generated by SharePoint and then replace it with the medium thumbnail (which works). You can follow that thread here. After resolving the picture issue, I was then again able to successfully export all the user profile images from SharePoint to Active Directory. So in retrospect, if there was an exception in the sync because of one of these images, FIM will tap out and not even attempt to export any other pictures that are working to Active Directory.

Recap & Lessons Learned

  1. User Profile Service Application and FIM Sync issues do not always require a rebuild of the UPA.
  2. If you rebuild your Sync DB or Connection to Active Directory, you will lose all of your Profile Property Mappings.
  3. If you rebuild your Sync DB or Connection to Active Directory, your next sync (either incremental or full, first incremental will force a full) will result in all of your profiles getting marked for deletion.
  4. To prevent your User Profiles from getting deleted, disable the My Site Cleanup Timer Job.
  5. Don’t believe the Support Engineer when he says it’s not possible to restore the Profile and Social DBs (YMMV).
  6. Don’t perform recommended actions that may cause downtime or loss of data. Use your common sense and don’t jeopardize your users’ data.
  7. You know your environment the best and sometimes you have to go with your gut on an issue. Having a second pair of eyes and helpful suggestions was definitely appreciated but if I had let the Support Engineers continue their scripts, we would still be trying to recreate and restoring the UPA to no avail.

Synching AlertSite Devices List with a SharePoint List via AlertSite’s REST API + PowerShell

This one’s a little bit more obscure than usual as I’m not sure how many people use SmartBear Software’s AlertSite service or not. If not, then this should be a good demonstration of how we can use PowerShell to connect to remote REST APIs and then sync the parsed XML response/information to a custom SharePoint List :) . On the otherhand, if you don’t care about SharePoint then this demo would also be a good code sample for the .NET crowd as well since AlertSite only has sample code using Perl.

This script assumes that there is a custom SharePoint List created with the following fields:

Title – Single Line of Text (default)
MonitorInterval – Number
BeingMonitored – Yes/No
NotifyOnError – Yes/No
Traceroute – Yes/No
MonitorTimeout – Number

Once you create the fields using the names above, feel free to change the display names. The script will work with the internal field names that are static.

Click here to download the PS1.

PowerShell Script pasted below for search purposes.
You may need to highlight and copy the script below into notepad to be able to read all of the characters that were chopped off by this blog’s layout.

######################################################################################################
# This script will log into AlertSite's REST API and sync the account's devices with a SharePoint List
#
# Author: Henry Ong
#
# AlertSite Documentation: 
# http://help.alertsite.com/AlertSite/RestAPI?skin=clean.nat%2cnat#5_1_Example_of_Report_API
######################################################################################################

############# Start Variables ################
$RESTServer = "https://www.alertsite.com/restapi"
$login = "YourEmailAddress"
$password = "YourPassword"
$postLogin = "<Login><Login>$login</Login><Password>$password</Password></Login>"
$devicesRequest = "<List><APIVersion>1.1</APIVersion><TxnHeader><Request><Login>$login</Login><SessionID>$sessionID</SessionID></Request></TxnHeader><Source></Source></List>"
$SPWebURL = "YourSharePointSiteURL"
$SPListName = "AlertSite Devices"
############# End Variables ##################

[System.Reflection.Assembly]::LoadWithPartialName("System.Net")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")

##############################################
## Setup user agent and login request
##############################################
$encode = [System.Text.Encoding]::UTF8
$loginBytes = $encode.GetBytes($postLogin)

## Creating and posting a request to AlertSite's REST API Login page
$req = [System.Net.WebRequest]::Create($RESTServer + "/user/login")
$req.Method = "POST"
$req.ContentType = "text/xml"
$req.ContentLength = $loginBytes.Length
$req.UserAgent = "AlertSite REST Client/1.0"

$reqStream = $req.GetRequestStream()
$reqStream.Write($loginBytes, 0, $loginBytes.Length)
$reqStream.Close()

## Working with the response sent back from AlertSite
$resp = $req.GetResponse()
$cookie = $resp.Headers["Set-Cookie"]

$responseStream = $resp.GetResponseStream()
$respReader = (New-Object System.IO.StreamReader($responseStream))
$XMLResponse = $respReader.ReadToEnd()
$responseStream.Close()

# Save Session ID and Customer Object ID for subsequent API calls
$XMLEncoded = [xml]$XMLResponse

$sessionID = $XMLEncoded.Response.SessionID
$objCust = $XMLEncoded.Response.ObjCust
$resp.Close()

##############################################
## Getting the list of devices from AlertSite
##############################################

$encode = [System.Text.Encoding]::UTF8
$listDeviceBytes = $encode.GetBytes($devicesRequest)

$req = [System.Net.WebRequest]::Create($RESTServer + "/devices/list")
$req.Method = "POST"
$req.ContentType = "text/xml"
$req.ContentLength = $listDeviceBytes.Length
$req.UserAgent = "AlertSite REST Client/1.0"
$req.Headers.Add("Cookie", $cookie)

$reqStream = $req.GetRequestStream()
$reqStream.Write($listDeviceBytes, 0, $listDeviceBytes.Length)
$reqStream.Close()

$resp = $req.GetResponse()

$responseStream = $resp.GetResponseStream()
$respReader = (New-Object System.IO.StreamReader($responseStream))
$XMLResponse = $respReader.ReadToEnd()
$responseStream.Close()

$XMLEncoded = [xml]$XMLResponse
$resp.Close()

###########################################
# Update SharePoint List
###########################################

$site = New-Object Microsoft.SharePoint.SPSite($SPWebURL)
$web = $site.OpenWeb()
$deviceList = $web.Lists[$SPListName]

foreach($device in $XMLEncoded.Response.TxnList.Txn)
{
    $deviceName = InjectSpaces $device.TxnName
    $interval = $device.TxnDetail.GetAttribute("Interval")
    $monitored = ReturnBoolFromLetter $device.TxnDetail.GetAttribute("Monitor")
    $notify = ReturnBoolFromLetter $device.TxnDetail.GetAttribute("Notify")
    $traceRoute = ReturnBoolFromLetter $device.TxnDetail.GetAttribute("TraceError")
    $timeout = $device.TxnDetail.GetAttribute("TimeOut")

    $listItem = FindDeviceInSharePoint $deviceName

	if($listItem -eq $null)
    {        
        CreateNewListItem $deviceList $deviceName $interval $monitored $notify $traceRoute $timeout
    }

    elseif($listItem -ne $null)
    {
        UpdateListItem $listItem $deviceName $interval $monitored $notify $traceRoute $timeout
    }

}

$spweb.dispose()
$spsite.dispose()

####################################################################################################
## This function will replace the "%20" characters in the string with spaces.
####################################################################################################
function InjectSpaces($string)
{
	$newString = $string.replace("%20", " ")
	return $newString
}

####################################################################################################
## This function will return the SharePoint List Item that represents the device if it exists.
####################################################################################################
function FindDeviceInSharePoint($device)
{
	$listQuery = New-Object Microsoft.SharePoint.SPQuery
	$listQuery.Query = "<Where><Eq><FieldRef Name='Title' /><Value Type='Text'>" + $device + "</Value></Eq></Where>"
	$itemCollection = $deviceList.GetItems($listQuery)
	if($itemCollection.count -eq 1)
	{
		return $itemCollection[0]
	}
	else
	{
		return $null 
	}
}

####################################################################################################
## This function will create a new SharePoint List Item to represent the AlertSite Device if 
## it doesn't exist.
####################################################################################################
function CreateNewListItem($spList, $deviceName, $monitorInterval, $beingMonitored, $notifyOnError, $traceRouteOnError, $monitorTimeout)
{
    $listItem = $spList.Items.Add()
    $listItem["Title"] = $deviceName
    $listItem["MonitorInterval"] = $monitorInterval
    $listItem["BeingMonitored"] = $beingMonitored
    $listItem["NotifyOnError"] = $notifyOnError
    $listItem["Traceroute"] = $traceRouteOnError
    $listItem["MonitorTimeout"] = $monitorTimeout
    $listItem.update()
}

####################################################################################################
## This function will update an existing SharePoint List Item that represents an AlertSite Device.
####################################################################################################
function UpdateListItem($listItem, $deviceName, $monitorInterval, $beingMonitored, $notifyOnError, $traceRouteOnError, $monitorTimeout)
{
    $listItem["MonitorInterval"] = $monitorInterval
    $listItem["BeingMonitored"] = $beingMonitored
    $listItem["NotifyOnError"] = $notifyOnError
    $listItem["Traceroute"] = $traceRouteOnError
    $listItem["MonitorTimeout"] = $monitorTimeout
    $listItem.update()
}
####################################################################################################
## This function will convert the AlertSite attributes for SharePoint consumption.
####################################################################################################
function ReturnBoolFromLetter($letter)
{
    if($letter -eq "y")
    {
        return $true
    }

    elseif($letter -eq "n")
    {
        return $false
    }
}

SharePoint Web Page/Valid Site Monitoring with Email Alerts PowerShell Script

Two scripts in two days? Am I out of my mind?! Yes, no, yes, maybe… Yesterday’s SharePoint Diagnostic Log Monitor + Email Alert PowerShell Script worked so well that I was getting bombarded with too many emails! Yes, they were all critical as far as SharePoint was concerned and I was able to resolve a great majority of them. But some of them just seemed to be inherent to SharePoint and didn’t really affect the end-user experience therefore the criticality of these alerts became less of a priority.

So today I went about bootlegging a different type of monitoring system that would be more indicative of an end user’s experience with the site. With this script, I wanted to connect to the SharePoint sites and see if I get a valid response or valid content back from the server. If I can’t download the page with the System.Net.WebClient.DownloadString() method, then it’ll throw an exception and that’s how I’ll know whether the site is accessible by an end-user or not. If it’s not accessible, then an email will be generated and sent to me. Pretty cool eh?

This is extra useful when used in an environment with load balanced front ends and you want to know exactly which server is having issues. If you want to use this script in this manner, then make sure you create the scheduled tasks for each server.

Sample Email Output:

URL: https://url1
Server: ALW01

Exception: System.Net.WebException: The remote server returned an error: (500) Internal Server Error. at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request) at System.Net.WebClient.DownloadString(Uri address) at DownloadString(Object , Object[] ) at System.Management.Automation.DotNetAdapter.AuxiliaryMethodInvoke(Object target, Object[] arguments, MethodInformation methodInformation, Object[] originalArguments)

===================================

URL: https://url2
Server: ALW02

Exception: System.Net.WebException: The remote server returned an error: (500) Internal Server Error. at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request) at System.Net.WebClient.DownloadString(Uri address) at DownloadString(Object , Object[] ) at System.Management.Automation.DotNetAdapter.AuxiliaryMethodInvoke(Object target, Object[] arguments, MethodInformation methodInformation, Object[] originalArguments)

===================================

You may need to highlight and copy the script below into notepad to be able to read all of the characters that were chopped off by this blog’s layout.

############# Start Variables ################
$urls = @("https://url1", "https://url2")
$emailFrom = "SharePoint.Automation@email.com"
$emailTo = @("email1","email2")
$subject = "SharePoint Down!"
$smtpserver = "SMTPServer"
$server = "ALW01"
# When used in a load-balanced environment where
# each server has host entries to itself, this can help you
# identify which server is having issues.
############# End Variables ##################

[System.Reflection.Assembly]::LoadWithPartialName("System.Net")
$wc = New-Object System.Net.WebClient 
$wc.UseDefaultCredentials = $true
$body = ""

foreach($url in $urls)
{  
    try  
    {   
        $page = $wc.DownloadString($url);   
        if($page.Contains("An unexpected error has occurred.") -or $page.Contains("Cannot connect to the configuration database"))   
        {    
            $body += "<b>URL:</b> " + $url + "<br><br>"    
            $body += "<b>Server:</b> " + $server + "<br><br>"    
            $body += "<b>Exception:</b> " + "Getting a nasty error. Please help me." + "<br><br>"    
            $body += "===================================<br><br>"    
        }  
    }  

    catch [Exception]  
    {   
        $body += "<b>URL:</b> " + $url + "<br><br>"   
        $body += "<b>Server:</b> " + $server + "<br><br>"   
        $body += "<b>Exception:</b> " + $_.Exception + "<br><br>"   
        $body += "===================================<br><br>"   
    }
}

if($body -ne "")
{  
    Send-MailMessage -To $emailTo -Subject $subject -Body $body -SmtpServer $smtpserver -From $emailFrom -BodyAsHtml
}

SharePoint Diagnostic Log Monitor + Email Alert PowerShell Script

Looking for a bootleg SharePoint monitoring and alert system? Well, you’ve come to the right place! I wrote the following PowerShell script to be run as a scheduled task against SharePoint’s Diagnostic Logs. It will not only find the latest log file to analyze, but it will slice and dice through that current log file for the most critical “Critical” events. Once it finds those Critical events that are usually indicative of something really wrong with your environment, it’ll package up all of the messages in an HTML email and send it your way.

You may need to highlight and copy the script below into notepad to be able to read all of the characters that were chopped off by this blog’s layout.

############# Start Variables ################ 
$logDirectory = "D:\Logs\Diagnostic Logs\*.log" 
$emailFrom = "FromEmailAddress" 
$emailTo = @("Email1","Email2") 
$subject = "SharePoint Diagnostics Critical Alert" 
$smtpserver = "EmailServer" 
############# End Variables ##################
$latestLogFile = get-childitem $logDirectory | sort LastWriteTime -desc | select -first 1
$criticalItems = Select-String $latestLogFile.FullName -Pattern "Critical"
if($criticalItems -ne $null) 
{  
     $body = ""  
     foreach($criticalItem in $criticalItems)  
     {   
         $body += "<b>Error:</b> " + $criticalItem.Line + "<br><br>"   
         $body += "<b>Line Number:</b> " + $criticalItem.LineNumber + "<br><br>"   
         $body += "<b>File Path:</b> " + $criticalItem.Path + "<br><br>"   
         $body += "===================================<br><br>"  
     }    

     Send-MailMessage -To $emailTo -Subject $subject -Body $body -SmtpServer $smtpserver -From $emailFrom -BodyAsHtml 
}

 

Adding SharePoint Social Rating Stars to the Homepage of SharePoint Blog Sites

The goal of this adventure was to leverage the SharePoint blog site template and SharePoint Social Rating capabilities to construct an ideation type of site.

But alas, this was one of those things that took me an ungodly amount of time to try and figure out a solution for… hopefully this post will be able to help others solve the same problem. Yes, there are lots of blog posts out there that show how we can add the rating capabilities to individual blog post entries, but I couldn’t find one that described the process for adding the rating stars to the homepage of the blog site template’s summary view web part for each of the posts. With some help with the MSDN SharePoint Forums and a fellow by the name of Raghavendra Shanbhag, we figured it out with the steps outlined below.

1. Assuming that your blog site has been created, turn on ratings for your Posts Document Library as described here.

2. Download this custom blog.xsl file and upload it to a Style Library or any other Document Library of your choice. Note at around line 121 is where we added the AverageRating field to the output which is the only customization that was performed on the original XSL.

3. Open the blog site homepage and edit it in SharePoint Designer. Here, you’ll want to click on the Posts web part and add the Rating (0-5) column for the field to be displayed in the view.

4. While you’re in SharePoint Designer, you can also include a link to the custom XSL that you had uploaded in Step 2.

5. Now go ahead and save all the changes that you’ve made in SharePoint Designer and then head over to your blog site in the browser. You should now see the rating stars below the title of each of your posts but you’ll find that they are not clickable/interactive. What you’ll want to do here is to edit the homepage of that blog site and then add another instance of the Posts list to one of the web part zones. After you add it, change the view on that web part to the “All Posts” view which should contain the rating stars. By adding this web part, it registers the necessary javascripts and CSS for the rating stars to properly function. But we don’t want to have a duplicate set of posts on our homepage right? What we can do here is mark the 2nd Posts web part as hidden!

And with that, we have now transformed a SharePoint blog site into an ideation site :)

SharePoint 2010 Intermittent Search Errors & Web Services Round Robin Service Load Balancer Event: EndpointFailure

Environment:

SharePoint 2010 Enterprise
3 WFE’s each with Query Roles
2 App Servers with 1 Dedicated Indexer

Error in Event Viewer:

Event ID: 8313

SharePoint Web Services Round Robin Service Load Balancer Event: EndpointFailure

Process Name: w3wp

Process ID: 5948

AppDomain Name: /LM/W3SVC/303076968/ROOT-1-129634027688317155

AppDomain ID: 2

Service Application Uri: urn:schemas-microsoft-com:sharepoint:service:767f7d712e064db3bb8945d9d42d0e12#authority=urn:uuid:f7625c7a33924a9981c0206f8bf0054a&authority=https://applicationserver:32844/Topology/topology.svc

Active Endpoints: 3

Failed Endpoints:1

Affected Endpoint: http://applicationserver:32843/767f7d712e064db3bb8945d9d42d0e12/SearchService.svc

Error in Diagnostic Logs:

Internal server error exception: System.ServiceModel.ServerTooBusyException: The HTTP service located at http://applicationserver:32843/767f7d712e064db3bb8945d9d42d0e12/SearchService.svc is too busy.  —> System.Net.WebException: The remote server returned an error: (503) Server Unavailable.     at System.Net.HttpWebRequest.GetResponse()     at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)

Troubleshooting Steps:

I tried to access the web service that SharePoint was trying to get to via the browser and noticed that it was reporting a 503 error as reported in the Diagnostic Logs. Then I hopped onto IIS on the affected servers and looked at the SharePoint Web Services web application (IIS) in search for a corresponding Service Application GUID that matched. The application didn’t exist on either of the application servers so that would cause the EndpointFailure as reported in the Event Viewer logs. I then double checked the Services on Server page for the application servers to make sure that the search services were turned on as having these turned on would provision the web services. They were all started which threw a little confusion my way but with some hints from this forum post, I went ahead and stopped/started the Search Query and Site Settings Service. After a little while, assuming that some timer jobs had to run, the Service Application with the missing GUID showed up in IIS and the error stopped appearing in the Event logs.

Summarized Solution:

1. Go to your Services on Server page in Central Administration.

2. Stop and Start the Search Query and Site Settings Service on each affected server as reported in the Event Viewer or SharePoint Diagnostic Logs.

Follow

Get every new post delivered to your Inbox.

Join 675 other followers