Configuring for ULS Verbose Logging as Part of Basic SharePoint Setup


While troubleshooting a Records Center Routing Rule Event Receiver issue recently, I ran into a problem whereby ULS was apparently erroring when trying to log errors to the Application event log on the server.  (So this error ended up masking the “real”\underlying error).  The error ULS was throwing was “The Records Center refused the submission. There was an error routing the file: The source was not found, but some or all event logs could not be searched.  Inaccessible logs: Security.”  Turns out, this issue arises when you configure SharePoint to run in a “least privileged” type scenario whereby the service account and/or IIS app pool account isn’t a member of the local Administrators group.

The resolution, the steps of which I’ve added to my standard SharePoint Installation Guide, is below:

The following steps should be performed on each SharePoint front end web server:
1.  Open RegEdit and navigate to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\EventLog key.
2.  Grant each SharePoint Application Pool Identity Full Control of the EventLog node and the Security child node.
3.  Exit RegEdit and perform an IISRESET /NOFORCE .

author: Jeremy Ragan | posted @ Wednesday, October 07, 2009 11:42 AM | Feedback (1)

Copying SharePoint Content Databases Between Environments Without STSAdmin


 

There are a zillion and one posts and how-tos out there amongst the Interwebs detailing the supposed “correct” way to use STSADMIN to copy or promote SharePoint Content Databases between different environments (i.e. Dev<->Stage<->Prod).  The problem with the typical STSADMIN approach is that it forces you to create a new Web Application within the target environment.  Well what if you’re dealing with a pre-existing target environment with complex IIS Bindings and or Alternate Access Mappings, etc (i.e. things that make you not want to hassle with having to re-set them all up.)  So in these circumstances, the easiest thing to do is just to grab the content database from the source environment and copy/bind it to the target environment.  The instructions to follow outline the steps needed in order to successfully perform such a promotion:

Pre-Move

