Archive for the ‘SharePoint’ Category

Merging The History of Two Documents in SharePoint 2010

The problem I needed to solve was this: Two users were independently creating the same document. Once this was discovered, one of them was chosen to be the master document. But the users didn’t want to lose the other document, even though it was not chosen as the master. The users wanted to merge this document into the history of the master document. Technically, it is not possible to alter the history of any document in SharePoint. So I decided we would create a new third document, which was generated based on the two documents to be merged. By using the SharePoint API I could control the dates and merge the histories of the two documents into a third one. Once that was done, the original documents could be deleted.

The basic algorithm works like this:

  1. Choose 2 documents, one to be the master, one to merge into the master
  2. Fetch the history of both items into a List<>
  3. Sort the list by date
  4. Loop through the list and write the historical files to a new list item, creating a new history
  5. Write the current version of the merge document  to the new list item
  6. Write the current version of the master document to the new list item, including the properties

I did try to keep the properties to all the previous versions, but there was some resulting weirdness that went away when that part was removed. Since the metadata wasn’t a strict requirement, but a nice-to-have, I didn’t investigate that any further. YMMV. Here is the code for the algorithm:

/// <summary>
/// Merges the history of two SPListItems into a single SPListItem
/// </summary>
/// <param name="siteUrl">The URL of the SharePoint site</param>
/// <param name="listName">The Name of a list containing the documents to be merged</param>
/// <param name="keepDocName">The name of the document which is the "Master"</param>
/// <param name="mergeDocName">The name of the document which is merging it's history into the master.</param>
/// <param name="newName">The name of a third document which is the destination of the other two</param>
protected void MergeDocuments(string siteUrl, string listName, string keepDocName, string mergeDocName, string newName)
{
    
//Attach to SharePoint, get the list and documents
     var site = new SPSite(siteUrl);
     var web = site.OpenWeb();
     var list = web.Lists[listName];
     var keepUrl = string.Format("{0}/{1}", list.RootFolder.ServerRelativeUrl, keepDocName);
     var discardUrl = string.Format("{0}/{1}", list.RootFolder.ServerRelativeUrl, mergeDocName);
     var keep = web.GetListItem(keepUrl);
     var merge = web.GetListItem(discardUrl);
     //Push the versions from each document into a list that can be sorted by date
     var allVersions = keep.Versions.Cast<SPListItemVersion>().ToList();
     allVersions.AddRange(merge.Versions.Cast<SPListItemVersion>());
     allVersions.Sort(new SortVersionsByDate()); //SortVersionsByDate is a simple
IComparer for the created date
     //Create the upload url for the new document being merged
     var newUrl = string.Format("{0}/{1}", list.RootFolder.ServerRelativeUrl, newName);
     //Write each old version as the new file to create a merged history
     foreach (var version in allVersions)
     {
          if (version.IsCurrentVersion)
                continue; //Skip the current versions of each, we need for them to be the last two docs
          var oldFile = version.ListItem.File.Versions.GetVersionFromID(version.VersionId);
          var oldestFile = list.RootFolder.Files.Add(newUrl, oldFile.OpenBinaryStream(),
                                                     null, version.CreatedBy.User,
                                                     version.CreatedBy.User, version.Created, version.Created,
                                                     oldFile.CheckInComment, true);
          //Even though the dates were set in the call above, it doesn't work
         
// when setting them in the past, so call this fixes the issue
          UpdateListItem(oldestFile.Item, version.Created, version.Created);
          list.Update();
      }
      //Add the last version of the merged document
      WriteFileToSharePoint(list, merge, newUrl, false);
      //Add the Final version of the document
      WriteFileToSharePoint(list, keep, newUrl, true);
}
/// <summary>
///
Helper function to set the created and modified dates on a list item.
///
The reason for this function is to allow the dates to be set in the past.
/// 
</summary>
/// <param name="item">a SPListItem
</param>
/// <param name="created">Date created
</param>
/// <param name="modified">Date modified
</param>
protected void UpdateListItem(SPListItem item, DateTime created, DateTime modified)
{
     
//Update the modification/creation dates.
      item[SPBuiltInFieldId.Modified] = modified.ToLocalTime();
      item[SPBuiltInFieldId.Created] = created.ToLocalTime();
      item.UpdateOverwriteVersion(); //Keep the changes to the date/time from making a new version of the item
}
/// <summary>
///
Upload a file to a list based on an existing SPListItem
/// 
</summary>
/// <param name="list">The target list for the upload
</param>
/// <param name="doc">The existing SPListItem
</param>
/// <param name="newUrl">The url to upload the file to
</param>
/// 
<param name="final"></param>
protected void WriteFileToSharePoint(SPList list, SPListItem doc, string newUrl, bool final)
{
     Hashtable props = null;
     if (final)
        props = doc.Properties;
     var lastMergedFile = list.RootFolder.Files.Add(newUrl, doc.File.OpenBinaryStream(), props, doc.File.Author,
                          doc.File.Author, doc.File.TimeCreated, doc.File.TimeCreated,
                          doc.File.CheckInComment, true);
     UpdateListItem(lastMergedFile.Item, doc.File.TimeCreated, doc.File.TimeLastModified);
     list.Update();
}

