Download code at - http://www.codeplex.com/MOSSProfileReplicate/
Recommended Reading/Basics before you dive into this blog post: User Profiles and Audience Targeting.
What is this utility?
This is a command line utility that will allow you to manage profile properties setup on a SharePoint web's SharedService provider. It allows you to export properties from an SSP to XML, and vice versa.
How to use this utility?
The utility comes with online help. Just type in "ProfilePropertyMgr -help" at commandline to get the help screen. The help screen reproduced below for your pleasure -
=================================================
Profile Property Manager - Help
=================================================
Usage: ProfilePropertyMgr -url <UrlToWeb> -filename <FileName> -import
ProfilePropertyMgr -url <UrlToWeb> -filename <FileName> -export
or
ProfilePropertyMgr -help
*********** Used For ***********
Export Profile properties from SharePoint to an XML file.
Import Profile properties from an XML file to SharePoint.
*********** Required Parameters are: ***********
-url <UrlToWeb>
-file <FileName.xml>
*********** XML File structure: ***********
<Properties>
<Property>
<PropertyInfo Name=" Type=" Data="/>
<PropertyInfo ..>
...
</Property>
<Property>...</Property>
...
</Properties>
*********** Sample usage: ***********
ProfilePropertyMgr -url http://moss2007 -filename output.xml -export
ProfilePropertyMgr -url http://moss2007 -filename input.xml -import
Code Explanation
The code accepts input as command line parameters, and passes them to a business object called ProgramInputs as shown below:
static void Main(string[] args)
{
ProgramInputs inputs = new ProgramInputs(args);
The ProgramInputs business object has 3 properties - Boolean "IsValid", String "URL", and "CurrentOperation". CurrentOperation is a custom enum with 3 possible valies - Unspecified, Import and Export. ProgramInputs encapsulates the functionality to validate inputs, and tell the end user if the inputs are accepted, or what was missing.
Once we know that the inputs are valid, we can act upon them. This is done using the following code
if (inputs.IsValid)
{ ...
}
else
{
Trace.WriteLine("Invalid inputs specified", true);
Trace.PrintHelp();
}
And the actual code looks like this -
using (SPSite site = new SPSite(inputs.Url))
{
ServerContext context = ServerContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
switch (inputs.CurrentOperation)
{
case Operation.Unspecified:
throw new InvalidOperationException("Invalid input operation specified, or input operation omitted, cannot continue");
case Operation.Import:
WorkerBee.ImportProfiles(inputs, profileManager);
break;
case Operation.Export:
WorkerBee.ExportProfiles(inputs, profileManager);
break;
default: // This will never happen, so safely ignored.
break;
}
}
As you would note from the above, the work is delegated to a class called WorkerBee. It has two methods, one to export, and one to import. I first get a hold of all the SharePoint properties using ProfileManager.Properties, and then I use reflection to sniff out which properties are exportable. Basically, anything that can be succesfully imported on the other end, is exportable on this end (duh!). So what is importable/exportable? Anything that is Read/Write, is value-type (or string), and doesn't start with "SSP-" (reserved for SharePoint), or isn't the reserved UserProfile_GUID property column. This in C#, is described as below -
private static bool ShouldExport(PropertyInfo typeProperty, Property profileProperty, bool entireProperty)
{
bool toReturn = false;
toReturn = typeProperty.CanRead & typeProperty.CanWrite &
(
typeProperty.PropertyType.IsValueType |
typeProperty.PropertyType.FullName.Equals("System.String")
);
// Proceed only if toReturn is still true ;
// Also, the following applies to only entire properties
if (toReturn & entireProperty)
{
object reflectedValue = typeProperty.GetValue(profileProperty, null);
string strReflectedValue = (reflectedValue == null) ? String.Empty : reflectedValue.ToString();
toReturn &= !strReflectedValue.Equals("UserProfile_GUID");
toReturn &= !strReflectedValue.StartsWith("SPS-");
}
return toReturn;
}
The full Export Profiles method looks like this -
internal static void ExportProfiles(ProgramInputs inputs, UserProfileManager profileManager)
{
// Variable Naming convention: <objectinConcern><Property/Properties>
PropertyCollection profileProperties = profileManager.Properties;
XmlDocument exportProperties = new XmlDocument();
XmlNode nodeProperties = exportProperties.CreateNode(XmlNodeType.Element, "Properties", "");
exportProperties.AppendChild(nodeProperties);
PropertyInfo[] allProps = typeof(Property).GetProperties();
Dictionary<string, PropertyInfo> typeProperties = new Dictionary<string, PropertyInfo>();
foreach (PropertyInfo typeProperty in allProps)
{
typeProperties.Add(typeProperty.Name, typeProperty);
}
foreach (Property profileProperty in profileProperties)
{
// Should I be even exporting this property?
bool isExportable = ShouldExport(typeProperties["Name"], profileProperty, true);
if (isExportable)
{
XmlNode nodeProperty = exportProperties.CreateNode(XmlNodeType.Element, "Property", "");
nodeProperties.AppendChild(nodeProperty);
foreach (PropertyInfo typeProperty in typeProperties.Values)
{
if (ShouldExport(typeProperty, profileProperty, false))
{
XmlNode nodePropertyInfo = exportProperties.CreateNode(XmlNodeType.Element, "PropertyInfo", "");
XmlAttribute attribProfilePropInfo;
object reflectedValue;
//Name
attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Name", "") as XmlAttribute;
attribProfilePropInfo.Value = typeProperty.Name;
nodePropertyInfo.Attributes.Append(attribProfilePropInfo);
// Data Type
attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Type", "") as XmlAttribute;
attribProfilePropInfo.Value = typeProperty.PropertyType.AssemblyQualifiedName;
nodePropertyInfo.Attributes.Append(attribProfilePropInfo);
// Value
attribProfilePropInfo = exportProperties.CreateNode(XmlNodeType.Attribute, "Data", "") as XmlAttribute;
reflectedValue = typeProperty.GetValue(profileProperty, null);
attribProfilePropInfo.Value = (reflectedValue == null) ? String.Empty : reflectedValue.ToString();
nodePropertyInfo.Attributes.Append(attribProfilePropInfo);
nodeProperty.AppendChild(nodePropertyInfo);
}
}
}
}
exportProperties.Save(inputs.FileName);
}
The ImportProfiles method is the exact anti-thesis of the above, so I am not going to bother copy pasting that here.
You can find the full code here -
http://www.codeplex.com/MOSSProfileReplicate/