Awesome ACS SharePoint Tools – On Sale Now! SharePoint Filter, SharePoint Designer LockDown and SharePoint Durable Urls

I have been a busy SharePoint programmer as of late and I'm proud to announce that all my work has led to some new and innovative solutions for SharePoint customers!  I'll be promoting these heavily over the next few months as they have been needed for some time now!

Here are the three tools:

You can get all three of them for $3595!!!

Here's what they do and why I built them:

SharePoint Designer LockDown –  customers are fed up with being limited to making users Site Collection admins just so they can use SharePoint Designer.  The other permissions like "Use Remote APIs" is also lacking and turning it off pretty much isn't an option when trying to keep regular non-super users from getting access to Designer (it is free ya know, and it doesn't help they put the link to download it on the ribbon menus).  This tool uses a SharePoint list where you place the domainusername of the person, and the site collection url that they should have access too.  If they aren't in the list, they don't get access!  The tool itself is scoped at a web application level.   Check out the video demo here

SharePoint Durable Urls – This is a feature that is in Oracle Portal and has been lacking in SharePoint for…oh yeah…forever.  This is similar to a bit.ly type of system, but rather than just providing shorten urls, it provides "durable" urls.  If the item is ever moved, renamed or the main URL of the site is changed.  The durable URL WILL STILL WORK!  Novel right?  And no, this is not the same as the DocumentID feature in SharePoint.  DocIDs in SharePoint are bloated ugly creatures that will kill you in the middle of the night! Check out the video demo here

SharePoint Filter – Needless to say, I'm pretty sure everyone has gotten bitten by legal and HR on those nice "Social" features in SharePoint.  Namely…User Profile properties (like your status message), social tagging and the note board.  I have heard horror stories about mis-behaving employees posting very naughty messages on noteboards and tagging items with inappropriate terms.  SharePoint Filter will prevent this from occurring and it will allow you to clean out anything you might already have in your Farm's social databases!  Check out the video demo here

To see these tools in action via a live demo, send me an email and I'll give you a username and password to hit the site and see how they work!

I will have a referral program where you can earn up to 15% of the sales.  Email me about potential customers and I'll get you tagged to them!  First come first served!

Enjoy!
Chris

HUGE Permissions bug with Import-SPWeb -includeusersecurity

The Export-SPWeb and Import-SPWeb is a tool for moving sites around, but with all the changes that have occured with SharePoint 2010 over the past two years, it seems some of the plumbing isn't connected correctly.

One of the issues we were seeing is that after importing a site, users are able to get to the site, but not to libraries underneath the site.  You would see ULS logs like:

OriginalPermissionMask check failed. asking for 0x00020000, have 0x00000000 

Nice list of the perm masks for reference:
http://social.msdn.microsoft.com/Forums/en/sharepointdevelopment/thread/c32bc150-7249-423d-8018-d2f23afc1f3b

Exception: System.UnauthorizedAccessException: Attempted to perform an unauthorized operation.. 

Access Denied for SITEURL. StackTrace:    at Microsoft.SharePoint.Utilities.SPUtility.HandleAccessDenied(HttpContext context)     at Microsoft.SharePoint.Utilities.SPUtility.HandleAccessDenied(Exception ex)     at Microsoft.SharePoint.SPWeb.GetWebPartPageContent(Uri pageUrl, Int32 pageVersion, PageView requestedView, HttpContext context, Boolean forRender, Boolean includeHidden, Boolean mainFileRequest, Boolean fetchDependencyInformation, Boolean& ghostedPage, Byte& verGhostedPage, String& siteRoot, Guid& siteId, Int64& bytes, Guid& docId, UInt32& docVersion, String& timeLastModified, Byte& level, Object& buildDependencySetData, UInt32& dependencyCount, Object& buildDependencies, SPWebPartCollectionInitialState& initialState, Object& oMultipleMeetingDoclibRootFolders, String& redirectUrl, Boolean& ObjectIsList, Guid& listId)     at Microsoft.SharePoint.ApplicationRuntime.SPRequestModuleData.FetchWebPartPageInformationForInit(HttpContext context, SPWeb spweb, Boolean mainFileRequest, String path, Boolean impersonate, Boolean& fGhostedPage, Byte& verGhostedPage, Guid& docId, UInt32& docVersion, String& timeLastModified, SPFileLevel& spLevel, String& masterPageUrl, String& customMasterPageUrl, String& webUrl, String& siteUrl, Guid& siteId, Object& buildDependencySetData, SPWebPartCollectionInitialState& initialState, String& siteRoot, String& redirectUrl, Object& oMultipleMeetingDoclibRootFolders, Boolean& objectIsList, Guid& listId, Int64& bytes)     at Microsoft.SharePoint.ApplicationRuntime.SPRequestModuleData.GetFileForRequest(HttpContext context, SPWeb web, Boolean exclusion, String virtualPath)     at Microsoft.SharePoint.ApplicationRuntime.SPRequestModule.InitContextWeb(HttpContext context, SPWeb web)     at Microsoft.SharePoint.WebControls.SPControl.SPWebEnsureSPControl(HttpContext context)     at Microsoft.SharePoint.ApplicationRuntime.SPRequestModule.GetContextWeb(HttpContext context)     at Microsoft.SharePoint.ApplicationRuntime.SPRequestModule.PostResolveRequestCacheHandler(Object oSender, EventArgs ea)     at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()     at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)     at System.Web.HttpApplication.PipelineStepManager.ResumeSteps(Exception error)     at System.Web.HttpApplication.BeginProcessRequestNotification(HttpContext context, AsyncCallback cb)     at System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr managedHttpContext, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr managedHttpContext, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr managedHttpContext, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)     at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr managedHttpContext, IntPtr nativeRequestContext, IntPtr moduleDa…