I deployed this as a PowerShell cmdlet, which is explained here and here. Essentially that route was chosen because the merging was to be performed on request by an administrator. It would be easy enough to wire this up to a ribbon command in the UI though.

Posted on:
Posted in SharePoint | Comments Off on Merging The History of Two Documents in SharePoint 2010

“Keyset does not exist” Error while Installing SharePoint 2010

In my continuing battle with SharePoint, I needed to set up a new development environment. This time I am constrained to using Windows 7. Clearly, this has been done before and good instructions exist. But, as is frequently the case with me and SharePoint, it just doesn’t work as advertised. I was performing an install with a local account instead of a domain account (a constraint I have no control over), which I have had issues with before. This time I got a new error when trying to create the configuration database using the new-SPCofigurationDatabase cmdlet:

Keyset does not exist

Turns out this was happening whether I tried to do a complete install with the SharePoint version of SQL Express or with a full version of SQL Server. I found one post which purported to have the solution, but it wasn’t the case for me. I think Sasha was trying to do a standalone install, which is not what I wanted. I have SQL 2008 R2 installed, and I wanted to use that instead of getting another SQL Express instance installed.

This post pushed me in the right direction. Unfortunately, I did not have the directory pointed out in the post, possibly because I was on Windows 7. For me the path was:

C:\Users\All Users\Microsoft\Crypto\RSA

The service account I was using during the install did not have permission to that directory. So I gave it Read/Write access, and was able to create the configuration databases and using the SharePoint Products Configuration Wizard to complete the setup and configuration of SharePoint.

Executing PowerShell in a Post-Build Event

This is another one of those tasks where I thought it should be simple. That should be a red flag right there. I ended up spending way too much time on this, and as it turns out it is really only a matter of syntax. I ran across a lot of incorrect information out there, so I figured I should post this and help make the links to the right content easier to find.

I was actually building my own cmdLet for PowerShell in C#, and decided I should deploy it using PowerShell, which seemed appropriate. For me, the basic stumbling block for getting this working was quotes. I had multiple paths in my script, all of which had spaces in them, which necessitated the use of nested quotes. This was quite hard to figure out, but Philippe had the right information. He also pointed to this article, which had a thorough explanation of how to call PowerShell from the command line, which is essentially what you are doing inside a post-build event.

Since my cmdLet was really for SharePoint, I later figured out how to add it using the SharePoint 2010 project type in Visual Studio and some declarative XML, making all the previous pain a moot point.

Shrinking SQL Log Files During SharePoint Development

I have been developing for SharePoint lately, working on timer jobs. This resulted in tons of deployments and job runs, and before I knew it my SharePoint_Config database was huge! I was working in a virtual machine with disk partitions, and my data disk ran out of space. At least it was on a partition and I didn’t hose my OS….

So it seems to be a simple problem to solve. Backup the database, shrink the log file. Not so fast partner, this is SharePoint. The database is not in Simple recovery mode, it’s in Full recovery mode (for a reason I am sure, I just don’t know what it is). You can truncate the log file and remove the data, but the physical file won’t shrink. My colleague J Wolfgang Goerlich pointed out one of his old blog posts that was just the ticket. Essentially you are changing the mode, shrinking the file and changing the mode back all in one operation, which won’t upset SharePoint. The log file went from about 9 gigs to 600k. Nice.

As noted in the blog post, this is not a good thing to do to a production system, there are consequences. But for a dev system I wouldn’t worry about it.

Posted on:
Posted in SharePoint, SQL Server | Comments Off on Shrinking SQL Log Files During SharePoint Development

Install SharePoint 2010 Without a Domain on a Single Server

I was trying to create a virtual machine for development with SharePoint 2010, but I wanted to have a full SQL Server available instead of using the standalone installation with SQL Express. Unfortunately, this situation is not supported unless you are using domain accounts. My VPC is not part of any domain, so the install won’t proceed (which is by design apparently). There is a well-documented workaround using the New-SPConfigurationDatabase PowerShell script, so I tried that.  That seemed to work, it ran for a while, but then I ended up with an error:

User does not exist or is not unique

