Brett's SharePoint Blog

  • New Advanced SharePoint Webcasts...

    Three of the SharePoint Academy and BPIO University courses I taught earlier this year are now available in webcast format. Many thanks to the sponsors on our product team, the Microsoft Information Worker community (global and local), content contributors, and MS Studios for making these possible. Now you can see what I look like when I'm reading from a teleprompter. :)

    These webcasts contain a mix of standard material from the respective courses, as well as a number of my own contributions. Many of my contributions focus on material from areas of focus in my blog - customization, master pages, features, solutions, etc. - so thanks to all of you who've helped me enhance the content by sharing feedback, questions, and corrections to the original blogs.

    I'm sure they'll end up on a landing page somewhere soon, but here are the public links:

    Microsoft SharePoint Technologies Solution Architecture

    http://www.microsoft.com/winme/0805/32847/mod4/index.html

     

    Customizing and Extending Microsoft SharePoint Products and Technologies

    http://www.microsoft.com/winme/0805/32847/mod9/index.html

     

    Configuring the Business Data Catalog

    http://www.microsoft.com/winme/0805/32847/mod10/index.html

    Enjoy!

  • Branding Design using Solutions

    In a previous post, I extolled the virtues of deploying our branding code in a solution. While I still make that recommendation without any reservations, it's important to note that you may need TWO solutions to deploy your branding customizations.

    History 

    First, let's do a quick review of some of the generic components I used in branding our MOSS environment (using the HTTP module approach for LAYOUTS customizations):

    • Customizations to the GAC 
      • Feature Receiver DLL for the branding features
      • Some custom web part assemblies
      • Redirect Module DLL
    • Customizations to the "12" directory:
      • Custom application.master (applied via a redirect)
      • Custom simple.master (applied via a redirect)
      • Custom images
      • Custom Theme
      • A branding feature for Meeting Workspaces
      • A branding feature for all the other site templates
      • Branding feature stapler 
    • Customizations to web.config:
      • SafeControls entries for the custom web parts
      • Redirect module configuration info

    Everything except the redirect module configuration info (which we configured manually) is easily accomodated by the Solution framework, so that's exactly what I put in the original SINGLE branding solution file. So deploy all that stuff to your Web Front End servers, and you're good to go, right? Not entirely...

    The Problem...

    Two odd symptoms occurred in our regression testing. When we created sites/site collections from self-service site creation, STSADM on the WFE servers, or from the Create Page in a site, branding was applied. This is good. :) However, when we created site collections via STSADM from an app server or from Central Administration > Application Management > Create Site Collection, the branding was missing. This is bad. :)

    Why? The Central Admin server - and all non-WFE servers - were missing the branding code. The feature stapler and everything referenced by it - the branding features, the feature receiver (since the features referenced it), and the custom theme (since the feature receiver attempted to set a new site's theme to the custom theme) - need to be on these servers in order for the site creation to complete with all branding applied.

    The Solution...

    In case you didn't know already (I didn't, which was the cause of this issue), there are two types of Solution deployments - Web Front End (which only pushes the changes to servers hosting the web application role) and Application Server (which pushes changes to ALL SharePoint servers in the farm). I don't remember the complete solution manifest spec, but one other restriction that's relevant to this scenario is that only the Web Front End deployment type supports creation of SafeControl entries.

    Since I had a set of code that needed to be on ALL the servers as well as a couple of SafeControl entries, the simplest thing to do was to divide the customizations into two separate solutions - "core" branding and "web server" branding. The core solution holds all the components that are required on all servers, while the web server contains only the components that are needed to render the branded sites (like images, web parts, and LAYOUTS customizations).

    Here's the breakdown:

    "Core" Branding Solution

  • Customizations to the GAC
    • Feature Receiver DLL for the branding features
    • Redirect Module DLL
  • Customizations to the "12" directory:
    • Custom Theme
    • A branding feature for Meeting Workspaces
    • A branding feature for all the other site templates
    • Branding feature stapler 

    "Web Server" Branding Solution

  • Customizations to the GAC 
    • Some custom web part assemblies
  • Customizations to the "12" directory:
    • Custom application.master (applied via a redirect)
    • Custom simple.master (applied via a redirect)
    • Custom images
  • Customizations to web.config:
    • SafeControls entries for the custom web parts

    For ease of maintenance, I still used WSPBuilder for creating the manifests and .WSP files - this just required a minor tweak to the build script, which now looks something like this:

    REM build core server customizations
    wspbuilder.exe -solutionid 12345678-1234-1234-1234-1234567890ab -deploymentservertype ApplicationServer -wspname core-branding.wsp -excludefiles "a_list_of_all_the_wfe_files.txt" 

    REM build web server customizations
    wspbuilder.exe -solutionid 12345678-1234-1234-1234-1234567890ac -deploymentservertype WebFrontEnd -wspname webserver-branding.wsp -excludefiles "a_list_of_all_the_core_files.txt"

    Enjoy...

  • Adding An "All Authenticated Users" Feature to Forms-Based Authentication

    Preamble... 

    One of the frequenly cited shortcomings of Forms-Based Authentication when compared to Windows Integrated Authentication is its lack of built-in support for an "All Authenticated Users" group similar to NT AUTHORITY\Authenticated Users.

    This type of role comes in handy in a number of situations, most notably for provisioning public (but not anonymous) sites and public SSP permissions like "Create Personal Site". The most common workaround I've encountered has been a custom solution that ties in with user provisioning - create a role in the directory service, add all existing users to it, add any new users to it, and remove any users from it as they're deleted. For very large-scale directories, it's not practical to store many thousands of users in a single group, so the actual implementation might be one involving nested groups or a denormalized structure where group memberships are enumerated in the user object, but the same idea applies.

    Problem...

    My present customer's extranet MOSS implementation uses a very large LDAP directory with hundreds of thousands of potential users. To deal with their problems of scale, they long ago adopted the "list groups in the user object" approach. This made us unable to use the out-of-the-box MOSS Role Manager, which expects to find users enumerated in group objects, rather than groups enumerated in user objects. This led to a need for a custom LDAP Role Manager.

    Like most MOSS implementations, they have a need for an "all authenticated users" role, but modifying user provisioning processes in their LDAP (of which there are many, but that's another story) wasn't a realistic option in terms of cost or schedule. Fortunately, the custom Role Manager opened the door to an elegant solution to the "All Authenticated Users" requirement.

    Solution...

    Once you've written your own role manager, it's pretty straightforward to embed your own "All Authenticated Users" feature into the component. I found it easiest to make this a configurable group name, but you could just as easily hard-code the value. Here's the walkthrough:

    1. Create a private class variable (_LDAPAuthUsersGroup in the example below) for storing the group name.

    2. Populate the group name variable in the Initialize method via a configuration setting.

    3. In the GetRolesForUser method, append the "All Auth Users" group name to the roles list every time, thereby making every user a member of the group.

    4. In the RoleExists method, always return true for the "All Auth Users" group name, thereby confirming its existence.

    That's all there is to it. Snippets from relevant sections of the RoleProvider appear below.

    namespace Company.MOSS.Auth
    {
        public class CustomRoleProvider : RoleProvider
        {
            /*
            TO DO: put your other class variables here
             */

            private bool _LDAPEnableAuthUsersGroup;
            private string _LDAPAuthUsersGroup;

            public CustomRoleProvider()
            {
                /*
                TO DO: set other defaults here
                 */

                this._LDAPEnableAuthUsersGroup = false;
                this._LDAPAuthUsersGroup = string.Empty;
            }

            public override void Initialize(string name, NameValueCollection config)
            {
                this._Name = name;
                if (config != null)
                {
                    try
                    {
                        /*
                        TO DO: initialize your other configuration variables here
                         */

                        //check for the presence of the auth users group configuration setting
                        if (config["authUsersGroup"] != null)
                        {
                            //Extra validation to prevent blank auth users group
                            if (config["authUsersGroup"].Length > 0)
                            {
                                //making this case-insensitive - this might not apply to your directory service
                                this._LDAPAuthUsersGroup = config["authUsersGroup"].ToUpper();
                                //turning on a boolean flag indicating that we're using this feature
                                this._LDAPEnableAuthUsersGroup = true;
                            }
                        }

                        return;
                    }
                    catch (Exception ex)
                    {
                        throw new LdapProviderException(SPResource.GetString("LDAPProviderGeneralFailure",
                            new object[0]));
                    }
                }
                throw new ArgumentNullException("config");
            }

            public override string[] GetRolesForUser(string username)
            {
                //this group list length works for me - might need to be different for you
                List<string> rolesList = new List<string>(10);

                /*
                TO DO: do whatever else you need to do to retrieve the "real" roles first
                 */

                //if you're using the feature, then add the all auth users group to the roles list
                if (_LDAPEnableAuthUsersGroup)
                {
                    rolesList.Add(_LDAPAuthUsersGroup);
                }

                return rolesList.ToArray();
            }

            public override bool RoleExists(string roleName)
            {
                //if authenticated users functionality is enabled, check to see if this is that special group
                if (_LDAPEnableAuthUsersGroup)
                {
                    //making this case-insensitive - this might not apply to your directory service
                    string role = roleName.ToUpper();

                    if (role.Equals(_LDAPAuthUsersGroup)) return true;
                }

                /*
                TO DO: do whatever else you need to do to check if "real" roles exist
                 */
            }

        }
    }

    Special Concerns:

    REALLY, REALLY IMPORTANT: Make sure that the "All Auth Users" group name doesn't intersect with current or possible user/group names. That would be a horrible security hole - if someone created  a user or group with the same name as the All Auth Users group (whatever you choose to call it) and provisioned permissions to it on their site, all of a sudden EVERYONE would have that permission level. Very, very, very bad, potentially with legal/compliance consequences. There are three tactics I recommend for preventing this outcome:

    • Simple, fast solution #1: Create a placeholder group (and user) in the directory service with the same name as the All Auth Users group that YOU own. Never do anything with them other than ensuring that they don't get deleted. You're just putting them there to prevent anybody else from creating a group with the same name, assuming that the directory service enforces uniqueness. :)
    • Simple, fast solution #2: Give the All Auth Users group a name that falls outside the allowable domain of values for group names in your directory service - i.e. include special characters like # or \ if those are disallowed in group names; create an group name 100 characters long if group names are restricted to 50 characters in the directory service; etc.
    • Slow, fallback solution: This could also be used in combination with method #2, but adds a bit of overhead. Instead of the mindless algorithms for checking if the role exists and the list of roles for the user, add a check to see if that role name exists in the directory service. If so, go the safe route and use the "real" non-All Auth users group  instead AND throw a warning to the event log.

    A secondary concern... what if you only want to make this feature available to selected users/sites? This is one of the reasons we went with a configurable setting for the group name - we didn't want your everyday user provisioning permissions to all authenticated users in their site. The design pattern for this is simple - go with an elaborate group name (like a GUID) and change it on a regular basis.

  • Be sure to use paging in membership providers for the MOSS Profile Import Tool...

    Just a quick lesson learned on the MOSS Profile Import Tool to save you import time and resources.

    The Profile Import Tool's current release has a hard-coded paging algorithm that attempts to page through any membership provider's GetAllUsers method by retrieving 1000 users at a time. If you DON'T support paging your GetAllUsers method - meaning you try to retrieve all users at once - then the import algorithm will retrieve and import all users N times, where N = (# users / 1000) . This becomes totally unmanageable - but a really good test for memory leaks in your provider - if you have many thousands of users... :)

    The best practice is (if able) to add paging to your membership provider, since that will be useful in all calls to the GetAllUsers, FindUsersByName, and FindUsersByEmail methods, all of which will be used by SharePoint.

    If you feel the need to alter this aspect of the Profile Import Tool's behavior - i.e. you have 100K users and don't want to take the time to build paging logic into your provider, or don't have access to the membership provider's source code - you'll find the paging logic in the ImportProvider.cs class. All you have to do is change the value of the pageSize constant to a sufficiently large value (i.e. 101000 if you have 100000 users), and you'll bypass the paging:

    private const int pageSize = 1000;

    If you choose to increase the pageSize value, make sure you have enough RAM to hold all those user records... and don't run the profile import application on a server that's hosting a RAM-intensive role like Query, Web App, or SQL.

  • Resolving Issues with Faulty Features and STSADM EXPORT

    If you're attempting an STSADM -o export (or, I'd assume, a content deployment) of a site and receive the following error:

    FatalError: Failed to compare two elements in the array.

    Check out the following KB article: http://support.microsoft.com/kb/948726/en-us .

    The article indicates that the likely cause is one or more invalid feature references in the SPSite and/or SPWeb object. Fortunately, Steven Van de Craen authored a wonderful GUI tool for removing "faulty features" (which is of course unsupported and should be used with caution, but nonetheless very useful). See his article on the topic for more details.

    Cases where you might encounter these errors:

    • Sites that were migrated from a MOSS Enterprise implementation (even if they're not USING Enterprise features) to a MOSS Standard implementation
    • Sites that were created in an beta version of MOSS/WSSv3
    • Sites which reference custom features that have since been removed from the filesystem but were not properly deactivated and uninstalled
  • List Attachments over 50MB need more than an increase in Maximum Upload Size...

    Chalk this down as the 1045th* time I've been reminded that you learn something new about SharePoint every day.

    *The number of days I've been working with SharePoint... :)

    Requirement:

    • Increase the maximum upload size for documents and attachments to 60MB for a web application

    Apparent solution:

    1. Connect to Central Admin
    2. Navigate to Central Admin > Application Management > Web Application General Settings
    3. Select your web application
    4. Set the Maximum Upload Size value to 60 MB and hit OK.

    Results:

    • Upload a 55MB document to a doclib - success.
    • Attach a 55MB file to a list item - receive the classic "An unknown error occurred" message, scratch head in puzzlement

    Turns out there's a setting at the IIS level that will block list attachment uploads above 50MB. Below 50MB, any max upload size you pick (for example, 10MB) works fine for both list attachments and document uploads and is enforced directly through web app settings. Above 50MB, you need to make at least one minor tweak to your web.config to allow larger uploads.

    Complete solution for max upload sizes over 50MB:

    1. Connect to Central Admin
    2. Navigate to Central Admin > Application Management > Web Application General Settings
    3. Select your web application
    4. Set the Maximum Upload Size value to "X" MB and hit OK.
    5. Repeat steps 6-7 for all zones for your web application on all servers hosting the web application role
    6. Open the web.config
    7. Replace the following line:
      <httpRuntime maxRequestLength="51200" />
      with
      <httpRuntime maxRequestLength="{X * 1024}" />

    if you're using SQL storage for your SharePoint content, the maximum useful value for the maxRequestLength should be 2097152 (2GB in kilobytes = 2 * 1024 * 1024). Keep in mind that other limits may affect large uploads, most notably:

    • Site Collection Quota
    • Timeouts (you may need to adjust these in BOTH Web Application General Settings and the executionTimeout attribute of the <httpRuntime/> element)
      • Timeout settings will be influenced by network throughput limitations like latency and available bandwidth
    • Custom Storage Solution
      • External Blob Storage solution (see WSS SDK v1.3 for this feature that's new to WSS SP1) - whatever max BLOB size applies
    • Disk Space
      • If using SQL Storage, this is the disk containing your data files for your webapp's content DBs
      • If using an External Blob Storage solution, then this is wherever your blob store resides

    For more on the httpRuntime web.config element, see http://msdn2.microsoft.com/en-us/library/e1f13641(VS.80).aspx .

  • MOSS User Profile Delete Tool

    For anyone using a custom authentication provider or non AD/LDAP* directory service, the Profile Import tool at http://www.codeplex.com/sptoolbox is an awesome tool for adding users. Just plug in your provider(s), tweak a few configuration settings, and go. I've used it to add 200,000+ user profiles from my customer's directory service (though I wouldn't recommend attempting to swallow all those records in one gulp - that requires more memory than we had on our import server) to our latest MOSS environment.

    Getting the configuration "just right" resulted in my creating lots of extraneous user profiles in the environment. Not wanting to recreate the SSP or build up some serious callouses hitting the "delete" key hundreds of times, I spent quite a bit of time yesterday searching for a tool for doing mass deletes of user profiles, to no avail. I did, however, find a number of postings for other folks out there looking for a similar tool.

    So I put together a quick-and-dirty command-line app for nuking your entire user profile DB or a portion thereof based on account name matches. You could modify this pretty easily to include more sophisticated filter criteria, like an existence check in your directory service of choice, but I didn't want to spend more than an hour on the script... :)

    * You'd also need to use the Profile Import tool if you need to do an authenticated bind to your LDAP service using something other than Windows credentials.

    Configuration File:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
     <appSettings>
      <add key="test" value="false"/>
      <add key="debug" value="true"/>
      <add key="filterPattern" value="qaldap:"/>
     </appSettings>
    </configuration>

    Configuration Comments:

    Here's how to use the config settings:

    • filterPattern
      • case-insensitive string used for a "starts with" match on user profile Account Names - i.e. "myprovider:" would be a match for all user profiles that use the provider "myprovider", or "mydomain\" would match all user profiles from the "mydomain" domain
    • test - used to control whether delete actions occur
      • true (default) - user profiles matching the filterPattern are reported as matches for deletion, but no delete actions occur
      • false - all user profiles matching the filterPattern are deleted
    • debug
      • true - application info/warning/error events are written to the console
      • false (default) - application events are written only to the event log

    Source Code:

    The code is pretty simple. Main is where the action is for retrieving/deleting user profiles and reading the config file; if you need to plug in a different algorithm for identifying profiles to be deleted (like an existence check in your directory service), you'd add it here. GetAnySiteUrl is copied directly from the Profile Import tool to save coding time in establishing a server context; if you have multiple SSPs, you might want to update the way you establish a context.

    If you're using only 1 SSP and are OK with the simple "starts with" pattern match algorithm, just put the right references in your project, build it, and you're ready to go. Enjoy!

    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Text;
    using System.Diagnostics;
    using System.Configuration;

    using Microsoft.Office.Server;
    using Microsoft.Office.Server.UserProfiles;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Administration;

    namespace ProfileDelete
    {
        class Program
        {
            static int logLevel = 1;
            static bool debug = false;
            static bool test = true;
            static string logSource = "ProfileDelete";
            static string logDestination = "Application";
            static string filterPattern = "";

            static void Main(string[] args)
            {
                int counter = 0;

                try
                {
                    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

                    NameValueCollection appSettings = ConfigurationManager.AppSettings;
                    logLevel = Convert.ToInt32(appSettings["loglevel"]);
                    debug = Convert.ToBoolean(appSettings["debug"]);
                    test = Convert.ToBoolean(appSettings["test"]);
                    filterPattern = appSettings["filterPattern"];
                }
                catch (Exception e)
                {
                    DoLog("Error reading configuration file:" + e.ToString(), EventLogEntryType.Error, 100);
                }

                string currentAccountName = String.Empty;

                using (SPSite site = new SPSite(GetAnySiteUrl()))
                {

                    ServerContext context = ServerContext.GetContext(site);

                    UserProfileManager profileManager = new UserProfileManager(context);

                    foreach (UserProfile profile in profileManager)
                    {
                        currentAccountName = (String) profile[PropertyConstants.AccountName].Value;
                        currentAccountName = currentAccountName.ToLower();

                        if (currentAccountName.StartsWith(filterPattern.ToLower()))
                        {
                            counter++;
                            DoLog(currentAccountName + " matches delete criteria. Count: " + counter, EventLogEntryType.Information, 110);
                            if (!test)
                            {
                                DoLog("Deleting " + currentAccountName, EventLogEntryType.Information, 120);
                                profileManager.RemoveUserProfile(profile.ID);
                            }
                        }
                    }
                }

            }

            private static string GetAnySiteUrl()
            {
                //the purpose of this function is just to get any site Url so that
                //we can obtain a ServerContext for the UserProfileManager
                string ret = string.Empty;

                SPWebServiceCollection wsc = null;

                try
                {
                    wsc = new SPWebServiceCollection(SPFarm.Local);
                    foreach (SPWebService sw in wsc)
                    {
                        foreach (SPWebApplication wa in sw.WebApplications)
                        {
                            if (wa.Sites.Count > 0)
                            {
                                ret = wa.Sites[0].Url;
                                break;
                            }
                            if (!string.IsNullOrEmpty(ret))
                                break;
                        }
                        if (!string.IsNullOrEmpty(ret))
                            break;
                    }
                }
                catch (Exception ex)
                {
                    throw new Exception("Error getting a site Url: " + ex.Message);
                }
                finally
                {
                    wsc = null;
                }

                return ret;
            }

            private static void DoLog(string msg, EventLogEntryType eventType, int code)
            {
                bool writeEntry = true;
                if (debug) Console.WriteLine("Event Type: {0}; ID: {1}; Message: {2}", eventType.ToString(), code, msg);

                if (logLevel > 0)
                {
                    if (!EventLog.SourceExists(logSource))
                        EventLog.CreateEventSource(logSource, logDestination);
                    if (eventType.Equals(EventLogEntryType.Warning) && (logLevel < 2))
                        writeEntry = false;
                    if (eventType.Equals(EventLogEntryType.Information) && (logLevel < 3))
                        writeEntry = false;
                    if (writeEntry) EventLog.WriteEntry(logSource, msg, eventType, code);
                }
            }


        }
    }

     

  • MOSS+ECM Supplemental Material, Week 3

    Various Topics: 

    • Virtualization:
      • Platform choice:
        • Virtual Server 2005 R2 x86 or x64 is supported
        • Recommended against running production environments on VPC
        • 3rd Party: See KB article 897615
      • Architecture considerations:
        • Recommended NOT to virtualize SQL
        • Restricted to 32-bit SharePoint, so this reduces addressable memory
        • No hard-and-fast rules as to how performance changes when you go from physical to virtual
    • SharePoint 2003-2007 migration webcast - http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?culture=en-US&EventID=1032330523&CountryCode=US
    • Infopath integration with the BDC - Other than using BDC data in list/library columns in InfoPath forms, you can write a web service to make use of the BDC’s Runtime Object Model:
      The Runtime object model is designed for use by the Business Data Catalog clients and applications. The Runtime object model has two major functions:
      • It offers an intuitive, object-oriented interface that abstracts the underlying data sources. The Runtime object model insulates the client from learning application-specific coding paradigms, and allows clients to access all business applications in a single, simplified way. Because of the Runtime object model, calling a method on an SAP application is very similar to calling a method on Siebel or executing a query in SQL.
      • It allows you to read the metadata objects from the metadata database and execute the business logic described there. The runtime object model is cached and fast, so clients that just need to query the metadata database for metadata information use the Runtime object model.

     

  • ECM Supplemental Material, Week 2

  • By Popular Demand... More HttpModule Sample Code

    Several folks have asked for this, so here it is... the two other pieces of the ResourceRedirect solution from my LAYOUTS branding post. They're also the least interesting, which is why I didn't post them before. :)  Enjoy... review this post for the full context.

    Redirect.cs

    using System;

    using System.Collections.Generic;

    using System.Text;

    namespace MOSS.Branding

    {

    public struct Redirect

    {

    public string pattern;

    public string masterPageUrl;

    public string originalMaster;

    public string destinationPageUrl;

    public Redirect(string _pattern, string _masterPageUrl, string _originalMaster, string _destinationPageUrl)

    {

    pattern = _pattern;

    masterPageUrl = _masterPageUrl;

    originalMaster = _originalMaster;

    destinationPageUrl = _destinationPageUrl;

    }

    }

    }

    RedirectSectionHandler.cs

    using System;

    using System.Collections;

    using System.Collections.Generic;

    using System.Text;

    using System.Configuration;

    using System.Xml;

     

    namespace MOSS.Branding

    {

    class RedirectSectionHandler : IConfigurationSectionHandler

    {

    #region IConfigurationSectionHandler Members

    public object Create(object parent, object configContext, System.Xml.XmlNode section)

    {

    ArrayList coll = new ArrayList();

    XmlElement root = (XmlElement)section;

     

    string pattern = "";

    string masterPageUrl = "";

    string originalMaster = "";

    string destinationPageUrl = "";

    foreach (XmlNode parentNode in root.ChildNodes)

    {

    XmlNode patternNode = parentNode.Attributes.GetNamedItem("pattern");

    if (patternNode != null) pattern = patternNode.InnerText.ToLower();

    XmlNode originalMasterNode = parentNode.Attributes.GetNamedItem("originalMaster");

    if (originalMasterNode != null) originalMaster = originalMasterNode.InnerText.ToLower();

    XmlNode masterPageUrlNode = parentNode.Attributes.GetNamedItem("masterPageUrl");

    if (masterPageUrlNode != null) masterPageUrl = masterPageUrlNode.InnerText.ToLower();

    XmlNode destinationPageUrlNode = parentNode.Attributes.GetNamedItem("destinationPageUrl");

    if (destinationPageUrlNode != null) destinationPageUrl = destinationPageUrlNode.InnerText.ToLower();

    if ((masterPageUrl.Length == 0) && (destinationPageUrl.Length == 0)) throw new ConfigurationErrorsException("Redirects (masterPageUrl and destinationPageUrl) cannot both be blank.");

    if ((pattern.Length == 0) && (originalMaster.Length == 0)) throw new ConfigurationErrorsException("Inputs (originalMaster and pattern) cannot both be blank.");

    Redirect redirect = new Redirect(pattern, masterPageUrl, originalMaster, destinationPageUrl);

    coll.Add(redirect);

    }

    return coll;

    }

    #endregion

    }

    }

  • MOSS+ECM Training Supplemental Material

    Recommended Readings/Resources on ITPro Topics:

    Recommended Reading/Resources on ECM/DM topics:

  • SharePoint AcademyLive Recommended Readings

    This won't mean too much for those of you who aren't attending the AcademyLive courses, but here are some recommended readings on two topics we were discussiong today - Security & Information Architecture.

    Recommended Readings on Security:

    ·         Server Communication table, general IT Pro topics - http://blogs.msdn.com/joelo

    ·         AAMs – anything by Troy Starr on http://blogs.msdn.com/sharepoint

    ·         Kerberos config for SharePoint – http://blogs.msdn.com/martinkearn

    ·         Steve Peschka’s FBA opus (part 1 of 3) - http://msdn2.microsoft.com/en-us/library/bb975136.aspx

    ·         SSL and Host Headers for IIS: http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/596b9108-b1a7-494d-885d-f8941b07554c.mspx?mfr=true

    ·         Adding SSL & Kerberos to Central Admin – http://blogs.msdn.com/bgeoffro

    Recommended Readings on Information Architecture:

    ·         Information Architecture - General

    o   Information Architecture for the World Wide Web by Rosenfeld & Morville

    o   Unlocking Knowledge Assets by Conway & Sligar

    o   http://www.boxesandarrows.com/

    ·         Information Architecture – SharePoint

    o   MOSS Administrator’s Companion, Ch. 8

    o   SharePoint Products & Technologies Resource Kit, Ch. 8

    o   Implementing SharePoint Governance - see link on http://technet.microsoft.com/en-us/sharepointserver/bb507202.aspx (find "the pyramid" here)

    o   SharePoint Products & Technologies Customization Policy – see link on http://technet.microsoft.com/en-us/sharepointserver/bb507202.aspx

    Recommended Resource for Features & Solutions:

    ·         WSPBuilder - not a panacea for deployment headaches, but close - http://www.codeplex.com/wspbuilder

    Recommended Readings/Resources for Backup/Recovery/DR:

    Recommended Readings/Rsources for Search:

    Recommended Readings on Customization:

  • Adding Kerberos & SSL to Central Administration

    This came up during the SharePoint AcademyLive Search class today, and isn't the first time I've been asked how to do this, so I figured it was time to write a post. :)

    When you're first configuring your farm, it's often easier to configure Kerberos & SSL later, once you've completed all the other farm deployment tasks and made sure everything else is working. Doing this for Central Admin requires a couple of minor tricks... the complete procedure listed is listed below.

    Assumptions:

    • CA was originally installed on the desired server
    • you don't have any web.config or other customizations applied to the CA webapp
    • all your Kerberos configuration (SPNs, delegation, etc.) is already configured
    • CA was originally installed with NTLM & HTTP.

    Central Admin Web Application (Kerberos+SSL)

    1.       Remove Existing CA site

    a.       Navigate to Start Menu > All Programs > Microsoft Office Server > SharePoint Products & Technologies Configuration Wizard

    b.      Click Next

    c.       Click Yes

    d.      Leave “Do NOT disconnect from this server farm” selected and click Next.

    e.      Select “Yes, I want to remove the web site from this machine” and click Next.

    f.        Click Next

    g.       Click Finish

    2.       Recreate CA site w/Kerberos auth

    a.       Navigate to Start Menu > All Programs > Microsoft Office Server > SharePoint Products & Technologies Configuration Wizard

    b.      Click Next

    c.       Click Yes

    d.      Leave “Do NOT disconnect from this server farm” selected and click Next.

    e.      If asked, indicate that this server should host the central administration web application and click Next.

    f.        Check the checkbox next to “Specify port number” and enter a port number of 12345.

    g.       Select the Negotiate (Kerberos) authentication provider.

    h.      Click Next

    i.         Click Yes

    j.        Click Next

    k.       Click Finish

    3.       Enable SSL for CA site

    a.       Navigate to Start Menu > All Programs > Administrative Tools > IIS Manager

    b.      Navigate to {Computer Name } > Web Sites

    c.       Right-click SharePoint Central Administration V3 and select Properties

    d.      Enter 12345 in the SSL port field.

    e.      Enter 80 in the TCP Port field.

    f.        Click Apply.

    g.       Navigate to the Directory Security Tab

    h.      In the Server Communications region of the tab, select Edit.

    i.         Select Require Secure Channel and click OK.

    j.        Navigate to the Directory Security Tab

    k.       Select Server Certificate

    l.         Click Next

    m.    Select Import a certificate from a .pfx file and click Next

    n.      Browse to the file

    o.      Click Next

    p.      Enter the password (if necessary) and click Next

    q.      Confirm that 12345 is in the SSL port field and click Next

    r.        Click Next

    s.       Click Finish

    t.        Click OK

    4.       Execute an IISRESET on the central administration host server

    5.       Update AAM for CA site

    a.       Open the OPERATIONS PAGE of the central administration site in a web browser (i.e. https://contoso86:12345/_admin/operations.aspx  - if a specific page is not indicated in the address, it will fail to open since the AAM requires updating).

    b.      If warned, ignore any certificate errors and continue browsing to the page.

    c.       Select Alternate Access Mappings from the Global Configuration subheader.

    d.      Click on the link for http://contoso86:12345