Looking at the permissions for the library (Site Actions->Site Permissions OR Library->Library Permissions), you WOULD see what you are supposed to see as far as permissons are concerned (even the CheckPermissions tool shows what we want).  However, looks are DECEIVEING!  Also, when we try to break permissions, it would continue to show that the list was inheriting from the parent.  No errors would be thrown in the UI.  When using powershell and tracing the verbose calls in ULSViewer you would actually see some more revealing details:

05/10/2012 09:23:21.29 PowerShell.exe (0x1180) 0x0D3C SharePoint Foundation General bj8s Medium setSecurityScopeUnique: start 
05/10/2012 09:23:21.29 PowerShell.exe (0x1180) 0x0D3C SharePoint Foundation General bj8t Medium setSecurityScopeUnique: already unique 
05/10/2012 09:23:21.29 PowerShell.exe (0x1180) 0x0D3C SharePoint Foundation General bj8w Medium setSecurityScopeUnique: finished 

Notice the second line:  "Already unique"…WTF…no its not!  The UI shows it is inheriting permissions!  So what is going on here?

Turning on the handy SQL Profiler, I did the following powershell on a "working" list:

$web = get-spweb http://blah
$list = $web.lists["Project Documents"]
$list.BreakInheritance($true, $false)
$list.Update()

What you see in profiler is a call to:

declare @p15 uniqueidentifier
set @p15='4EE7D0C0-0133-4600-A96E-53BD0D133DEF'
exec proc_SecChangeToUniqueScope @SiteId='EFAF8E96-FA9F-4816-A965-CE7A8C6ED8AC',@WebId='E504097A-2BA1-400F-BCC2-5DE6B32E8615',@OldScopeId='3FB9F8DA-A957-4B82-B4AA-FECB3B1B4551',@CopyFromScopeId='3FB9F8DA-A957-4B82-B4AA-FECB3B1B4551',@Url=N'asi/Shared Documents',@DocId='00000000-0000-0000-0000-000000000000',@bIsWeb=0,@UserId=1073741823,@CopyAnonymousMask=1,@CopyRoleAssignments=1,@ClearSubScopes=0,@bBreakBySiteOwner=0,@ReturnAuditMask=1,@MaxScopeInList=50000,@NewScopeId=@p15 output,@RequestGuid='00000000-0000-0000-0000-000000000000'
select @p15