I found a post on the MSDN Forums that had the right answer. When the script prompted me for credentials, I put in a local username and password, and this was accepted. Until later when the script crashed. It turns out that I needed machinename\username and password. Even though the authentication prompt accepted the credentials without the machinename, somewhere down the line the script needed the machinename and it wasn’t there. Another important item to note was that once the PowerShell script crashed, it would no longer work even if you got all the prompts correct. I needed to exit the script shell and start again.

Posted on:
Posted in SharePoint, Strange Problems | Comments Off on Install SharePoint 2010 Without a Domain on a Single Server

A 64-bit 32-bit Dilemma

I recently ran into an interesting situation that I was able to solve through code. The project was to create a document migration tool, something I’ve done many times, so it seemed pretty straightforward. I was moving documents and metadata from a 3rd party content management system to SharePoint 2007. I was developing on a 32-bit OS on a machine provided by my client. The source system had an API, albeit 32-bit COM. There was some example code in their SDK for VB6, so at least there was something. It was easy enough to create a .Net wrapper to that COM DLL and figure out the API. The SharePoint code was not too difficult either, something I’ve done before. In this case I had to use the API, not the web services because I wanted to keep the original file created/modified dates from the source system. You can’t set those using the web services. Therefore, the tool is forced to run on the SharePoint server (this is important factor for the upcoming dilemma). Luckily the source system was accessible remotely.

Works on My Machine

Of course, in my dev environment everything works great. So I copy my app to a SharePoint server to test in an environment more like production. It crashes immediately with a COM exception:

 Retrieving the COM class factory for component with CLSID {FDD9199A-1BB4-4433-B9E1-D550D3118676} failed due to the following error: 80040154.

What?? After a little research I determine the problem: the SharePoint server is 64-bit. My .Net code was compiled for any CPU, so it ran as 64-bit on the server, while it was 32-bit on my dev box. When compiled for 64-bit, it can’t access the 32-bit COM DLL (there might be a solution for this, I didn’t find it). I went back, and forced the code to compile for 32-bit. The app soared right past the error for the COM DLL, but came to a screeching halt when trying to access the SharePoint API. If SharePoint is installed on a 64-bit server, the code has to run compiled for 64-bit in order to access the API. Now you see the dilemma. I have to use a 32-bit only COM DLL and a 64-bit app for accessing SharePoint. I can’t skirt the issue using the SharePoint web services because I need functionality only available in the API.

My Solution

I decided on using two applications, one compiled for 32-bit which accesses the source system and fetches the data, and a second one compiled for 64-bit to access the SharePoint API. Five years ago I probably would have done the same thing, and write the data to an XML file from the first app, and then launch the second app and read the data from the file. There were hundreds of thousands of documents to migrate, all that disk access would be a performance killer. But now that I have WCF, I could do this much more elegantly.

32-64solution[1]

The 32-bit app contained the UI, and was able to use the 32-bit COM DLL to fetch documents and metadata. Once it had a logical set of data in memory, it used WCF with named pipes as the transport protocol to pass the data structure to the second app. The second app was 64-bit, and launched by the first app as a process using the System.Diagnostics namespace. It communicated just fine with the SharePoint API. The second app had no GUI, but I kept the console window for writing messages and to provide a way to manually exit the app in case things went awry. The first app kept a handle on the process that launched the second app, so that when a user closed the first app, it could close the second app for  the user.

WCF was a good fit for this dilemma. The only issue I encountered was managing the message size, because WCF expects you to configure a maximum size for that. Of course configuring WCF is not that easy, but you only have to do it once. I was pleased that I could make a mex endpoint in the 64-bit app, and then use Visual Studio to make a Service reference to it from the 32-bit app, and the configuration comes out for named pipes even though the mex endpoint was http. Another feature I didn’t know but managed to discover. In the end, the process is seamless to the user, and does what I need without much disk access to slow it down.

Posted on:
Posted in .Net 3.5, SharePoint, WCF | Comments Off on A 64-bit 32-bit Dilemma

Method for Installing WSS 3.0 Beta2 TR as Slipstream

From JOPX on SharePoint 2007.

Also, the WSS-only install for WSS 3.0 is available here.

Posted on:
Posted in SharePoint | Comments Off on Method for Installing WSS 3.0 Beta2 TR as Slipstream

SharePoint Web Part Template for Visual Studio Install Problem

I tried to install the SharePoint web part templates today and got this error:

“Visual Studio .NET must be installed before you can install the Web Part Templates for Visual Studio .NET.”

But of course, Visual Studio is installed. I found two answers to the problem on the Usenet:

Add Missing Registry Keys

Reinstall Visual Studio

Along the way I also discovered that you must either have the Microsoft.SharePoint.dll on your machine or on a share you can access in order for the install to proceed.

Posted on:
Posted in SharePoint, Visual Studio | Comments Off on SharePoint Web Part Template for Visual Studio Install Problem