Mutex Mischief

February 16th, 2008 by josef.nielsen

To clean up an old farm from some setup errors, I recently rebuilt the farm from bare metal, re-attaching all the content DB's after the rebuild to prevent data loss.  Imagine my horror when after 100% successful testing, and flawless installation, the first page I pull up shows this:

Mutex could not be created.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: Mutex could not be created.

After some research I learned that this is an ASP.Net issue that occasionally crops up when an app pool identity (ie the Web App Service Account in SharePoint) is not a local administrator.  When setting the identity, access to the Windows Registry key where the IIS Mutex Key is kept is supposed to be granted (obviously not an issue for local admins).  If it does not have access to read this key, the above error is generated.  To add it, you have to modify the key and grant permissions for you identity account(s) to read that key, then close all Mutex Threads (or just reboot the server).

A big thanx to Jerry Orman who documented this issue and the fix here:

http://blogs.msdn.com/jorman/archive/2006/07/24/system-invalidoperationexception-mutex-could-not-be-created.aspx

As a side note to any trying to implement least-permission security on a locked down server, make sure your local WSS_WPG group has full control of the WindowsTemp folder, or you'll get an odd ASP.Net Temporary files error (that points to a bogus dir).  The WSS_WPG group contains all your Web App Identity accounts, and is created by the SharePoint Installation.

Image Changes on Hover

January 22nd, 2008 by josef.nielsen

It sees there are a lot of large javascript samples out there to change an image source on mouse over, but they all seem overly complex.  Here's a little one I made recently for a project I was working on:

<script>
function imgHover(imgObj,imgSrc){
    imgObj.src=imgObj.getAttribute(imgSrc);
}
</script>

And here's some HTML to demonstrate how to use it:

<img
    src="
/PublishingImages/main.jpg"
    out="/PublishingImages/main.jpg"
    over="/PublishingImages/main_01.jpg"
    onmouseover="java-script:imgHover(this,'over');"
    onmouseout="java-script:imgHover(this,'out');"
    alt=""
    border=0
/>

Change the "java-script" and remove the "-" to make it work… It won't show up on the blog post otherwise (it tries to run it).  I've found it easiest to just embed the script in the master page, then I can use img tag with the extra attributes on any page in my site I want…

Security Trimming WebControls and ASP Objects

January 22nd, 2008 by josef.nielsen

I hadn't used this yet, and had cause to this week.  It works slick, and is pretty configurable… I used it to hide the SiteActions button on list pages for members, but not admins, as well as admin web parts embedded in a page.  It works with Web Parts, Web Controls, and other ASP.Net objects (ie. SharePoint:*, etc.).  Wrap the content you want trimmed in the following code (using SPD):

<Sharepoint:SPSecurityTrimmedControl runat="server" PermissionsString="BrowseDirectories">
[Content to Trim]
</Sharepoint:SPSecurityTrimmedControl>

You can then swap out the "BrowseDirectories" with whatever you need for the trimming.  Here are the one's I've found handy so far:

  • BrowseDirectories = Anyone but Limited Read (Anonymous is Limited Read)
  • ManageWeb = Site Collection Admins
  • ManageSubwebs = Site Owners
  • AddListItems = Site Members and specified list contributors

Here's a link to all the defined values in the SPBasePermissions Enum.

Adding category titles to Blog sites (category.aspx)

December 27th, 2007 by josef.nielsen

I recently found an application of the Blog template that was a great fit, but needed the Category title listed on the top of the page when you click a category from the default page.  Normally, it is not visible anywhere except in the URL as a parameter, and in the categories of the posts listed. 

The primary content of the page is displayed by a ListViewWebPart embedded in a table in the aspx file.  It starts like this:

<table cellpadding=0 cellspacing=0 style='padding: 5px 10px 10px 10px;'>
           <tr>
            <td valign=top>
                <WebPartPages:WebPartZone runat="server" FrameType="TitleBarOnly" ID="Left" Title="loc:Left"><ZoneTemplate>
<WebPartPages:ListViewWebPart runat="server" __MarkupType="xmlmarkup" WebPart="true" __WebPartId="{973762DE-3C5C-47B0-B80C-895B16BEDD6E}" >
<WebPart xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">
  <Title>Posts</Title>
  <FrameType>None</FrameType>
  <Description>Use the Posts list for posts in this blog.</Description>