When performing the same operation on a "broken" list (remember, from an imported web), you DO NOT see this call made.  So I decided, lets look at that stored procedure and see what it does.  The SP will actually look at the "Perms" table for a "ScopeId", then it will replicate what it needs in all the related tables to create a copy of a parent scope (actually doesn't have to be a parent, but the SharePoint team doesn't let us do this through the UIs – wouldn't that be handy!).

So what is the problem?  Well…the problem centers on that pesky "ScopeId".  What is happening is that SharePoint has TWO ways to get permissions information:

  1. Get the scopeid from the AllLists table (using urls or ids)
  2. Get the scopeid from the perms table (using urls or ids)

UPDATE:  There is also a scopeid on the AllDocs table that also gets out of whack!

In some places the object model and the "obfuscated" SPRequest COM objects are making two different calls!  One will look at the AllLists table, the other will look
at the Perms table.  This is the BUG.  ALL access APIs are using the "Perms" table, all UI APIs (including the CheckPermissions tool) are using the "AllLists" table.  They should look at the same place!  What is happening is the scopeID of imported objects is being set to the parent webs scopeID!  So even though the UI shows you what you WANT to see, the internals think that the permissions are that of the web's parent web!  THAT'S NOT GOOD!

How do you fix this?  Your not going to like the answer…none of the object model calls would reset the scopeid of the lists or webs.  I had to go into the database and reset the mapping of the scopeIds.  Once I did this, BAM!  Everything started working again…

Second big bug I have found in SharePoint 2010 in the last two weeks.

SQL Queries to help you find things:

Find List Scopes:

SELECT tp_Title, tp_ScopeId
FROM
[AllLists]
where

tp_WebId
= 'GUID'

Find Doc Scopes:

SELECT WebId, DirName, LeafName, scopeid
FROM
[AllDocs]
where DirName like '%blah%'

Find Web Scopes:

SELECT ID, FullUrl, scopeid
FROM
[AllWebs]
where fullurl like '%blah%'

Find Scope Permissions:

declare @scopeid as varchar(max)
set @scopeid = 'A3A5D2C8-5624-4E2A-BE7A-7D51C26DB081'

SELECT *
FROM
[Perms]
where scopeid in (@scopeid
)

select *
FROM RoleAssignment ra,
Roles r
where ScopeId =
@scopeid
and ra.RoleId = r.RoleId

Grrr….
Chris

Follow me on twitter: @givenscj

Check out the previous blog post:
https://blogs.architectingconnectedsystems.com/blogs/cjg/archive/2012/04/20/ATTENTION_3A00_-Database-Leak-in-SharePoint-2010_210021002100_.aspx

Project Server PSI WCF bug – WSEC_CAT_UID and WSEC_GRP_UID

At one of my customers we are automating the project and project site creation for Project Server 2010.  As part of that, we wanted the proper permissions to be setup for each resource/user.  This includes making them in the "Project Manager" group and the "My Projects" and "My Resources" categories.  The code wasn't very straight forward, but I got most of it working (creating the resource and making a resource a user).  The part that doesn't work is the group and categories assignment.  Here is the code:

SvcSecurity.SecurityGroupsDataSet sgDataSet = new SvcSecurity.SecurityGroupsDataSet();
//Get the security group
SvcSecurity.SecurityGroupsDataSet sglist = securityClient.ReadGroupList();
SvcSecurity.
SecurityGroupsDataSet.SecurityGroupsRow groupDs = null;
foreach (SvcSecurity.SecurityGroupsDataSet.SecurityGroupsRow sg in sglist.SecurityGroups)
{
if (sg.WSEC_GRP_NAME == groupName)
{
groupDs = sg;|
}
}

SvcSecurity.SecurityGroupsDataSet ds = new SvcSecurity.SecurityGroupsDataSet();
// Specify which users belong to the new group.

SvcSecurity.SecurityGroupsDataSet ds = new SvcSecurity.SecurityGroupsDataSet();
// Specify which users belong to the new group.

SvcSecurity.SecurityGroupsDataSet.GroupMembersRow groupMembersRow = ds.GroupMembers.NewGroupMembersRow();
groupMembersRow.WSEC_GRP_UID = groupDs.WSEC_GRP_UID;
// Add the GUID of the resource to the group.
groupMembersRow.RES_UID = NewResGuid;
ds.GroupMembers.AddGroupMembersRow(groupMembersRow);
securityClient.SetGroups(ds);

SecurityGroupsDataSet.GroupMembersRow groupMembersRow = ds.GroupMembers.NewGroupMembersRow();
groupMembersRow.WSEC_GRP_UID = groupDs.WSEC_GRP_UID;
// Add the GUID of the resource to the group.
groupMembersRow.RES_UID = NewResGuid;
ds.GroupMembers.AddGroupMembersRow(groupMembersRow);
securityClient.SetGroups(ds);

The problem lies in the finding of the group.  The DataSet that is returned returns the WSEC_GRP_UID.  This particular column is NOT what the following AddGroupMembersRow call wants!  It actually is looking for a second GUID in the database called WSEC_GRP_GUID.  This is not returned in the PSI call and therefore, you have ZERO chance of successfully going through the PSI apis to get the id.  You end up having to query the database directly…yuk.

Anyone that develops PSI out there?  You really need to fix this…

Chris

ATTENTION: Database Leak in SharePoint 2010!!!

I have been doing a massive Oracle Portal migration, 3000+ pages of content, with a targeted subset of that in my dev envrionment at just over 3GB of content.  I noticed the other day that my development content database kept getting bigger and bigger.  Running a "dbcc checkdb" pointed me to the largest tables.  After clearing the Audit logs, EventLogs and everything else I could think of, then shrinking the database files, the databse size was still WAY bigger than what it was supposed to be (it should be 35GB, but it is currently 72GB).  The dbcc command pointed to the AllDocStreams table as the source of the problem.  I thought it possible the new web recycle bin may be the culprit, so I set out to find the root cause and prove to the community that "Redmond, we have a problem"

My first test was to create a web and then delete it over and over again.  That didn't change the row sizes in the database.  Next test was to do the same thing, only add a document to the doc library.  That didn't cause any leaks.  The last step was to enable versioning and to upload a document a few times, then delete the webs…VIOLA!  Found the leak!  Want to try this on your own…here is the PowerShell that proves that we have a big issue here:

function CheckRows($table, $conn)
{
$sql = "select count(*) as count from $table";
$cmd = new-object system.data.sqlclient.sqlcommand($sql, $conn)

$reader = $cmd.executereader()

while($reader.read())
{
$line = $reader["count"].tostring()
}

$reader.close()

"$table has " + $line + " rows"
}

function ReportRowCounts($cdb)
{

$connString = $cdb.LegacyDatabaseConnectionString

$conn = new-object system.data.sqlclient.sqlconnection
$conn.connectionstring = $connstring
$conn.open()

CheckRows "AllDocs" $conn
CheckRows "AllDocStreams" $conn
CheckRows "AllDocVersions" $conn

$conn.close()

}

function UploadFile($web)
{

$list = $web.lists["Shared Documents"]
$list.EnableModeration = $true
$list.EnableMinorVersions = $true
$list.contenttypesenabled = $true
$list.update()

$spFolder = $list.rootfolder
$spFileCollection = $spFolder.Files
$file = get-item "c:scriptsexport.zip"
$spfile = $spFileCollection.Add($file.name,$file.OpenRead(),$true)
$spfile.checkout()
$spfile.checkin("checkin")
$spfile.approve("approved")

}

$cdb = get-spcontentdatabase "WSS_Content_Contoso"
$web = get-spweb
http://www.contoso.com

ReportRowCounts($cdb)

#create a site
$subweb = $web.webs.add("Test1", "Test1", "", 1033, "STS#0", $false, $false);

ReportRowCounts($cdb)

$subweb.delete()

ReportRowCounts($cdb)

$subweb = $web.webs.add("Test1", "Test1", "", 1033, "STS#0", $false, $false);
UploadFile $subweb
UploadFile $subweb
UploadFile $subweb
UploadFile $subweb
UploadFile $subweb

ReportRowCounts($cdb)

$subweb.delete()

ReportRowCounts($cdb)

You will see the AllDocStreams table continues to get larger and larger.  This is the table that has your BLOBS!  The worst table to have a database leak!  From a high level, what does this mean?  It means if you turn on versioning, and say upload a 1Gb file and then make 5 changes to it, you will have 5GB of storage used in your database.  If you then delete the web, you will lose file pointers in the database and that 5GB will remain in the database forever and never get cleaned up.  Now, how likely is it that you are creating and deleting webs in production?  Not too many people will have a fancy process setup like this, but in Development and QA, you will.  I can't afford to keep restorting a "clean" copy of my development databases everytime it gets too big.

NOTE:  This bug shows up in all builds after SP1 (the web recycle build), my environment is the latest release build of 14.6112.5000.

Here's how to determine if you are having a problem and how BIG it is:

$global:count = 0
$global:size = 0

function CheckDatabase($cdb)
{
$connString = $cdb.LegacyDatabaseConnectionString
$conn = new-object system.data.sqlclient.sqlconnection
$conn.connectionstring = $connstring
$conn.open()

$sql = "select count(*) as count, sum(datalength(content)) as size from alldocstreams where id not in (select id from alldocs)";
$cmd = new-object system.data.sqlclient.sqlcommand($sql, $conn)
$reader = $cmd.executereader()

while($reader.read())
{
$count = [double]::parse($reader["count"].tostring())
$size = [double]::parse($reader["size"].tostring())

}

$reader.close()
$conn.close()

$global:Count += $count
$global:size += $size

}

$cdbs = get-spcontentdatabase

foreach ($cdb in $cdbs)
{
CheckDatabase $cdb
}

"Orphaned rows: " + $global:count
"Orphaned rows size: " + $global:size + " bytes"

Microsoft…you need to get us a cumulative updatehot fix RIGHT NOW.

UPDATE:  AUGUST 2012 Cumulative Update fixes this bug:
http://technet.microsoft.com/en-us/sharepoint/ff800847.aspx

Not one to say enjoy too, but at least I know what is going on now and so do you!
Chris

Follow me on twitter: @givenscj

Check out the previous blog post:
https://blogs.architectingconnectedsystems.com/blogs/cjg/archive/2012/04/20/Project-Server-PSI-WCF-bug-_2D00_-WSEC_5F00_CAT_5F00_UID-and-WSEC_5F00_GRP_5F00_UID.aspx

The workflow failed to start due to an internal error

Yeah, that error.  Like an STD, its the one you hope you don't get! There are many reasons this error can creep up on you.  The most common being that the content types are corrupted and cannot be assigned to the task and history lists.

There are several blog posts that try to help here:

  • Uninstall and re-install the workflow features
    • In essense this should be the fall back, as it should remove the content types and put them back – as I found out, this didn't work in my case
  • If you are building your own workflow, ensure that your dlls have been loaded and the assembly manifests match up 
  • Make sure your Infopath forms are in the right places and haven't been modified
    • Typically, the forms are loaded up at the site collection under (_Catalogs/wfpub)
  • Make sure your web.config file was not corrupted and all the workflow entries exist (System.Workflow.ComponentModel.WorkflowCompiler)

So what was special in my case that none of these worked?  A simple answer, an orphaned web.  We were doing some migration work and we started to move a very large web from one web application to another.  We stopped the process halfway through, then we deleted the failed web.  Unfortunately, we didn't realize that this created the orphaned web.  As part of being orphaned, the transaction that does the deleting doesn't complete all its tasks and you are left with residual in the database. 

How did I reach this conclusion?  I tested workflows in another site collection in the same web application, they worked (ruling out that corruption occured via cumulative updates and feature corruption).  I tested the workflows in two different webs in the same site collection, both failed.  I tested an import on a different QA server and workflows still worked (but being that I didn't replicate what we did exactly last time, stop halfway, not a reliable test).  I kept at it and re-fired the workflow and saw that it would execute on the second or third execution, but it wouldn't create any tasks.  That lead me to believe that there was something wrong with the task list.  I looked at the working task list and saw that the content types DID exist on the list, but in the broken site, they did not.  I decided to manually add the content type via powershell and bingo…a helpful error ("Object reference not found").  Doing some searching I ran across this post:

http://social.technet.microsoft.com/Forums/zh/sharepoint2010programming/thread/dc211298-75b4-4c1a-8c95-acf6d610ed6f

Each content type has to have a field.  Looking at the content types, they didn't have any fields!  How weird right?  That made me realize that the content types were corrupt.  Therefore, I needed to delete all the content types and parents that didn't have fields.  This started at Publishing Approval Workflow Task (en-US)->SharePoint Server Workflow Task->Workflow Task.  Workflow task being one before the system content type "Task".  If you lose that one, you are screwed!

Luckily we didn't have many workflows so I decided to delete all the workflow content types (from all Task lists, which means the tasks must be deleted too).  I was able to delete the first two easily (turn off the features), but when I went to delete "Workflow Task" it would not delete as it was part of a feature (that meant chaning the bit column in the database for the content type)!  I turned off the feature (695b6570-a48b-4a8e-8ea5-26ea7fc1d162), but it still would not delete as it was being used!  So using the handly SPContentTypeUsage class (as blogged here – http://sharepoint.mindsharpblogs.com/NancyB/archive/2011/01/17/Finding-children-the-easy-way-(Content-Type-children,-that-is).aspx).  I was able to find the usage of the content type.  To my amazement…a deleted/orphaned web was showing. 

Once a web is deleted, you have no visibility to it via the object model.  That means a trip to the database tables was the next step.  Remember, the residual I mentioned earlier?   In our case, the ContentType and ContentTypeUsage tables had yuky residual in them.  I had to manually remove the ContentTypeUsage records, then manually delete the ContentType row for the orphaned web.

After that i was able to remove the ContentType via the object model.  I then reactived the workflow features and my problem was solved!  All the content types show fields for them now and all workflows work as expected!

Enjoy,
Chris

Oracle Portal 10g to SharePoint Migration

Do you have Oracle Portal 10g?  Want to move to SharePoint with full fidelity?  I have scripts that will do exactly that!  This would include:

  • Portal Groups
  • All pages with all "things" (URLs, content, files)
  • Files with versions and comments
  • Workflows
  • Permissions

Contact me at:
chris@architectingconnectedsystems.com

I'd be happy to chat with you about moving to SharePoint 2010!

Oh, and here are some interesting facts about Oracle Portal versus SharePoint:

  • Web part zones can have web part zones (these are called regions and subregions in Portal) 
  • Workflows have steps for approvals (whereas SharePoint an approval is just a single step)
  • Groups are global in Oracle Portal (SharePoint groups are limited to site collections)
  • Oracle uses a concept of "things".  Everything is one and everything can have other "things".  This is *super* powerful notion and means that a single page can contain "things", where a thing can be a URL, a file, content, etc.  SharePoint pages can only have web parts on them.
  • Each "thing" on the page can have security for it, SharePoint doesn't let you do this
  • Each "thing" has a version (no matter what it is), SharePoint doesn't have this
  • Each "thing" on a page can have a workflow.  SharePoint workflows are tied to list items, you can't workflow web parts
  • Each "thing" can be optionally HIDDEN.   SharePoint doesn't have a notion of hidden.  The closest you can get is by creating a second list/library and setting permissions.
  • Oracle has a built in notion of shortcut links (a GUID that takes you to the page you want to hit), SharePoint doesn't

So what saves SharePoint at the end of the day?

  • Office Web Apps is the savior, you can't do fancy Office Document manipulation in Oracle
  • Oracle Portal is *much* more expensive than SharePoint

Chris

1 Year Anniversary Tribute – eBay upgrade video

As a tribute to the 1-year anniversary of the start of the eBay upgrade project, I thought I would share the video I created to show at our celebration party in San Jose.  It is a great video, and shows how much fun we had (and you should too) when we did the upgrade.  The team deserves a tremendous amout of credit for the work and effort they put in…so here it is:

http://youtu.be/EnK2c_lntOY

As a run down, here is what occurs in the video:

  • The SharePoint odessey – the 2001 to 2010 evolution.  When the monkey discovers the monolith and evolves.  Simiarly, eBay's discovery of SharePoint 2010 starts with a monolithic-white board (its only appropriate that we use Richard Struass's Also sprach Zarathustra to show this)
  • You see that it really is a love of "SharePoint" that progresses the joining of all parties
  • As we drive through the initial requirements (found and un-found), you will realize that this project was done in 3.5 months (when similar quotes were at 14 months at 4x the cost).  This drives the selection of Queen's…"Under Pressure"
  • At one point you will see Ramin and I on the ground with our hands in the air…it was a fun moment as we were not really knowing what was going to happen…
  • You will see photos of our countdown timer of the night we went live (we had an upgrade window of the weekend to finish the upgrade successfully).  It slowly winds down until the "GO-NOGO Moment"
  • As the pictures are displayed, you will see various moments over the 3.5 months of us working intent-ly getting the job done
  • Not everything went as planned and hence you see the "GO-NOGO" which occured at least 4-5 times (some elements include NDR64 discoveries and the infamous Search ResultsProvider that has claimed countless develpers…
  • Katy Perry's song of "Firework" is perfect for our dispay of the team members that worked so very hard to make the upgrade happen and thusly are amazingly bright and wonderful people that randomly came together to accomplish something amazing (NOTE: there are moments in the song lyrics that relate to hidden moments)
  • Let me be the first to say that eBay has an amazing NetOps team that just kicks butt (Kenny Cheng and Ken MacIntosh), they saved our a$$ a few times…
  • You will see a snapshot of a fortune cookie that "I" got…it was the week of the upgrade and it said "Your hard work is about to pay off", it was the best fortune EVER!
  • There were some times when we had some pretty "interesting" dinners…hence the snapshot of the menu…
  • The video ends with 00:00:00 and pictures of the eBay intranet running SharePoint 2010 and our SharePoint Conference 2011 session – "How eBay successfully upgrade their intranet to 2010"

Enjoy!
Chris

Fixing: Action 4.0.23.0 of Microsoft.SharePoint.Upgrade.SPSiteWssSequence failed.

This was a tough error, THAT IS AN UPGRADE SHOWSTOPPER, that I ran into a few months ago.  Just now getting around to blogging it.  Here's the full error:

[powershell] [SPSiteWssSequence] [ERROR] [9/29/2011 12:02:27 PM]: Action 4.0.23.0 of Microsoft.SharePoint.Upgrade.SPSiteWssSequence failed.
[powershell] [SPSiteWssSequence] [ERROR] [9/29/2011 12:02:27 PM]: Inner Exception: Field name already exists.

The name used for this field is already used by another field in the list.  Select another name and try again.<nativehr>0x81020013</nativehr><nativestack></nativestack>
[powershell] [SPSiteWssSequence] [ERROR] [9/29/2011 12:02:27 PM]:    at Microsoft.SharePoint.Library.SPRequestInternalClass.UpdateField(String bstrUrl, String bstrListName, String bstrXML)
   at Microsoft.SharePoint.Library.SPRequest.UpdateField(String bstrUrl, String bstrListName, String bstrXML)
[powershell] [SPSiteWssSequence] [ERROR] [9/29/2011 12:02:27 PM]: Exception: Field name already exists.

The name used for this field is already used by another field in the list.  Select another name and try again.
[powershell] [SPSiteWssSequence] [ERROR] [9/29/2011 12:02:27 PM]:    at Microsoft.SharePoint.SPGlobal.HandleComException(COMException comEx)
   at Microsoft.SharePoint.Library.SPRequest.UpdateField(String bstrUrl, String bstrListName, String bstrXML)
   at Microsoft.SharePoint.SPField.UpdateCore(Boolean bToggleSealed)
   at Microsoft.SharePoint.SPField.Update()
   at Microsoft.SharePoint.SPFieldIndexCollection.IndexOneField(Object field, Boolean bIndexed)
   at Microsoft.SharePoint.SPFieldIndexCollection.Add(SPField field)
   at Microsoft.SharePoint.Upgrade.RestoreWorkflowAndDatasourceLibraryPermissions.Upgrade()
   at Microsoft.SharePoint.Upgrade.SPActionSequence.Upgrade()

What causes this?  Diving into the upgrade code I find that it is obviously trying to add a column to a list.  But which one?  The hint was in the name of one of the methods – "RestoreWorkflowAndDatasourceLibraryPermissions".  Looking at this, it is checking for all task lists in each site that are setup for workflow.

I reran the code in powershell and eventually found the offending list:

foreach($web in $site.allwebs)

{

$ht = new-object system.collections.hashtable

foreach($list in $web.lists)

{

foreach($ct in $list.ContentTypes)

{

if ($ct.Id.IsCHildof([Microsoft.SHarePoint.SPBuiltInContentTYpeId]::WorkflowTask))

{

$ht.add($list.id, $list.id)

"Web:" + $web.TItle

"List: " + $list.Title

"Content Type:" + $ct.Name

} #end if

} #end ct

} #end list

foreach($id in $ht.values)

{

$l1 = $web.lists[$id]

#$g = new-object system.guid ([Microsoft.SHarePoint.SPBuiltInFieldId]::WorkflowInstanceID.tostring())

$g = new-object system.guid ("de8beacf-5505-47cd-80a6-aa44e7ffe2f4")

#$l1.FieldIndexes.delete($g);

$l1.FieldIndexes.add($list.fields[[Microsoft.SHarePoint.SPBuiltInFieldId]::WorkflowInstanceID]);

$l1.update()

#$g = new-object system.guid([Microsoft.SHarePoint.SPBuiltInFieldId]::Guid.tostring())

$g = new-object system.guid("ae069f25-3ac2-4256-b9c3-15dbc15da0e0")

#$l1.FieldIndexes.delete($g);

$l1.FieldIndexes.add($list.fields[[Microsoft.SHarePoint.SPBuiltInFieldId]::Guid]);

$l1.update() 

} #end hashtable

} #end web

What was the cause?   When you create a workflow, it asks for a task list. 
You can point the workflow at an existing task list or create a new one.  It seems that not only did the users create a workflow that pointed to an existing list, but that list in fact was the task list that the workflow was using for task tracking!!  This causes a loop in the upgrade code and hence the failure.

Fixing it requires deleting some of the fields that were added by the workflow.

Enjoy!

Chris


Adding "Add folder" to SharePoint 2010 document library views

A customer asked me if they could add a "Add Folder" to the document library list type in SharePoint 2010.  I said, "probably", but wasn't sure exactly how one might do it.  I dug in and found it!  The secret lies in the vwstyles.xsl file.  It is located in:

C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14TEMPLATELAYOUTSXSL

You will want to open the file and find the template called:

<xsl:template name="Freeform">

From there you should see some XSLT that is responsible for creating the "Add document" link.  Right above the variable declaraton"<xsl:variable name="Url">", you need to add a new one:

<xsl:variable name="FolderUrl">
<xsl:value-of select="$HttpVDir"/>/<xsl:value-of select="
$XmlDefinition/List/@title"/>/Forms/Upload.aspx?Type=1&amp;IsDlg=1&amp;List=<xsl:value-of select="$List"/>&amp;RootFolder=<xsl:value-of select="$XmlDefinition/List/@RootFolder"/>
</xsl:variable>

Then add a new table row below the current one that defines the "Add Document" link:

        <tr>
          <td class="ms-addnew" style=’padding-bottom: 3px’>
          <span style=’height:10px;width:10px;position:relative;display:inline-block;overflow:hidden;’ class=’s4-clust’><img src=’/_layouts/images/fgimg.png’ alt=” style=’left:-0px !important;top:-128px !important;position:absolute;’  /></span>
          <xsl:text disable-output-escaping="yes" ddwrt:nbsp-preserve="yes">&amp;nbsp;</xsl:text>
          <xsl:choose>
            <xsl:when test="
List/@TemplateType = '115'">
              <a class="ms-addnew" id="{$ID}-{$WPQ}"
                 href="{$Url}"
                 onclick="javascript:NewItem2(event, &quot;{$Url}&quot;);javascript:return false;"
                 target="_self">
                <xsl:value-of select="$AddNewText" />
              </a>
            </xsl:when>
            <xsl:otherwise>
              <a class="ms-addnew" id="{$ID}Folder"
                 href="{$FolderUrl}"
                 onclick="javascript:NewItem2(event, &quot;{$FolderUrl}&quot;);javascript:return false;"
                 target="_self">Add folder
              </a>
            </xsl:otherwise>
          </xsl:choose>
          </td>
        </tr>

BAM!  You now have a handly "Add folder" link at the bottom of the page instead of using the ribbon.  NOTE:  This change would need to be applied to all WFEs in the farm and you would need to re-check after every CU or Service Pack deployed.  Also, you will need to add some other logic to keep it from showing up in places you don't want it…I'll have to add this in a later update to this blog.

NOTE: Also keep in mind that this file is cached on its first load and an IISRESET is required in order to see any changes.

Enjoy!
Chris

 

CanUpgrade [Microsoft.SharePoint.Administration.SPIisWebSite] failed.

How do I always seem to be the guy finding this stuff is beyond me, but I'm always excited when I figure this stuff out…ok, was getting this error after trying to run the PSConfig after an upgrade to SP1 and December 2011 CU.

[OWSTIMER] [SPUpgradeSession] [INFO] [1/8/2012 1:48:44 PM]: SPWebApplication Name=SharePoint – xxx
[OWSTIMER] [SPUpgradeSession] [ERROR] [1/8/2012 1:48:44 PM]: CanUpgrade [Microsoft.SharePoint.Administration.SPIisWebSite] failed.
[OWSTIMER] [SPUpgradeSession] [INFO] [1/8/2012 1:48:44 PM]: SPWebApplication Name=SharePoint – xxx
[OWSTIMER] [SPUpgradeSession] [ERROR] [1/8/2012 1:48:44 PM]: Exception: The system cannot find the path specified.
[OWSTIMER] [SPUpgradeSession] [INFO] [1/8/2012 1:48:44 PM]: SPWebApplication Name=SharePoint – xxx
[OWSTIMER] [SPUpgradeSession] [ERROR] [1/8/2012 1:48:44 PM]:    at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
   at System.DirectoryServices.DirectoryEntry.Bind()
   at System.DirectoryServices.DirectoryEntry.get_AdsObject()
   at System.DirectoryServices.PropertyValueCollection.PopulateList()
   at System.DirectoryServices.PropertyValueCollection..ctor(DirectoryEntry entry, String propertyName)
   at System.DirectoryServices.PropertyCollection.get_Item(String propertyName)
   at Microsoft.SharePoint.Administration.SPIisVirtualDirectory.get_Path()
   at Microsoft.SharePoint.Administration.SPProvisioningAssistant.GetBuildVersionOnIisWebSite(Int32 iisInstanceId)
   at Microsoft.SharePoint.Upgrade.SPSequence.get_CanUpgrade()
   at Microsoft.SharePoint.Upgrade.SPUpgradeSession.CanUpgrade(Object o)

So what is causing this?  Using the handy reflector tool I went into the upgrade code and found the sequence that upgrades SPWebApplication objects.  This creates a list of all children objects that must be upgraded and then upgrades those objects one by one.  One of the children objects is the created or extended IIS web sites.  In order to check if these "CanUpgrade", it must look at the file in the IIS virtual directory "_vti_pvtuildversion.cnf".  If it can't find this file (the virtual path is deleted or missing), then the upgrade will stop for that web application and your upgrade is stuck in a weird state with this crappy error "Product / patch installation or server upgrade required"!

How do you fix?  You can try to add the directory back, but how do you know what the directory is that you need to add?  Funny thing, you can't find it unless you open up the SharePoint_Config database.  I will spare you the details and jump to the "supported" way of fixing this.  In Central administration you won't see the offending missing zone to delete it, therefore you must resort to powershell:

$webapp = get-spwebapplication "http://yourfailedurl"

foreach($setting in $webapp.iissettings.values)
{
$setting.path
}

foreach($zone in $webapp.iissettings.keys)
{
$z = $zone
}

$webapp.IisSettings.Remove("missingzone")
$webapp.update()

Rerun your PSconfig command, viola, you are back in business!!!

Enjoy!
Chris