in

SharePoint Blogs

The Best Place for SharePoint-related Blogs

Real-World SharePoint Experiences by John Powell

Real-word experiences planning, implementing, customizing and supporting SharePoint.
  • How to Install Microsoft eScrum 1.0 process template on TFS 2008 Beta 2 "Orcas"

    Microsoft hasn't released an updated version of the eScrum process template for Visual Studio Team Foundation Server (TFS) 2008 Beta 2, code name "Orcas."  Until a supported version is available, these instructions will enable you to deploy the eScrum 1.0 process template (for TFS 2005) on TFS 2008 Beta 2.

    Assuming you already have TFS 2008 Beta 2 up and running, you will need to install the following prerequisites:

    1. Visual Studio 2005 Team Explorer (the eScrum installer checks for it).  If you are adventurous, you could modify the eScrum msi file using Orca (not to be confused with Orcas) to eliminate the launch condition check for Team Explorer 2005.  Orca is a msi editor packaged in the Windows SDK, but you can download it separately here.

    Once you have the prerequisites, install the eScrum template.  In addition to adding a process template to TFS, the installer will also deploy a WSS 2.0 site template and create an additional website.  After the installation you will modify the website configuration and replace the WSS 2.0 site template with a WSS 3.0 site template.

    Follow these steps to remove and replace the eScrum WSS 2.0 template that was installed in WSS 3.0. 

    1. Download the eScrum WSS 3.0 template and place it in a temporary directory on the TFS server (c:\temp).  I upgraded this template to WSS 3.0 by installing it on a WSS 2.0 image and then performing an in-place upgrade to WSS 3.0.
    2. Remove the WSS 2.0 template that the eScrum installer deployed.  Run the following command on the TFS server (or the WSS server if you split WSS from TFS): C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\stsadm -o deletetemplate -title eScrum
    3. Reset IIS 
    4. Deploy the template you downloaded by running the following command: C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\STSADM.EXE -o addtemplate -filename "c:\temp\escrum.stp" -title eScrum
    5. Reset IIS

    Follow these steps to ensure the eScrum website is configured properly (there is a website on the TFS server in addition to the WSS site).

    1. Open the eScrum web.config file (C:\Program Files\Microsoft Visual Studio 2008 Team Foundation Server\Web Services\eScrum\) and ensure the following configuration section is the first node in the <configuraiton> section:
      <configSections>
            <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                <sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                    <section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false"/>
                    <sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
                        <section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false"/>
                        <section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false"/>
                        <section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false"/>
                    </sectionGroup>
                </sectionGroup>
            </sectionGroup>
            <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=1a5b963c6f0fbeab"/>
        </configSections>
    1. Using Visual Studio 2008 Beta 2 Team Explorer, create a new team project using the eScrum template. 
    2. Once the project is created, there is one more configuration step required to get the eScrum website to work.  Edit the RegisteredGroups xml file located in C:\Program Files\Microsoft Visual Studio 2008 Team Foundation Server\Web Services\eScrum\RegisteredGroups.xml and add the name of your TFS server and Project (you can add more projects by adding more <Server> nodes), for example:
    <?xml version="1.0"?>
    <RegisteredGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <isflatGroupCollectionDirty>false</isflatGroupCollectionDirty>
        <Server Name="TFSSETUP" Uri="http://orcasbeta2_tfsvsts:8080/">
            <Group Name="eScrumProject" />
        </Server >
    </RegisteredGroups>
    Verify the eScrum website is working by accessing the url where you installed it.

    Verify the eScrum WSS 3.0 site is working for the TFS project you created.

    Although unsupported, I hope these instructions will help until Microsoft releases a Visual Studio 2008 TFS eScrum process template.

  • What Happens to Listings During Upgrade and Migration

    In SharePoint 2003, each area had a built-in list "Listings" which was used to maintain links between content in different portal areas as well as external links.  This was somewhat of a flawed design in that listings could very easily become broken if the content or area it points to is renamed, moved or deleted.  But that is a topic for another blog post on listing validation.  This article gets into the details of exactly how a SPS 2003 Listing is migrated to MOSS 2007.

    A list named "Listings" is created in each site.  This is no longer a built-in list; in other words, you can delete or rename it if you choose.

    A content type, "Listing" (which inherits from the Link content type), is created at the site-collection, and the Listings list is set to use that content type.

     

    Here is the definition of the Listing content type:

    If you edit one of the listing items, you can see how it was migrated.  Note the url has been fixed up with the more readable MOSS url and the description contains the cryptic SPS 2003 url.

    Any page that contained a SPS 2003 Grouped Listings web part is replaced with a Content Query web part in the corresponding MOSS page.  The web part is named "Grouped Listings," but that's just the title.  It's a Content Query web part.

  • 64-Bit Gotchas

    There are a few very important things you need to be aware of when choosing between 32-bit and 64-bit MOSS environments.

    Upgrade and migration

    .Net 1.1 framework is not installed on Windows Server 2003 64-bit by default.  You can download and install it, but here's the issue: to run .Net 1.1 code in IIS, you have to set IIS to run in 32-bit mode.  This is a global server setting and by doing this, you are also running MOSS in 32-bit mode, losing any benefits of a 64-bit installation.  In other words, you might as well have gone 32-bit in the first place.  Here is the Microsoft support article on how to configure IIS for .Net 1.1 on Windows 2003 64-bit.

    Why is this an issue?  Chances are, you have an investment in third-party or custom-developed .Net 1.1 SharePoint 2003 customizations such as web parts and controls.  Be prepared to build extra time into your migration schedule to update these to .Net 2.0.  In many cases, this is the desirable path and the right time to do it is during upgrade.  The downside is that you are potentially de-stabilizing solutions and customers won't see a big return in terms of functionality for this investment.  But if the customer wants to run 64-bit, then the investment is necessary.

    Indexing and IFilters

    Not all IFilters are available in a 64-bit version.  In fact, at the time of this posting, the PDF IFilter is not available from Adobe.  You can get a 64-bit from a Foxit software here.  If necessary, you can make your index server 32-bit until 64-bit IFilters are widely available.  You can mix 32-bit and 64-bit MOSS servers in the same farm, by the way.

    Hardware drivers

    The technicians building the servers need to ensure 64-bit drivers are available for the hardware and that they use them!

    Application integration

    Applications that co-exist with SharePoint may be dependent on .Net 1.1.  A good example is Business Scorecard Manager.  Check out this article on that subject.

    Experiencing isues that are specific to 64-bit environments

    You may find as I have errors that are specific to 64-bit environments.  Because 64-bit is relatively new in terms of adoption, you may also find it more difficult to find solutions.  I'm not saying that Microsoft won't support you, but I am saying you will find less information from blogs and forums which are often very helpful for problem solving.  A great example of this is the following error which I have been unable to resolve and exists on 2 different 64-bit farms.  The symptoms go like this: you are unable to open IIS Manager on the server.  iisreset will fix the issue.  The event log is filling up with variations of the following error message:

    Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

    By the way, if anyone has the solution to that problem, please let me know!

    Do the benefits outweigh the gotchas?

    64-bit architectures are more scalable and performant.  64-bit servers can have more physical memory and CPUs as well as increased virtual memory capabilities.  Consider this example from Microsoft:

    The application, which had been running on a dual processor, 32-bit server was moved to a new four-way, x64 server with 32 GB of RAM running Windows Server 2003 x64 Edition and a beta x64 version of SQL Server 2005. The historical query, which had taken 8 hours to finish on 32-bit Windows and SQL Server, now finishes in less than 5 minutes. 

    If you are interested in learning more about the benefits of 64-bit Windows, check out this whitepaper from Microsoft.

    Summary

    Be aware of the "gotchas" with a 64-bit MOSS farm when determining if 64-bit is the right path for your (or your customer's) environment.

  • Quiescing - Can You Use It In a Sentence?

    There is a feature in MOSS that enables you to gradually shut down the farm for maintenance.  This feature is called "Quiescing."  I have to admit, this isn't a word I use everyday, so I looked it up on Dictionary.com and found the following:

    "become quiet or quieter"

    "To render quiescent, i.e. temporarily inactive or disabled. For example to quiesce a device (such as a digital modem). It is also a system command in MAX TNT software which is used to temporarily disable a modem or DS0 channel."

    Here is how Microsoft spins it:

    "Quiescing is the process of gradually bringing long-running applications of a resource offline without incurring data loss. It has been introduced in Microsoft Office servers for 2007."

    So if you need to perform maintenance on a farm, you "quiesce" it. IMHO, something like "Take Farm Offline" would have been a better choice.

    Why should you use the quiesce feature?

    Simply put, to prevent data loss.

    How do you quiesce the farm?

    1. From Central Administration, Operations, select "Quiesce Farm."

    2. Enter the number of minutes in which you want the farm to be fully quiesced and click "Start Quiescing."

    3. The page will display the quiescing status.  [From Microsoft] "Quiescing has three states: normal, quiescing, and quiesced. Normal is the active state in which the farm handles all requests that come into it. Quiescing is the state in which the farm only handles requests from existing sessions, and quiesced is the state in which the farm does not allow any new sessions to start."

    How do you "un-quiesce" (reset) a farm:

    1. From Central Administration, Operations, select "Quiesce Farm."

    2. Click "Reset Farm."  Why couldn't they have come up with an obscure word for "reset?"

    What quiescing doesn't do.

    I expected that after quiescing a farm I'd see a page saying something like "This site is down for maintenance" when accessing a site collection.  It doesn't do that.  I found this information useful: 

    [From Microsoft] "Not all applications and services use quiescing. Many other features and operations do not need to use quiescing because they do not have long-running sessions where users enter data over multiple server requests without saving information. For instance, when a user edits an item in a SharePoint list, the information is saved to the database in a single transaction."

    [From Microsoft] "In InfoPath Forms Services, a form-filling session may require several communications with a server as the form posts back for server-side data processing for operations such as view switching. Data from the session is usually not saved until the very end when a user submits or saves the form that they’re filling. If an administrator were to take the farm offline while some users were in the process of filling forms out, the users would lose all of the data accumulated so far in their session."

    To summarize, there are 2 ways to use quiescing.  The first is to impress your friends.  Casually slip the word into a conversation and watch their expression.  The second way to use quiescing is to perform maintenance on your farm and prevent data loss (the key benefit of using this feature).

  • Setting up the Office HTML Viewer to Support Users Without Office

    The Office HTML Viewer server provides support for users who want to view the content of files in Microsoft Windows SharePoint Services document libraries, but do not have Word, Excel, or PowerPoint from Office 97, or a newer release of Office, installed on their local computer.

    This is a free download from Microsoft: http://www.microsoft.com/downloads/details.aspx?familyid=c62e0232-9bf6-48fc-829e-5c34d5c8b15f&displaylang=en

    Dedicate a server to the Html Viewer Server.  Once you have it installed, go to Central Administration > Operations > External Service Connections > Html Viewer and configure MOSS to use the server.

    Enter the url of the Html Viewer server and check the box "Allow HTML viewing."

     

  • SharePoint Metadata Integrity Checker Utility

    The metadata features of SharePoint 2003 and 2007 enable you to customize, sort, group, filter and search documents and lists.  In SharePoint, this equates to adding columns that describe and categorize list items.  SharePoint 2007 takes a more disciplined content management approach with the introduction of site columns and content types. 

    Although powerful, there is an integrity flaw in both SharePoint 2003 and 2007 that you should be aware of.  When you add a choice or multi-choice column, you can supply a list of values.  If you delete or modify values in this list, any list items using the old values will not be updated and will still contain the original values.  Those of you with database design experience can see the issue immediately: SharePoint does not maintain referential integrity.  The list item column references a value in a lookup list which may change. 

    I'll demonstrate the issue.

    Go to a document library and select Modify settings and columns.

    Click on Add a new column.

    Name the column "Color" and enter 3 choices "White, Brown and Balck."  Notice that I have misspelled "Black."  Although this is a simple example, errors like this occur in the "real world".  In addition, values can and do change.  Imagine you uploaded 100 documents using a given company name and the company goes through a name change.  Without a utility, your only option is to modify the column value and update each documents by hand.  From experience, SharePoint content managers usually just update the column list value without realizing existing list items contain the old value.

    After adding the column, add a new document to the list.  When prompted, select the value "Balck" for the color.

    You can see the list item contains the misspelled value.

    Go back to the document library and edit the new column you added.  Change the value "Balck" to "Black."

    Even though you changed the value, the list item still has the old value.

    If you edit the list item, notice the color drop down reverts to the default value because it was unable to find "Balck" in the list.  Until the list item is updated, it will contain an invalid value.

    How do you deal with this issue?  First, make sure your content managers are aware of the issue.  From now on, use a utility when you need modify or remove list item values that are in use.  You can do it by hand if there are a small number of list items. 

    Once the issue is under control, analyze and fix integrity issues.  I developed a sample in C# that will  analyze a SharePoint 2003 environment.  In a later post, I will update it for 2007.

    The code starts at a given url and recursively analyzes each list and list item verifying each column contains only valid values.  The end result is a list of invalid list items that you could output to a log file or run nightly and send e-mail notifications when issues are encountered.  This code is not comprehensive, it is only a sample to get you started. 

    If want to see if you have any metatdata integrity issues in your environment, copy the .exe in bin\debug directory in the attached zip file to your SharePoint server and run it.  After it runs, open the log file and see how many issues you have. 

    Download the full source code here

  • Display Version and Other SharePoint Metadata in Word 2003 Documents

    You can view and edit text columns from a document library from a Word 2003 document. For example: 

    This is a great integration point between Office and SharePoint, but there are a couple a gotcha's you should be aware of, and the following very reasonable and realistic requirements for a document management solution will highlight them.

    1. The version and author from SharePoint should be displayed in the header of Word 2003 documents in the document library
    2. The version and author from SharePoint should be updated automatically when users open or print documents from the document library

    The first issue is that only SharePoint text columns can be synchronized into Word document properties.  Version is not a text column.  The second issue is that the fields used to display the metadata in Word are not updated automatically when opening or printing the document.  I will present a solution (or workaround) for both issues, but first let's examine how the integration works.

    When a Word document is created from or uploaded to a SharePoint document library, text columns are copied to Word document properties.  SharePoint will create the document properties for you when the document is first added, but not when it is updated.  You can always manually add a document property if you need to--just make sure the name exactly matches the SharePoint text column name.

    Here's a simple example to illustrate the integration.

    Add a text column to your document library:

    For testing purposes, edit one of the document library items and enter some text in the custom column:

    Now open the document in Word 2003 and examine the document properties.  Select File...Properties.  If you see the following dialog, click on File Properties.

    Click on the Custom tab and note that the MyCustomColumn document property exists and contains the text from the SharePoint list item.

    To demonstrate the metadata is two-way, edit the value of MyCustomColumn (the UI is slightly cloogy here).  Click on the MyCustomColumn in the bottom list.  Enter a value in the Value field and then click the Modify button:

    Save the document back to SharePoint and notice the column is updated.  This is great if want two-way updates, but bad if you want it to be read-only in Word.

    In order to display the version number, we need a text column that contains the version number.  One way to accomplish this is to use an event handler to copy the version number to a read-only text column.  Here is the code for the event receiver.  I'll leave it to you how you want to deploy the event receiver but a simple way is write a program to register the event receiver on the document library.

        //****************************************************************
        /// DocumentVersionEventReciever
        /// <summary>
        /// An event receiver to provide a custom version number
        /// text field that can be displayed in Word 2003.
        /// </summary>
        //****************************************************************
        public class DocumentVersionEventReciever : SPItemEventReceiver
        {
            #region Constants
    
            private const string FIELD_DOC_VERS = "DocumentVersion";
            private const string FIELD_ITEM_VERS = "Version";
    
            #endregion Constants
    
            #region Methods
    
            //****************************************************************
            /// SetDocumentVersion
            /// <summary>
            /// Copies the document version to a text field so it can be
            /// sychronized with a Word property.
            /// </summary>
            /// <param name="item"></param>
            //****************************************************************
            private void SetDocumentVersion(SPListItem item)
            {
                item[FIELD_DOC_VERS] = item[FIELD_ITEM_VERS];
                item.SystemUpdate(false);
    
            } // end SetDocumentVersion()
    
            //****************************************************************
            /// DeleteDocumentVersionField
            /// <summary>
            /// Deletes the document version field.
            /// </summary>
            /// <param name="list"></param>
            //****************************************************************
            private void DeleteDocumentVersionField(SPList list)
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    list.Fields[FIELD_DOC_VERS].ReadOnlyField = false;
                    list.Fields[FIELD_DOC_VERS].Update();
                    list.Fields[FIELD_DOC_VERS].Delete();
    
                    // Check
                    if (list.Fields.ContainsField(FIELD_DOC_VERS))
                        throw new ApplicationException("Failed to delete field '" + FIELD_DOC_VERS + "'.");
    
                }); // end run with elevated
    
            } // end DeleteDocumentVersionField()
    
            //****************************************************************
            /// CreateDocumentVersionField
            /// <summary>
            /// Creates the document Version field.
            /// </summary>
            /// <param name="list"></param>
            //****************************************************************
            private void CreateDocumentVersionField(SPList list)
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    list.Fields.Add(FIELD_DOC_VERS, SPFieldType.Text, false);
                    SPField fld = list.Fields[FIELD_DOC_VERS];
                    fld.ReadOnlyField = true;
                    fld.Update();
                    list.Update();
    
                    // Check
                    if (!list.Fields.ContainsField(FIELD_DOC_VERS))
                        throw new ApplicationException("Failed to create field '" + FIELD_DOC_VERS + "'.");
    
                    // Populate existing Versions
                    foreach (SPListItem liExisting in list.Items)
                    {
                        SetDocumentVersion(liExisting);
                    }
    
                }); // end run with elevated
    
            } // end CreateDocumentVersionField()
    
            //****************************************************************
            /// SetDocumentMetaData
            /// <summary>
            /// Sets document metadata.
            /// </summary>
            /// <param name="properties"></param>
            //****************************************************************
            private void SetDocumentMetaData(SPItemEventProperties properties)
            {
                try
                {
                    // Prevent triggering of other events
                    this.DisableEventFiring();
    
                    SPListItem li = properties.ListItem;
                    SPList list = properties.ListItem.ParentList;
    
                    //DeleteDocumentVersionField(list);
    
                    // Add field if doesn't exist
                    if (!list.Fields.ContainsField(FIELD_DOC_VERS))
                    {
                        CreateDocumentVersionField(list);
                    }
                    else
                    {
                        SetDocumentVersion(li);
                    }
                }
                catch (Exception ex)
                {
                    properties.ErrorMessage = ex.Message;
                }
                finally
                {
                    // Enable triggering of other events
                    this.EnableEventFiring();
                }
    
            } // end SetDocumentMetaData()
    
            #endregion Methods
    
            #region Event Handlers
    
            //****************************************************************
            /// ItemUncheckedOut
            /// <summary>
            /// The event that occurs when an item is checked out.
            /// </summary>
            /// <param name="properties"></param>
            //****************************************************************
            public override void ItemUncheckedOut(SPItemEventProperties properties)
            {
                SetDocumentMetaData(properties);
    
            } // end ItemUncheckedOut()
    
            //****************************************************************
            /// ItemCheckedOut
            /// <summary>
            /// The event that occurs when an item is checked out.
            /// </summary>
            /// <param name="properties"></param>
            //****************************************************************
            public override void ItemCheckedOut(SPItemEventProperties properties)
            {
                SetDocumentMetaData(properties);
    
            } // end ItemCheckedOut()
    
            //****************************************************************
            /// ItemCheckedIn
            /// <summary>
            /// The event that occurs when an item is checked in.
            /// </summary>
            /// <param name="properties"></param>
            //****************************************************************
            public override void ItemCheckedIn(SPItemEventProperties properties)
            {
                SetDocumentMetaData(properties);
    
            } // end ItemCheckedIn()
    
            //****************************************************************
            /// ItemUpdated
            /// <summary>
            /// The event that occurs when an item is updated.
            /// </summary>
            /// <param name="properties"></param>
            //****************************************************************
            public override void ItemUpdated(SPItemEventProperties properties)
            {
                SetDocumentMetaData(properties);
    
            } // end ItemUpdated()
    
            //****************************************************************
            /// ItemAdded
            /// <summary>
            /// The event that occurs when an item is added to the list.
            /// </summary>
            /// <param name="properties">Properties about the item</param>
            //****************************************************************
            public override void ItemAdded(SPItemEventProperties properties)
            {
                SetDocumentMetaData(properties);
    
            } // end ItemAdded()
    
            #endregion EventHandlers
        }

    You can see that the event receiver is ensuring the text column DocumentVersion always has the latest copy of the version number.  I do have a criticism of this code, and that is the creation of the column by the event receiver.  In a production envrionment, I would recommend creating a feature that creates a content type and hooks an event receiver to that content type.

    Insert the DocumentVersion field in the document header (or wherever you want the version to appear).  From the menu, select Insert..Field.  Find the DocProperty field name and then select the DocumentVersion field property.

    The version will appear in the document, and that leads into the next issue.  The field won't update until the user clicks on the field and selects Update Field.  (There is also an option you can set to update fields before printing, but as you recall, the requirement is to display the current version number when the document opens).

    To illustrate the issue, check out the document in SharePoint and check it back in (or publish a major version).  Then open the document in Word.  You will see the field displays the old version number.  If you access the DocumentVersion property, you will see it has the correct value and the field just needs to be updated.  We are going to solve that issue with a Macro.

    In Word, select Tools...Macros...Macro.  Then click on Create to create a new Macro.  Name the macro AutoOpen, and place the following code in the editor:

    Sub AutoOpen()
        With Options
            .UpdateFieldsAtPrint = True
            .UpdateLinksAtPrint = True
        End With
        ActiveDocument.Fields.Update
    End Sub

    This code is setting the option to update fields and links before printing.  More importantly, it is updating all fields when the document opens.  You have a couple of choices for deploying this.  I'd recommend making this document the template for the document library.  That way, if users create documents from the document library, the properties, fields and macro is all setup.

    I would caution you to set users expectations with this solution / workaround.  If they upload documents from outside SharePoint they obviously will not have the fields set up in the Word document to display the version number.  They also will not have the macro. 

    I hope you find this solution helpful for your Office 2003 users.  If Office 2007 is an option, I recommend investigating the labeling and bar coding features of SharePoint 2007 first.

     

  • Migrating SharePoint Portal 2003 Database to SharePoint 2007 using Content Database Migration

    There are pockets of information avaiable on migration strategies, but it's difficult to find concise instructions for migrating a content database.  By the way, I would recommend this approach over in-place or gradual simply because I don't lose sleep because it has the least risk of de-stabilizing the production SharePoint environment. 

    If you are lucky enough to have a shiny new MOSS farm and you are trying to migrate the content to, then you are in the right post.  Please keep in mind that these are the simple instructions with superflous details purposely omitted.  I encourage you to research the details around each of these steps before making this your migration strategy.  You will find a few links at the end of this post that are comprehensive.  Also, I am only migrating the content database in this scenario, not the search [site]_SERV, profiles / audiences database [site]_PROF or single sign on [site]_SSO databases.  You can migrate those too by the way!

    1. Download the upgrade pre-scan tool to your current production SharePoint 2003 server.  Here's the link http://www.microsoft.com/downloads/details.aspx?familyid=e8a00b1f-6f45-42cd-8e56-e62c20feb2f1&displaylang=en
    2. If you're paranoid like me, backup your SharePoint 2003 farm using the SharePoint Backup and Restore tool.
    3. Run the pre-scan tool from your SharePoint 2003 server using the /all and /c PREUPGRADESCANCONFIG.XML.  (The XML file tells the prescan the portal templates are not custom).  The pre-scan tool will identify any issues that will cause upgrade failure; and before you ask, yes you DO need to do this step.  It will also make some harmless modifications to your SharePoint 2003 databases.  If you encounter any errors in the pre-scan tool, view the log file and correct them.
    4. Backup the SharePoint 2003 content database(s)--the [name]_SITE database(s) using Sql Server tools (2000 or 2005). I realize you already did this in step 2, but do it again anyway.
    5. Copy the backup file to your MOSS database server and restore it using Sql Server tools.  This would be a good time to give it a name consistent with your shiny new MOSS databases.
    6. Create a web application, but not a root site collection
    7. Run the following command: stsadm -o addcontentdb -url [site collection url] -databasename [name of database you restored].  This may take several hours to complete.
    8. Check out the migrated content by browsing to [site application url] and get a feel for what customizations are broken or missing.

    Here are some very good comprehensive posts on content migration.  Please let me know if there are others.

    MS-Advantage's SPS to MOSS 2007 Upgrade Tips (Database Migration) by Alan Coulter

    WSS 3.0 & Sharepoint 2007 Database Migration Strategy Steps For Migrating A Sharepoint 2003 Site by Jason Fortner

    Don't be afraid of Prescan - Part 1 by Joel Oleson

    Your Friend Prescan.exe - How to Get it & What it Does - Part 2 by Joel Oleson

    Deploy a new farm, then migrate databases by Microsoft

     

  • Search SharePoint from the IE 7 Toolbar

    Here is an easy way to search a SharePoint site from the IE toolbar without having to develop an add-in. 

    Microsoft implemented open search in IE 7.  Here's how it works: when a user browses to your site, the search provider drop down lights up.  If they click the drop down, they will see your custom search provider that they can add temporarily or permanently to their list of search providers.  Here's an example:

    To temporarily use your custom search provider, the user simply selects the menu item and the search box will change (note that the yellow star indicates the search provider is temporary).  For example:

    To permanently add your custom search provider, the user selects Add Search Providers and selects it:

    The following dialog appears:

    After that, your custom search provider will remain in the list (note there is no yellow star, indicating when the user navigates away from your site, the search provider will remain).

    When the users searches from the search box, they are taken to the SharePoint search results page.

    Here's how to implement it.

    Place the following tag in the home page of your site.  You can put it anywhere in the page (but put it in the <head> element if possible).

    <link title="SharePoint Search" type="application/opensearchdescription+xml" rel="search" href="SharePointSearchProvider.xml" />

    Then create an XML file using the following code and upload it to a url that matches the href attribute in the link tag.

     <?xml version="1.0" encoding="UTF-8"?>
     <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
       <ShortName>SharePoint Search</ShortName>
       <Description>Search SharePoint from Internet Explorer</Description>
       <Tags>SharePoint Search</Tags>
       <Contact>john@johnwpowell.com</Contact>
       <Url type="text/html"
            template="http://yoursharepoint.com/searchcenter/Pages/Results.aspx?k={searchTerms}"/>
     </OpenSearchDescription>

    You can see that open search is simply passing the search term to the SharePoint search results page using the k (keyword) parameter.  There is a special token {searchTerms} that will be replaced with the text the user types in the search box.

    I'll leave it you how you want to deploy the xml file and insert the link element into the page.  One simple approach is to upload the XML file to a document library and use a content editor web part for the link tag. 

    Here are the full specifications for open search: http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_description_document

     

  • Choosing between AD and SharePoint Groups.

    A question that comes up often is what is the best approach for assigning permission levels in SharePoint? 

     

    Here are some guidelines from my personal experience:

     

    A general rule of thumb is the less security principals you have, the more scalable your security design will be.  In other words, it's easier to assign permission levels to 1 group than 100 users.

     

    Avoid assigning permission levels directly to user accounts—use either an Active Directory (AD) group or a SharePoint group to contain the users.  If there is a one-to-one mapping between an AD group and a SharePoint permission level, you could assign permissions to the AD Group rather than creating a SharePoint group, but if you always use a SharePoint group, you have a clean way to add more users/groups later if you need to. 

     

    Use SharePoint groups over AD security groups.   You can delegate control of SharePoint groups to site administrators.  If you use AD groups, there could be a bottleneck getting users added/removed from them since only a select few in the organization have permissions.  Another issue with AD groups is you cannot view the members in SharePoint, making it difficult to determine who has access to what. 

     

    Here is an excellent post that compares and contrasts SharePoint and AD groups

  • Alerts failing: Cannot Connect to Smtp Host.

    I recently had an issue where alerts were not being sent out from a MOSS 2007 server.  The event log had several "Cannot Connect to Smtp Host [ENCRYPTED]" entries.  Except, that instead of saying [ENCRYPTED], it was actually a bunch of Chinese and non-printable characters.  My first thought was that the Smtp host name had been corrupted, but that was an incorrect conclusion. 

    I am going to share a little acronym I learned in the military for troubleshooting electronics that helped me solve this issue:  RVAIR

    Recognize.  First, someone has to notice there is a problem.  In my case, no alerts

    Verify.  Not that I can't trust users, but I must verify the problem and be able to reproduce it consistently

    Analyze.  Understand the issue and potential causes

    Isolate.  This is the most critical step.  Remove all the ancilliary "stuff" and find the root cause

    Repair.  Fix the problem

    One thing to remember about Smtp error messages is you need to see the inner exception and stack trace to get to the actual error.  SharePoint doesn't output the detail you need in the event log, so to isolate the problem and get to the root cause, I wrote a little mail tester program.  Basically, it takes everything else out of the equation and tests the machine's ability to send a message via Smtp.  Here is the source:

        //****************************************************************
        /// Program
        /// <summary>
        /// A console application to test mail
        /// </summary>
        /// <remarks>
        ///        <u>Created</u><br/>
        ///        Author: johpow<br/>
        ///        Date: 2/20/2007 7:08:55 AM<br/>
        ///        <u>History</u><br/>
        ///        Modified by: <br/>
        ///        Date: <br/>
        ///        Description: <br/>
        /// </remarks>
        //****************************************************************
        public class Program
        {
            public static void Main(string[] args)
            {
                try
                {
                    // Check arguments
                    if (args.Length != 3)
                    {
                        ShowUsage();
                        return;
                    }
    
                    // Create message
                    MailMessage mmMessage = new MailMessage();
                    mmMessage.To.Add(new MailAddress(args[2] as string));
                    mmMessage.From = new MailAddress(args[1] as string);
                    mmMessage.Subject = "TestMail";
                    mmMessage.Body = "This is a test";
    
                    // Send message
                    SmtpClient sc = new SmtpClient(args[0] as string);
                    sc.Send(mmMessage);
    
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
                finally
                {
                    //Pause
                    Console.WriteLine();
                    Console.Write("Press a key >");
                    Console.ReadKey();
                }
            }
    
            private static void ShowUsage()
            {
                Console.WriteLine("Usage");
                Console.WriteLine("TestMail [SmtpServer] [FromAddress] [ToAddress]");
            }
    
        } // end Program

    When I used the TestMail utility, I finally got to the root error:

    An established connection was aborted by the software in your host machine

    More helpful than "Cannot connect to Smtp Host," wouldn't you say?  So that isolated it down to a firewall or anti-virus software blocking the Smtp port (25).  As it turns out it was anti-virus software.  The point is, you can see how by analyzing the problem, determining potential causes, and ruling them out one by one, you get to the root cause.

    I have used this mail tester utility several times troubleshooting Smtp connection, relay and authentication issues from applications.  I hope this simple utility is something you'll consider adding to your toolkit, along with RVAIR.  The source code and the utility are attached to this post.

  • Browser Crashes when Opening Office Documents from SharePoint

    Are you experiencing browser crashes when accessing Office Documents in SharePoint?  Something like this:

     

    I recently encounted an issue where Internet Explorer 6 and 7 would crash when opening Office documents on SharePoint 2003.  Since it didn't happen to all users, it was pretty obvious that some client configuration was incompatible with the component SharePoint uses to open Office documents. 

    Have you ever noticed a special dialog is diplayed when you click on a Word document in a document library?  For example:

    Under the hood, this dialog is displayed by an ActiveX control.  If you view the source of a document library page, you will see the SharePoint.OpenDocuments component being called.   For example:

    var url='http://moss2007/sites/documents/projectscopestatement.doc';
    var od3 = new ActiveXObject('SharePoint.OpenDocuments.3');
    od3.ViewDocument3(window,url,3,'');

    What is the cause of this problem? 

    As it turns out, some users had all or parts of Office 2007 installed such as SharePoint Designer.  Office installed a component, "owssup.dll" that causes the issue.  

    How do you fix it?

    Update: Download the hotfix from Microsoft here.

    Here are the steps prior to the hotfix: 

    1.) In Windows Explorer, navigate to C:\Program Files\Microsoft Office\Office12

    2.) Delete owssup.dll.  Make a backup if you are uncomfortable with this step

    3.) Run the Office diagnostics by selecting Start > All Programs > Microsoft Office > Microsoft Office Tools > Microsoft Office Diagnostics.  This tool will find the mising component and install the "correct" version.

    For those users that are unable to complete these steps, the workaround is to right click Office documents and select Save As until it can be resolved.

    Credit goes out to this posting.

  • How to enable anonymous access for a site collection and disable anonymous access for sub-sites

    Suppose you want to have a site collection such as http://sharepoint.yourcompany.com that contains information about the SharePoint services you offer.  You want anyone to be able to browse the site without having to login. You can simply enable anonymous access by accessing Advanced Permissions in the site collection settings.  In the menu, select Anonymous Access.

    If you don't have a menu item for anonymous access, then you will need to configure the web application to allow anonymous access.  In Central Administration, access Applications Management > Authentication Providers.  Click on the zone you would like to enable anonymous access for and check the Enable anonymous access box.  

    When you set this option, anonymous access will also be enabled in IIS on your front-end servers.  The menu item for managing this feature also lights up.

    Once anonymous access is enabled for the web application, you can then enable it for the site collection.  When you select the Anonymous Access menu item, you are presented with the following options:

    Now suppose you want to have sites under the site collection for your potential customers to have play areas.  In this case, the permissions for each sub-site will be unique and cannot be accessed anonymously.  To accomplish this, you must disable anonymous access and customize permissions for each sub-site.

    Logically, you would expect to follow the same steps outlined above, accessing the settings from the sub-site and changing the anonymous access to Nothing.  But it appears that the feature that enables the menu item for anonymous access at the site collection level, does not light up the menu item at the site level.  You will see a menu such as this:

    The workaround for setting anonymous access at the site level is to access the anonymous access admin page (setanon.aspx) directly.  This admin page looks at the url to determine which site to set anonymous access for.  For example, to configure anonymous access for the sub-site, you would access http://sharepoint.mycompany.com/subsite-a/_layouts/setanon.aspx.  If you wanted to set anonymous access for the site collection, you would access http://sharepoint.mycompany.com/_layouts/setanon.aspx.  Many other admin pages are url context sensitive like this.

    After accessing the setanon.aspx page, change anonymous access to Nothing.

    Important: when you enable anonymous access, users will be able to access your view form pages without logging in.  For example: http://sharepoint.mycompany.com/pages/forms/allitems.aspx.  Depending on your requirements, this may not be desirable.  Fortunately, there is a view form lockdown feature that prevents anonymous users from accessing these forms.  Check out this excellent post on the Microsoft Enterprise Content Management blog.

  • Which server the SharePoint Central Administration web site should be installed on and how to move it there.

    The Central Administration (CA) web site is hosted on the first server you configure in your farm.  Because it seems like a logical place to start, the first front-end web server is often the first to be configured.  As a result, CA is often installed on a front-end web server.  Microsoft recommends that you do not host the CA web site on a front-end web server!  If you have a single-server implementation, you don't have much of a choice, but if your farm has a dedicated index server, the recommendation is to host the CA there.  I believe the intent is to host CA on a box that isn't accessible outside your firewall.

    Another point about the CA web site: the site is only hosted on one server in your farm.  In other words, if you go into IIS on each SharePoint server, you will only find the CA web site on one server.  Here are some questions you might be wondering about the CA web site.

    Q: Because the CA web site is hosted on one server and I need the site to configure my farm, is it a single point of failure?

    A: It is not a single failure because the configuration information and the CA web site content are stored in the database, not on the file system.  If the server that hosts the CA web site goes down, you can configure another server to host the CA. web site  The only caveat to this is that any customizations you made to the CA web site that are not stored in the database (IP binding, SSL certificate, web.config changes) will be lost.  Your backup plan should include copying files from the CA web site and backing up the IIS metabase.  But even if you lost those customizations, you would still be able to get the CA web site up and running enabling you to configure the farm.

    Q: Can you load-balance the CA web site?

    A: No.  The CA web site can only be hosted on one server at a time.  The only reason to load balance the CA web site would be for redundancy, and the configuration information is stored in the database, not on the server.  So if you are going to make anything redundant - make it your database server.

    Q: How do I move the CA web site to another server?

    A: Use the SharePoint Products and Technology Configuration Wizard to remove the CA web site from one server and install on another server.  Here are the steps:

    1. Backup the current CA web site.  Backup the directory (C:\Inetpub\wwwroot\wss\VirtualDirectories\[CA port number]\).  If you backup nothing else, get the web.config.  Backup the IIS web site (you can use IIS to save the web site as a file).
    2. On the server that currently hosts the CA web site, run the SharePoint Products and Technology Configuration Wizard.  Click next until you see an Advanced Settings button.  Select Do not use this machine to host the web site.
    3. On the server that you want to host the CA web site, run the SharePoint Products and Technology Configuration Wizard.  Click next until you see an Advanced Settings button.  Select Use this machine to host the web site.
    4. Restore any files / IIS customizations if necessary.

     

    I hope you find this information useful.

     

  • How to hide the New! icon or control how long the icon is displayed.

    A couple of questions come up from time to time about the New! icon: 

    "How long does the New! icon display after I add an item?" and "How do I prevent the New! icon from displaying?"

     

    The New! icon displays for 2 days by default, but you can use the command line to set that:

    [SharePoint Bin Directory]\stsadm.exe -o setproperty -propertyname days-to-show-new-icon -propertyvalue [Days to Display] -url [Your Virtual Server's URL]

     

    To hide the New! icon, set [Days to Display] to 0.  An alternate and unsupported approach is to replace the New! icon with a clear gif file.
     
  • An absolute way to store a relative url in a hyperlink/full html column

    You cannot store a relative url in a list column (at least not in any columns that directly support hyperlinks).  From a web development perspective this is bad--you should never use absolute urls for hyperlinks or any other resources.  If you move or rename the web site, the relative urls are unaffected, but all the absolute urls will have to be found and updated.

    The million dollar question is: why does SharePoint only allow absolute urls?  I don't know the answer and hope this limitation will be removed in a future release. 

    Let me give you a scenario I recently encountered and the solution (hack?) I came up with.  Here's the use case:

    1.) A SharePoint site is created to support a new project

    2.) The site document library contains documents that must be filled out

    3.) The site task list has a task for each document that must be filled out with a hyperlink to the document (like the administrator task list in Central Administration)

    4) When the user clicks on the hyperlink, the document should open in the client program (not the web browser)

    This should be as simple as creating a site that has the required documents and tasks and saving the site (and the content) as a template, but it isn't.  I thought I found a solution by using a full HTML column (which is only available as a site column) and an anchor tag containing a relative link.  To my astonishment, the HTML I entered was parsed and one of the modifications made was to make the url absolute.  It also took my nicely formed XHTML and made a mess of it.  I hunted for a setting "turn off HTML parsing" to no avail.  And how long has HTML been at use in Redmond?   Just kidding guys..but seriously...you need to fix this.

    Here's a quick demonstration of the issue.  Create a Task list and add a Hyperlink column.  Add a new task and enter the relative url of a document on the site.

    Save the item and then edit it again to see what happened to your url.  It's now absolute.

    Here's a demonstration of the same with a full HTML column.  Add a column from existing site columsn and select Full HTML.  Add a new task.  Click edit content and then click the icon to edit the HTML source:

    Enter the html for a hyperlink.

    Go back and edit the item and look at what happened to the html.

    Not only did it make the url absolute, it removed the quotes from the target attribute.

    So let's get to the solution.  The basic jist is a url that can translate a request as if it were a relative url.  I made a page named relativeurl.aspx that takes several parameters, the most important of which is the relative url.  This way, the hyperlink column can contain an absolute url just like SharePoint likes it, such as http://moss2007/relativeurl.aspx?url=[relative url].

    The magic happens in the code behind the page.  What it does it determines where you were before you requested relative.aspx (HTTP_REFERER).  Then it combines that with the url parameter and redirects the browser, thus acheiving relative url behavior.  Here's the full code listing:

    using System;  using System.Data;  using System.Configuration;  using System.Collections;  using System.Text;  using System.Web;  using System.Web.Security;  using System.Web.UI;  using System.Web.UI.WebControls;  using System.Web.UI.WebControls.WebParts;  using System.Web.UI.HtmlControls;    public partial class RelativeUrl : System.Web.UI.Page  {      protected void Page_Load(object sender, EventArgs e)      {          try          {                // Get the referring url              string sRefer = Request.ServerVariables["HTTP_REFERER"];              if (string.IsNullOrEmpty(sRefer))              {                  throw new InvalidOperationException("The HTTP_REFERER is null or empty.");              }              Uri uRefer = new Uri(sRefer);                // Get the level of the referring url to use as the base url              // http://moss2007/ProjectDirectory/SampleProjectTeamSite/Lists/Tasks/AllItems.aspx              // Level 2 would be: http://moss2007/ProjectDirectory/SampleProjectTeamSite              // Level 0 = /              // Level 1 = ProjectDirectory              // Level 2 = SampleProjectTeamSite              // Level 3 = Lists              int iLevel = 2;              string sLevel = Request.QueryString["level"];              if (!string.IsNullOrEmpty(sLevel))              {                  iLevel = int.Parse(sLevel);              }                // Set the base url              string sBase = sRefer;              string sSeg = uRefer.Segments[iLevel];              int iIdx = sBase.LastIndexOf(sSeg);              if (iIdx != -1)              {                  sBase = sBase.Substring(0, iIdx + sSeg.Length);              }              Uri uBase = new Uri(sBase);              //Page.Response.Write(sBase + "<br>");                                        // Get the requested relative url              string sRel = Request.QueryString["url"];              if (string.IsNullOrEmpty(sRel))              {                  throw new ArgumentException("The url parameter was not supplied.");              }              sRel = Server.UrlDecode(sRel);                // Get the absolute uri              Uri uAbs = new Uri(uBase, sRel);                // Get the open document parameter              string sOpenDoc = Request.QueryString["opendocument"];              bool bOpenDoc = false;              if (!string.IsNullOrEmpty(sOpenDoc))              {                  bOpenDoc = bool.Parse(sOpenDoc);              }              StringBuilder sbScript = new StringBuilder();              if (bOpenDoc)              {                  sbScript.AppendLine("<script type='text/javascript' language='javascript'>");                  sbScript.AppendLine("var url='" + uAbs.ToString() + "';");                  sbScript.AppendLine("var od3 = new ActiveXObject('SharePoint.OpenDocuments.3');");                  sbScript.AppendLine("if(od3 != null) {");                  sbScript.AppendLine("od3.ViewDocument3(window,url,3,'');");                  sbScript.AppendLine("document.write('<center><h2>The document has been loaded.<br>You can close this window.</h2></center>');");                  sbScript.AppendLine("}");                  sbScript.AppendLine("else {");                  sbScript.AppendLine("window.location=url;");                  sbScript.AppendLine("}");                  sbScript.AppendLine("</script>");              }                // Get the debug mode              bool bDebug = false;              string sDebug = Request.QueryString["debug"];              if (!string.IsNullOrEmpty(sDebug))              {                  bDebug = bool.Parse(sDebug);              }                  try              {                  if (bDebug)                  {                      Page.Response.Write(string.Format("Referring Url: {0}<br>", sRefer));                      Page.Res