SharePoint won’t automatically update all the links in your site to match the target environment’s host address, especially if you’ve added your own links to places within the site structure.  So, to make sure links work once the site has been moved, you need to go through any links you’ve created and make sure they are relative URLs and not fully-qualified.  It’s also a good idea to navigate to the internal site list for the Site Directory entries (http://<your host>/SiteDirectory/SitesList/AllItems.aspx) and modify them to be relative.

Performing the Copy/Promotion

First - Copying Content Databases Between Environments

Same Database Name:
If the target database has the same name as the source database

  1. Backup the source database (full backup).
  2. Copy the backup to the target database server.
  3. Stop the target IIS Website on all target front-end-web servers.
  4. Perform a full restore to the target database using the backup.
  5. Start the target IIS Website on all target front-end-web servers.
Different Database Names:

If the target database has a different name than the source database

  1. Stop IIS on each of the target front-end-web servers.
  2. On the target database server:
    a.  Note the name of the applicable content database.
    b.  Document the security applied to the applicable content database
         (Specifically, what logins have what access to the database).
    c.  Document any backup/maintenance plans for the applicable content database.
    d.  Detach the applicable content database.
    e.  Backup the corresponding database data (.MDF) and log (.LDF) files to another location.
  3. Stop IIS on Website on all source front-end-web servers.
  4. Stop the source content database on the source database server.
    a.  Copy the corresponding .MDF and .LDF files to the target database server
          (usually C:\SQL\MSSQL10.MSSQLSERVER\MSSQL\DATA\) .
         i.   Rename the copied database files to match the names of the files in 2.e.
    b.  Re-Start the applicable source database on the source server.
  5. Re-Start IIS on the source server.
  6. On the target database server:
    a.  Open the Attach Database dialog:
         i.   Select the renamed MDF file from 4.a.i.
         ii.  In the upper “Databases to attach:” area, change the “Attach As” value to match 2.a.
         iii. In the lower “’<database name>’ database details” area, change the “Current File Path” values for both rows to match the paths and filenames in 4.a.i
    b.  Modify the security of the newly attached database to match 2.b.
    c.  Re-Apply any backup/maintenance plans from 2.c.
  7. Do not yet start IIS on each of the target web front end servers as we’ll do that later.

Second – Reconfiguring the Content for its New Home

At this point, were you to try to load the restored site, you’d most likely receive a “400 BAD REQUEST” error when browsing “/” and a “File Not Found.” error when browsing the default “/Pages/Default.aspx” path.  This is because SharePoint has gotten a little confused about what has just been done to it.  To resolve the confusion, we need to essentially force SharePoint to reconfigure the newly promoted content database.  The easiest non-STSADMIN way to do this is to use Central Administration web UI to remove the content database and re-add it:

  1. Navigate to the Central Administration Website for the target SharePoint instance.
  2. Within the Application Management tab, find the SharePoint Web Application Management section and open the Content Databases link.
  3. Change the Web Application to match the applicable application we’ve just updated.
  4. Click the Content Database name item to open its property page:
    a.  Document the following settings for use later:
         --   Database server
         --   SQL Server database name
         --   Number of sites before a warning event is generated
         --   Maximum number of sites that can be created in this database
         --   Windows SharePoint Services search server
    b.  Place a check mark in the Remove content database box
         (and click OK in the resulting prompt)
    c.  Click the OK button to perform the removal of the content database.
         (The database itself will not be deleted, it is just no longer associated with this site)
  5. You should now be back at the Content Databases Application Management screen.
  6. Verify the Web Application is still the applicable application .
    (change it if necessary)
  7. Click the Add a content database menu item to open the Ad Content Database page:
    a.  Verify the selected Web Application is correct.
    b.  Using the information documented in 4.a. fill in the properties for each to match exactly.
    c.  Click the OK button to add the existing content database.
         (SharePoint will now update the promoted Content Database to work with its new home)
  8. Start IIS on each of the target web front end servers.
  9. Test the target site.
    (Sometimes the first load will result in an error. It should work if you refresh the page)

author: Jeremy Ragan | posted @ Friday, July 31, 2009 10:29 AM | Feedback (1)

Removing line numbers from code pasted into Visual Studio


This is mostly for my own reference, but I'm sure others on the Interwebs might find this useful as well.

If, per chance, you find yourself pasting a large amount of code into Visual Studio that, for whatever reason, has line numbers included in the copied data, one can easily remove the line number's using Visual Studio's Find & Replace with the "Use Regular Expressions" option...

The trick, obviously is the RegEx, which is: ^ *:d+\:

Make sure the Match case and Match whole word options are disabled and that the Replace with text box is empty, and you're good to go!

author: Jeremy Ragan | posted @ Friday, May 01, 2009 11:29 AM | Feedback (1)

Programmatically Adding Users and Groups to SharePoint List Item Security


Recently, a colleague of mine was working on a SharePoint event handler to automatically apply security to an item on insert into a list.  In performing my own research on the topic, I came to realize there wasn’t a lot of concise or really usable information out there on the Interwebs about programmatically modifying the security and roles for a list item.  An additional constraint was that the list of users and groups to be added were contained in SharePoint “Users or Groups” column in another list. 

After a lot of research and some time spent stubbing out a test harness, I came upon the solution and created a helper method with overloads that allows you to programmatically add security to a list item with a number of different options for how the user or group is passed in, including support for passing in a list of SPRoleDefinition items to grant to the security item:

  • SPFieldUserValue - Useful if you're iterating through the values of a SharePoint User or Group column
  • String – Useful for passing in an ActiveDirectory group or user account name (If the user or group don’t already have access to the site itself, they will be automatically added)
  • SPPrincipal – Useful if you have an SPPrincipal, SPGroup, or SPUser object (since the latter two inherit from SPPrincipal).

#region addPermissionToListItem Overloads

/// <summary>
/// Applies role-based security to a user or group for a particular list item.
/// This overload is useful if you're iterating through the values of a SharePoint User or Group column.
/// </summary>
/// <param name="SharePointWeb">The SharePoint Web the List belongs to.</param>
/// <param name="ListItemToAddTo">The particular List Item to apply the security to.</param>
/// <param name="UserOrGroupToAdd">The SPFieldUserValue type from a SharePoint User or Group column.</param>
/// <param name="RolesToGrant">SPRoleDefinition items to apply to the user or group being added.</param>
private void addPermissionToListItem(SPWeb SharePointWeb, SPListItem ListItemToAddTo, SPFieldUserValue UserOrGroupToAdd, params SPRoleDefinition[] RolesToGrant)
{
    if (SharePointWeb != null && ListItemToAddTo != null && UserOrGroupToAdd != null && RolesToGrant != null && RolesToGrant.Length > 0)
    {
        // Get SPPrincipal from the UserOrGroupToAdd parameter
        SPPrincipal newItemToAdd;
        if (UserOrGroupToAdd.User != null)
        {
            // We have a user
            newItemToAdd = UserOrGroupToAdd.User as SPPrincipal;
        }
        else
        {
            // We have a group
            newItemToAdd = SharePointWeb.SiteGroups.GetByID(UserOrGroupToAdd.LookupId) as SPPrincipal;
        }

        // Call the overload that accepts an SPPrincipal object to add to the list
        addPermissionToListItem(ListItemToAddTo, newItemToAdd, RolesToGrant);
    }
}

/// <summary>
/// Applies role-based security to a user or group for a particular list item.
/// This overload is useful if you only have the windows login id of a user or a group name.
/// If the user or group doesn't belong to the site, they will be automatically added.
/// </summary>
/// <param name="SharePointWeb">The SharePoint Web the List belongs to.</param>
/// <param name="ListItemToAddTo">The particular List Item to apply the security to.</param>
/// <param name="WindowsUserIdOrGroupToAdd">The SPFieldUserValue type from a SharePoint User or Group column.</param>
/// <param name="RolesToGrant">SPRoleDefinition items to apply to the user or group being added.</param>
private void addPermissionToListItem(SPWeb SharePointWeb, SPListItem ListItemToAddTo, string WindowsUserIdOrGroupToAdd, params SPRoleDefinition[] RolesToGrant)
{
    if (SharePointWeb != null && ListItemToAddTo != null && WindowsUserIdOrGroupToAdd != null && RolesToGrant != null && RolesToGrant.Length > 0)
    {
        // Get SPPrincipal from the UserOrGroupToAdd parameter
        SPPrincipal newItemToAdd;
        if (Microsoft.SharePoint.Utilities.SPUtility.IsLoginValid(SharePointWeb.Site, WindowsUserIdOrGroupToAdd))
        {
            // We have a user
            newItemToAdd = SharePointWeb.EnsureUser(WindowsUserIdOrGroupToAdd) as SPPrincipal;
        }
        else
        {
            // We have a group

            SPGroup groupToAdd = SharePointWeb.SiteGroups[WindowsUserIdOrGroupToAdd];
            if (groupToAdd != null)
            {
                // The group exists, so get it
                newItemToAdd = groupToAdd as SPPrincipal;
            }
            else
            {
                // The group didn't exist so we need to create it:
                //	Create it:
                SharePointWeb.SiteGroups.Add(WindowsUserIdOrGroupToAdd, SharePointWeb.Site.Owner, SharePointWeb.Site.Owner, string.Empty);
                //	Get it:
                newItemToAdd = SharePointWeb.SiteGroups[WindowsUserIdOrGroupToAdd] as SPPrincipal;
            }
        }

        // Call the overload that accepts an SPPrincipal object to add to the list
        addPermissionToListItem(ListItemToAddTo, newItemToAdd, RolesToGrant);
    }
}

/// <summary>
/// Applies role-based security to a user or group for a particular list item.
/// </summary>
/// <param name="ListItemToAddTo">The particular List Item to apply the security to.</param>
/// <param name="UserOrGroupToAdd">The SPFieldUserValue type from a SharePoint User or Group column.</param>
/// <param name="RolesToGrant">SPRoleDefinition items to apply to the user or group being added.</param>
/// <remarks>This overload is called by the other overloads to actually set the the security.</remarks>
private void addPermissionToListItem(SPListItem ListItemToAddTo, SPPrincipal UserOrGroupToAdd, params SPRoleDefinition[] RolesToGrant)
{
    if (ListItemToAddTo != null && UserOrGroupToAdd != null && RolesToGrant != null && RolesToGrant.Length > 0)
    {
        // Create a new role assignment for the principal
        SPRoleAssignment newRoleAssignmentToAdd = new SPRoleAssignment(UserOrGroupToAdd);

        // Bind the role definitionss to the  new role assignment
        foreach (SPRoleDefinition roleDefinition in RolesToGrant)
        {
            if (roleDefinition != null)
            {
                newRoleAssignmentToAdd.RoleDefinitionBindings.Add(roleDefinition);
            }
        }

        // Add the new role assignment to the list item
        ListItemToAddTo.RoleAssignments.Add(newRoleAssignmentToAdd);
    }
}

#endregion

I wrapped the overloads (titled addPermissionToListItem) in a winform test harness that linked below.  The test harness will pull groups and users from a SharePoint “Users and Groups” column in a separate list, and use those items to replace any existing permissions on a specified list item (breaking inheritance along the way).

Test Harness Screenshot:
Test Harness and referenced code:

 

Applicable Code KeyWords: RoleDefinitionBindings, SPFieldUserValue, SPFieldUserValueCollection, SPGroup, SPPrincipal, SPRoleDefinition, SPUser

author: Jeremy Ragan | posted @ Thursday, January 15, 2009 8:21 PM | Feedback (4)

Resetting a SmartDesk Installation To A Configured But Pre-Work-Received State


 
For post-configuration testing scenarios, it’s often needed to “reset” the SmartDesk databases to a pre-work-received state.  (I.e. users, tabs, views, modules, content have all been configured, but no work has yet come into the system).
 
For this reason I have created a SmartDesk 4.x Database Cleanup Script that will return a SmartDesk installation to the state described above.
 
The script is available by contacting your BlueThread representative or by opening a request ticket on the BlueThread Support Portal.

author: Jeremy Ragan | posted @ Thursday, January 08, 2009 2:53 PM | Feedback (0)

Featured in December 2008 K2 Networker


Available for download here: http://www.k2.com/en/displaycontent.aspx?id=1027

My section, excerpted:

author: Jeremy Ragan | posted @ Friday, December 19, 2008 5:23 PM | Feedback (0)

The need to have an existing process instance run against a process map published after the instance started


 . 
In the course of engagements, a customer often brings up the "need" to be able to make an already-in-process item switch maps and run against a new map that was published after the item in question already started. I place quotes around "need" because this isn't really a need. It's more of an ill-conceived want based on fear and mis-understanding. 
 
What needs to be understood is why the customer feels they “need” this capability. In general, 99% of customer concerns that could lead to them feeling they have this “need” can be wrapped up into the items below:
 
·   Some data element may change in such a way that it affects processing within a process.
o    For example, an approval process involving a system-made decision around some dollar value such that if that value is greater (>) than $5 then the process follows one branch but if it’s less than or equal (<=) to $5, then the process goes another way.
§ What if the business wants to make the decision based on $10 as opposed to the original $5?
o    In this scenario, a properly designed process where the decision points are configurable on-the-fly satisfies the need without having to modify the process and push out a new version. 
§ Configurable data elements are out-of-the-box functionality of K2 blackpearl, called StringTable entries.
 
·   The process changes in a dramatic way.
o    Generally speaking this just doesn’t happen. A properly designed process designed and developed through proper Software Development Lifecycle phases with rounds of detailed user acceptance testing will result in a valid and acceptable process by the business. In the extremely rare off-hand chance that a business’s process changes in such a dramatic way as to be incompatible with the previous process, then the validity of the original process as approved by the business should still be considered valid and applicable for items currently in the system and running against that process. If this isn’t the case, then one would have to seriously reconsider the validity of the completed items that had already finished the previous process…If a process changes so much that items currently running against the previous process are invalid, then the items already completed have to also be considered suspect.
o    However, if it is absolutely imperative that currently running process instances against the previous process must go against the new process, then there are two ways to facilitate this:
§ Design the process in such a way that there is a step in the process that would route asynchronously into a new process instance of the same process type (the newly started process as a result will run against the most recently-published process map). 
ØThere is out-of-the-box functionality in blackpearl to allow an admin to select currently running instances and direct them to this special ‘re-start’ step in the process.
ØUsing the blackpearl API, a simple web page could also be developed to allow selected business users to also perform this action.
§ Delete the process instance and the associated collateral, and re-start a new process instance.
 

author: Jeremy Ragan | posted @ Friday, December 05, 2008 9:27 PM | Feedback (1)

Deploying New StringTable Entries for Existing Processes


Ran into a late night deployment issue whereby I was trying to deploy an updated process map to an existing environment that had new StringTable entries to go with it.  I was under the impression that just deploying the map would automatically create everything for me...but it didn't.  Within the K2 Workspace/Management area you can't manually add new entries but you can manually add new Environment Library items.  Turns out, in order to deploy new StringTable entries to an existing map, you need to perform the following:

  1. Manually add the entries to the Environment Library in K2 Workspace/Management 
  2. Publish the map.

After performing these steps, the actual StringTable entries will be created and assigned for you.

author: Jeremy Ragan | posted @ Wednesday, August 20, 2008 7:00 PM | Feedback (0)

DateTime Formats in the BlueThread Upload Web Service


So I was debugging some code I had written to perform a move/copy of a document within SharePoint and noticed none of the columns that were setup in SharePoint as DateTime were getting populated.  Long story short, it turned out that our Upload Web Service is expecting metadata of a DateTime type to be in the UTC format (yyyy'-'MM'-'ddTHH':'mm':'ss'Z') which is a cross between the "s" and "u" built-in .Net DateTime.ToString() formatters. So I used:

[DateTimeObject].ToString("s") + "Z"

 

author: Jeremy Ragan | posted @ Wednesday, August 20, 2008 7:00 PM | Feedback (0)