I added the following table row to the table above (before the webpart's row) to display it.

           <tr>
            <td><script language="javascript">
function getURLParam()
    {
        var strReturn = "null";
          var strHref = window.location.href;
        if ( strHref.indexOf("?") > -1 )
        {
            var strQueryString = strHref.substr(strHref.indexOf("?")+1);
            var aQueryString = strQueryString.split("&");
            for ( var iParam = 0; iParam < aQueryString.length; iParam++ )
            {
              if (aQueryString[iParam].indexOf("Name=") > -1 )
              {
                var aParam = aQueryString[iParam].split("=");
                strReturn = aParam[1];
                break;
              }
            }
            var fields,i;
              fields = document.getElementsByTagName('DIV');
              for( i = 0; i < fields.length; i ++ )
              {
                if(fields[i ].innerHTML.indexOf("::") != -1)
                {
                     fields[i ].innerHTML = unescape(strReturn);
                }         
            }
         }
    }
    _spBodyOnLoadFunctionNames.push("getURLParam");
            </script>
             <div class="ms-sitetitle">::</div></td>
           </tr>

Note the use of the _spBodyOnLoadFunctionNames.push method to fire the script… This is documented on the SPD team's blog here.  This is so you can embed your javascript in master or aspx and have it execute correctly once the entire page is loaded.  I probably could have gotten away without it here, but used it to be safe. 

The script basically just looks for the <DIV> tag containing "::" and replaces it with the unescaped text from the "Name" parameter in the URL (Looks like http://MySharePointServer/sites/ICS/blog/category.aspx?Name=General%20Questions).  That is how the category is passed from the hyperlink to the category.aspx page.

How to clear off a SQL server quickly…

December 5th, 2007 by josef.nielsen

So I was asked in an online community how to quickly clean off a SQL Server to rebuild a MOSS Farm.  Although it won't clean up and "add-ins" to the system DB's or any security identities, this does a quick and dirty job…

IF object_id('tempdb..#AllDatabases') IS NOT NULL DROP TABLE #AllDatabases

CREATE TABLE #AllDatabases
(
            id          INT IDENTITY,
            name     NVARCHAR(128)
)

DECLARE @dbname   nvarchar(128)
DECLARE @counter  int
DECLARE @maxcount int

INSERT INTO #AllDatabases SELECT name FROM master..sysdatabases WHERE name NOT IN ('tempdb', 'master', 'model', 'msdb')

SET @counter = 1
SELECT @maxcount = MAX(id) FROM #AllDatabases

WHILE @counter <= @maxcount
BEGIN
    SELECT @dbname = name FROM #AllDatabases WHERE id = @counter
    Print 'Deleting DB ' + @dbname
    –Uncomment the next line to enable deleting all User DBs
    –DROP DATABASE @dbname
    SET @counter = @counter + 1
END

IF object_id('tempdb..#AllDatabases') IS NOT NULL DROP TABLE #AllDatabases

Service Account problems…

December 4th, 2007 by josef.nielsen

I was asked a question the other day that I thought might be good to share…  The company had made some changes and suddenly SharePoint stopped working, and they got these errors in the Even log every minute (italics added for anonymity):

————————————–

Event Type:      Error
Event Source:      Office SharePoint Server
Event Category:      Office Server Shared Services
Event ID:      6482
Date:            date
Time:            time
User:            N/A
Computer:      computername
Description:
Application Server Administration job failed for service instance Microsoft.Office.Server.Search.Administration.SearchServiceInstance (ea45ac67-ede8-4be3-812c-193b10678515).
Reason: Logon failure: the user has not been granted the requested logon type at this computer
Techinal Support Details:
System.ComponentModel.Win32Exception: Logon failure: the user has not been granted the requested logon type at this computer
   at Microsoft.Office.Server.Search.Administration.SearchServiceInstance.SynchronizeDefaultContentSource(IDictionary applications)
   at Microsoft.Office.Server.Search.Administration.SearchServiceInstance.Synchronize()
   at Microsoft.Office.Server.Administration.ApplicationServerJob.ProvisionLocalSharedServiceInstances(Boolean isAdministrationServiceJob)
————————————–

And:
————————————–

Event Type:      Error
Event Source:      Office SharePoint Server
Event Category:      Office Server Shared Services
Event ID:      6641
Date:            date
Time:            time
User:            N/A
Computer:      computer
Description:
The SSP Timer Job Distribution List Import Job was not run.
Reason: Logon failure: the user has not been granted the requested logon type at this computer
Technical Support Details:
System.ComponentModel.Win32Exception: Logon failure: the user has not been granted the requested logon type at this computer
   at Microsoft.Office.Server.Utilities.WindowsSecurity.GetUserTokenFromCredentials(String userDomainName, String password, LogonType logonType)
   at Microsoft.Office.Server.Utilities.WindowsSecurity.GetUserTokenFromCredentials(String userDomainName, String password)
   at Microsoft.Office.Server.Administration.JobHandler.Execute(Object state, Boolean timedOut)
For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

————————————–

Turns out they had changed the service account (and done it correctly), but they forgot to grant the Logon as Service security right in the Local Security Admin tool for that account.  A good thing to remember :)   This is done automatically when run the SharePoint Product Install and Configuration Wizard (or run psconfig -cmd configdb -create)…

A document repository by any other name is still… well… just a list…

November 27th, 2007 by josef.nielsen

So this is not so much technical as it is theoretical, but for requests I've gotten, here is a brief on SharePoint Lists. 

Everything in SharePoint (WSS, MOSS, MSS, etc) is a list.  This goes from a document lirbary to a publishing web part page library to a custom list, to the galleries that hold master pages and web parts, to the very site security that allows people to access or not access your content.  This is done in a very OO method, where all of these items inherit basic list functionality.  Many of these also add additional functionality through the SharePoint object model. 

A simple example is the custom list.  This is a list that starts out as just the basic require modified and modified by columns, along with a "name" column. You can add to this just about anything you can think of, either through the standard field types (text, multi-line text, hyper-link, file, etc.) or through site columns.  If you haven't experimented with site columns, check them out…  They are quite cool and give you the ability to create a field that is completely reusable.  I usually make a meta-data or "tag" site column on all my sites, to make it easy to add tagging or meta-data searches to my lists.  Ted Pattison actually has a few great resources available for download, like this screencast on Site Columns.

You can also extend existing list templates, like the task or document library lists. Go to any list and select Settings, List (or Library) Settings.  You'll get a similar screen each time, listing general settings and permissions options, as well as a list of Columns and Views. This is where you can modify or add (or remove) columns.  You can use this to add additional options to your list, like meta-data.  If you have a task list, you can add fields for things like estimated cost, or project codes. If you have a calendar, you can add a column for classification or category.  This changes the built-in templates for lists from limited examples, to jumping-off points for customized anything-you-wants.

Another example of this is the security list.  This is a list of individuals and their access for your site.  You can see some of the system-level activities (and a few screen shots) of in a previous post of mine about the behavior of My SharePoint Sites.  The gist of this is that if you go to the user information page of a root site collection (that's the "people" section of the people and groups admin pages), you can actually select "List Settings" just like any other list, and see the settings for the user list.  You can, in fact, even add columns to the user list, such as meta data or notes.  This could also include mapping extension information for programmatic user cross-matching to alternate data sources by user identity.

Another things that is great is that the list object in SharePoint inherits the ability to have a workflow attached to it.  That means that it is not just document library that benifit, as they are commonly thought of as the target when we talk about workflows.  You can even attach workflows to the user security lists.

Also, all lists of any sort can be saved as templates (stp files) with or without content in your template gallery (which, by the way, is another list).  This automatically adds that custom list to the appropriate category on the "create" page.  For those willing to adventure a bit farther in to the mirk, there is Content Types.  You can find a great post on the concept behind Content Types (I like to call it the Content Type is to Lists post) at John Holiday's blog.

So what does this all mean?  I'll say it again… Everything in SharePoint is a list.  Understand that and what it implies to the platform, and you take a big jump forward in understanding how you can make SharePoint do whatever it is you want it to. 

Excel Calculation Services (ECS) Invalid Handle error

November 20th, 2007 by josef.nielsen
Just had this happen to me while testing out a new IIS Build script…
 
When firing up the ECS on a new farm, if you get the following "Handle is
Invalid" error:
 
System.Security.Cryptography.CryptographicException: The handle is invalid.

   at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
   at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
… and so on …

 
You need to set permissions as follows:
 
"C:Documents and SettingsAll UsersApplication
DataMicrosoftCryptoRSAMachineKeys"
 
Add Local Administrators – Full
Add NETWORK SERVICE – Read
 
Then try firing up ECS…

I got this error in the Event Log (Applicaiton) as an Office SharePoint Server error with EventID of 6482 (MOSS Shared Services).  Not sure if this is always this ID or not.  It is accompanied by a 7034 event (Topology) telling you to try it again (which obviously wouldn't work with the fix first).
 

Getting Search Crawl Details from the DB

November 13th, 2007 by josef.nielsen

Ok, so I must be on a roll… Here's another glorious script that goes directly to the SharePoint DB's… Don't tell Bill!  As usual, this is not recommended by MS, etc., etc., etc.  This one is to get result sets of your Crawl Details.  It will show each attempt to start/stop/delete a crawl, what it's current status is, when it was requested, started, and finished.  Handy for monitoring your Search crawling with home grown tools ;)

 –Begin Script

/*
CrawlLogDetails.sql
Written by
Josef Nielsen
Nov. 2007

Displays MOSS Crawl Details (Type, status, and times)
Point this script at your Search DB (ie. SharedServices_Search_DB)
*/

BEGIN

– Create temp tables for System values
CREATE TABLE [#CrawlStatus](
[CrawlStatusName] VARCHAR(35),
[CrawlStatusID] INT
)

CREATE TABLE [#CrawlType](
[CrawlTypeName] VARCHAR(25),
[CrawlTypeID] INT
)

– Populate Crawl Status System Values
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_ACQUIRED', 1)
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_INSERTSTARTPAGE', 2)
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STARTCHECK', 3 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_START', 4)
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_FORBID',  5)
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_UPDATE_SEED', 6 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_QUERY_DONE', 7 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_DELETEUNVISITEDITEMS', 8 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_PAUSE', 9 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_RESUME', 10)
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_DONE', 11 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_UPDATE_STOP', 12 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_STOP', 13 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_STATUS_RESET',  14)
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_START_DELETE', 15 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_DELETE_CS', 16 )
INSERT INTO [#CrawlStatus] VALUES ('CRAWL_DELETE_SA', 17 )

– Populate Crawl Type System Values
INSERT INTO [#CrawlType] VALUES ('CRAWLTYPE_FULL', 1 )
INSERT INTO [#CrawlType] VALUES ('CRAWLTYPE_INCREMENTAL', 2 )
INSERT INTO [#CrawlType] VALUES ('CRAWLTYPE_DELETE', 6 )

– Join MSCrawlHistory to SCrawlHostList and our two temp tables
SELECT    [CrawlID]
        ,[HostName]
        ,[CrawlTypeName]
        ,[CrawlStatusName]
        ,[RequestTime]
        ,[StartTime]
        ,[EndTime]
  FROM [SharedServices1_Search_DB].[dbo].[MSSCrawlHistory]
  LEFT JOIN [dbo].[MSSCrawlHostList] ON [ProjectID] = [HostID]
  LEFT JOIN [#CrawlStatus] ON [Status] = [CrawlStatusID]
  LEFT JOIN [#CrawlType] ON [CrawlType] = [CrawlTypeID]
  WHERE 1 = 1

  — Uncomment and use this conditional to filter the results to just one Web App
  –AND [HostName] = 'MySharePointSiteName'
  ORDER BY [RequestTime] DESC

END

– Do a little clean up and get rid of those pesky temp tables
DROP TABLE [#CrawlStatus]
DROP TABLE [#CrawlType]

 –End Script

Details and manual monitoring of the Content DBs

November 8th, 2007 by josef.nielsen

So our Ad-Hoc environment started growing a bit faster than we had originally anticipated.  We knew we would have some disparity between site's content size, so we set our content DB max site limits a bit lower than normal (hey, we have 50 of them, so we thought we'd be safe).  The thought was that way we could help balance the sizing by adjusting the max counts on the DB's to reflect the physical size based on content. 

It quickly became way to much of a pain to manually collate the size details with the site details based on content DB…  So heres a little script I wrote up (ok, modified from my original Site Details script) that pulls all the goodness you could want about a content DB direct from the DB itself… <Insert canned warnings about how MS does not recommend querying the DB directly here>

 

/*
ContentDBReport.sql
written by Josef
Nielsen
October 2007 

NOTE: You must
create a linked server if you use multiple SQL server to house you content DBs
*/ 
 

BEGIN
DECLARE
@ts1 varchar(1000),
@ConfigDB
VARCHAR(128) 

– Set your
Config DB Name here if it is different
SET
@ConfigDB = 'SharePoint_Config'

– This creates
a temp table to hold the list of content DBs referenced by the Config DB

CREATE TABLE [#TempDbList]
(

DBname VARCHAR(128),
DBInstance VARCHAR(128),
DBServer VARCHAR(128),
MaxSites INT,
WarnSites INT
)

– Populate the
temp table with content DBs

SET @ts1 = 'INSERT
INTO #TempDbList
SELECT
[DbName].[Name] AS ''DatabaseName'',
[Instance].[Name]
AS ''DatabaseInstance'',
[Server].[Name]
AS ''DatabaseServer'',
CONVERT(XML,
[DbName].[properties]).value (''(/object/sFld/text())[1]'', ''int'') AS
''MaxSites'',
CONVERT(XML,
[DbName].[properties]).value (''(/object/sFld/text())[2]'', ''int'') AS
''WarnSites''
FROM
'
+'['+@ConfigDB+']'+'.[dbo].[Objects]
AS [DbName]
LEFT
JOIN '
+'['+@ConfigDB+']'+'.[dbo].[Objects]
AS [Instance]
ON
[DbName].[ParentId] = [Instance].[ID]
LEFT
JOIN '
+'['+@ConfigDB+']'+'.[dbo].[Objects]
AS [Server]
ON
[Instance].[ParentId] = [Server].[Id]
WHERE
[DbName].[Properties] LIKE ''%SPContentDatabase%''
AND
[DbName].[Properties] NOT LIKE ''%WebApplication%'''

EXEC (@ts1)

DECLARE @ts2 VARCHAR(1000)

–This creates a
temp table to hold the end results of the Site Collection lists from all
Content DBs

CREATE TABLE [#TempContentDbList]
(
WebApp
VARCHAR(128),
DBServer VARCHAR(128),
DBName
VARCHAR(128),
DBSites
int,
DBWarnSites
int,
DBMaxSites
int,
DBSize float
)

– Create a
cursor to walk through each content DB

DECLARE DB_cursor CURSOR
FOR
SELECT [DBServer], [DBInstance],
[DBName], [MaxSites], [WarnSites]

FROM [#TempDbList] 

OPEN DB_Cursor

– Declare
Variables to populate by Cursor

DECLARE @vDBServer VARCHAR(128)
DECLARE
@vDBInstance VARCHAR(128)
DECLARE @vDBName VARCHAR(128)
DECLARE @vMaxSites INT
DECLARE
@vWarnSites INT
DECLARE
@DBv1 VARCHAR(5000) 

FETCH NEXT FROM DB_cursor INTO @vDBServer, @vDBInstance, @vDBName, @vMaxSites,
@vWarnSites

WHILE @@FETCH_STATUS = 0
BEGIN  

– Add a
backslash for DBServers that are not default instances

DECLARE @slash VARCHAR(5)
IF @vDBInstance = ''
SET @slash = ''
ELSE
SET @slash = ''

– Script to
insert Content DB details to the temp site summery table

SET @DBv1 = 'INSERT
INTO [#TempContentDbList]
SELECT
[ConfigObjects].[Name] AS ''WebApp'',
(SELECT
'''
+ @vDBServer+@slash+@vDBInstance
+
''')
AS ''SQL Server'',
(SELECT
'''
+ @vDBName + ''') AS ''Content DB Name'',
(SELECT
COUNT([Webs].[Title])) AS ''Current Site Count'',
(SELECT
'
+ CONVERT(VARCHAR(20), @vMaxSites) + ') AS ''Max Site Count'',
(SELECT
'
+ CONVERT(VARCHAR(50),@vWarnSites) + ') AS ''Site Size'',
(SELECT
round(sum(convert(float,[size])*8/1024),2) AS ''DB Size in MB''
FROM
[sys].[master_files]
WHERE
[state]
= 0
AND
[data_space_id] = 1
AND
db_name([database_id]) = '''
+ @vDBName + '''
GROUP
BY [database_id]) AS ''Site Size''
FROM
['
+@vDBServer+@slash+@vDBInstance+'].['+@vDBName+'].[dbo].[sites] AS [Sites] WITH (NOLOCK)
LEFT
JOIN ['
+@vDBServer+@slash+@vDBInstance+'].['+@vDBName+'].[dbo].[webs] AS [Webs] WITH (NOLOCK) ON
[Webs].[siteID] = [Sites].[Id]
LEFT
JOIN '
+'['+@ConfigDB+']'+'.[dbo].[SiteMap]
AS [ConfigSiteMap] WITH (NOLOCK) ON
[ConfigSiteMap].[Id] = [Sites].[Id]
LEFT
JOIN '
+'['+@ConfigDB+']'+'.[dbo].[Objects]
AS [ConfigObjects] WITH (NOLOCK) ON
[ConfigSiteMap].[ApplicationID] = [ConfigObjects].[Id]
WHERE
[Webs].[ParentWebId]
IS NULL
GROUP
BY [ConfigObjects].[Name]'
 

EXEC (@DBv1)

FETCH NEXT FROM DB_cursor INTO @vDBServer, @vDBInstance, @vDBName, @vMaxSites, @vWarnSites

END

CLOSE DB_cursor
DEALLOCATE DB_Cursor

END

– Cursor is
closed and released, and now we select the results of the scan

SELECT * FROM #TempContentDbList ORDER BY [WebApp], [DBName]

GO

– Clean up to
get rid of those temp tables

DROP TABLE [#TempDbList]
DROP TABLE [#TempContentDbList]