SharePoint 2007 Utility #2 - PI - Utility to Import/Export actual user profiles

Posted on 1/15/2007 @ 5:52 PM in #SharePoint by | Feedback | 28754 views

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 export and import user profiles between two MOSS installations that cannot talk with each other, OR, do not have BDC (say you had a cheapo version of MOSS). You could export data from (SAP, or other sources) to XML, and use that XML to suck in data into your SSP in MOSS.

How to use this utility?

The utility comes with online help. Just type in "PI -help" at commandline to get the help screen. The help screen reproduced below for your pleasure -

=================================================
Profile Import Utility
=================================================

Usage: PI -mapping <mapfile.xml> -inputs <inputfile.xml> -url <MOSS_Web_URL>

*********** Required Parameters are: ***********
-mappingfile:
This Parameter points to an XML file that specifies mapping between your XML elements and MOSS properties.
For instance, PostalCode in your SAP system may mean ZipCode in your MOSS system, the mapping would thus look like this -
<Mapping InputField='PostalCode' MOSSField='ZipCode'/>

-inputs The file taht contains your data

-url URL of the MOSS 2007 web.

*********** XML File structures are: ***********

Mapping File:
<Mappings>
<Mapping InputField=.. MOSSField=../>
...
</Mappings>

Inputs File:
<Inputs>
<Input>
<AccountName>MOSS2007\administrator</AccountName>
<Title>Some JobTitle</Title>
... other properties ...
</Input>
... other inputs ...
</Inputs>

The inputs file can have properties identified by elements that mean something to your MOSS web.
Both inputs are validated via Embedded Schemas (the schemas are available in the source code).

*********** Sample usage: ***********
PI -mapping mapfile.xml -inputs inputsfile.xml -url http://moss2007

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 4 properties - Boolean "IsValid", String "SSPUrl", string "MappingFile" and a String "InputsFile". It performs various validations, such as if it is able to find the specified files, the specified URL, and if everything works - it decides that IsValid = true, i.e. inputs are valid.

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();
}

In the actual code, one validation we need to do, is validate the XML Files, and see if they are in a valid acceptable form. As you can see, I do that using simple utility functions I have written, so my code becomes really simple to read -

if (
    Utilities.IsXmlValid(inputs.InputsFile, "Winsmarts.PI.Common.InputsFileSchema.xsd") &
    Utilities.IsXmlValid(inputs.MappingFile, "Winsmarts.PI.Common.MappingFileSchema.xsd")
    )
{

The schemas are embedded in my dll. Suggested further reading: Working with Embedded Resources, and Validating an XML file with an XSD Schema.

Here are the schemas I am using -

InputsFileSchema -

<?xml version="1.0" encoding="utf-8"?>
<
xs:schema
 
id="InputsFileSchema"
 
elementFormDefault="qualified"
 
xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <
xs:element name="Inputs">
    <
xs:complexType>
      <
xs:sequence>
        <
xs:element name="Input" minOccurs="0" maxOccurs="unbounded"/>
      </
xs:sequence>
    </
xs:complexType>
  </
xs:element>
</
xs:schema>

MappingFileSchema -

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema 
  id="MappingFileSchema" 
  elementFormDefault="qualified"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Mappings">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Mapping" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="Mapping">
    <xs:complexType>
      <xs:attribute name="InputField" use="required"/>
      <xs:attribute name="MOSSField" use="required"/>
      <xs:attribute name="DateFormat" use="optional"/>
    </xs:complexType>
  </xs:element>
</xs:schema>

Once I know all my inputs are valid, I can then start working on the actual import process. The first thing I do is to import the Mappings into a custom business object. This business object is a dictionary of "Mapping" object instances. In setting up this business object, I can do all kinds of magic - calculations, validations etc. Here is the structure of the business object -

And setting this up is abstracted into a single line of code -

Mappings mappings = new Mappings(inputs.MappingFile);

(Single line of code, because all the rest of the crap I dont' want to see is encapsulated inside the business object).

The next thing I do is, read up the inputs, and setup the necessary counters -

// Begin the import
XmlDocument inputDoc = new XmlDocument();
inputDoc.Load(inputs.InputsFile);
XmlNodeList inputNodes = inputDoc.SelectNodes("./Inputs/Input");

int successes = 0;
int errors = 0;

Then in order to begin the import, I get a hold of the appropriate SharePoint API objects

using (SPSite site = new SPSite(inputs.SSPURL))
{
    ServerContext context = ServerContext.GetContext(site);
    UserProfileManager mngr = new UserProfileManager(context);
    Mapping currentMapping = null;
    foreach (XmlNode inputNode in inputNodes)
    {
        string accountName = inputNode.SelectSingleNode("./" + mappings.AccountField).InnerText;
        UserProfile profile = mngr.GetUserProfile(accountName);
        try
        {
            foreach (XmlNode dataNode in inputNode.ChildNodes)
            {
// ... this is where more code will come } profile.Commit(); successes++; } catch (Exception ex) { errors++; Trace.WriteLine("Error Importing:" + accountName + ". Error Details:\n" + ex.ToString(), true); } } Trace.WriteLine("Import Finished, total errors: " + errors + " and total successes: " + successes); }

As you can see, above I iterate over every node, i.e. every single input specified, I then get a hold of the appropriate profile in SharePoint, and then for each specified value under the input, I do some more stuff). This "some more stuff", is basically setting appropriate values while taking mappings into consideration. This in C# goes right here you see the "... this is where more code will come" comment, and it looks like this -

currentMapping = mappings[dataNode.Name];

if (currentMapping.MOSSField != "AccountName")
{
    if (currentMapping.IsDate)
    {
        if (dataNode.InnerText.Length != 0)
            profile[currentMapping.MOSSField].Value =
                DateTime.ParseExact(dataNode.InnerText, currentMapping.DateFormat, null);
    }
    else
    {
        profile[currentMapping.MOSSField].Value = dataNode.InnerText;
    }
}

And then finally, once all the profile information I wished to set has been set appropriately, I simply call profile.Commit() to make the changes permanent, and keep a count of errors and successes.

Pretty simple really. :-). You can now export user profile information in your organization from any system to XML, and from that XML suck it into any MOSS installation.

SHWEET !! :-)

You can find the full code here -

http://www.codeplex.com/MOSSProfileImport/

Sound off but keep it civil:

Older comments..


On 2/7/2007 12:58:48 PM Denny said ..
Question: This utility will only sync existing user profiles. It will not create any new profiles if they don't exist. Is that correct? The codeline "mngr.GetUserProfile(accountName);" would throw "usernotfoundexception" if that profile does not exist. Please correct me if I am missing something here.


On 2/7/2007 2:58:26 PM Sahil Malik said ..
Yes ! But the code can be easily modified to add new users also.


On 6/17/2007 6:55:42 AM Pim Verver said ..
Just one question: the only help there seems to be is on importing xml into a moss installation. What is de commandline utility to export from a moss installation into an xml file?


On 3/12/2008 11:47:50 PM AAron nAAs said ..
I had to export 2003 profile data, so I used your file conventions. The mapping, and resulting inputfile are ready for import with PI utility. Thanks for posting!

Today I have to move some profile fields from an old SSP to a new SSP (due to a rebuild Microsoft recommended to fix a problem). I've tweaked my old exporter for MOSS 2007. I'll post it soon on my blog.


On 3/17/2008 6:53:15 AM Roland said ..
Thank's for this u tility, but where can we found the exe version? The codeplex give only the sources.


On 3/17/2008 10:28:03 AM Sahil Malik said ..
Hi Roland,

I never created the EXE. You can download and compile.


If you want, I can add you as a dev into the project, and you can create the release.

Interested?

SM


On 4/16/2008 2:23:32 AM Simo said ..
Hi,

As Pim Verver asked, how export users from moss to xml? I have a problem with script. I have more accounts in xml and it will give error(because this script can't add profile).

So how to export or how to modify this script to add users if they do not exist?

Simo


On 8/7/2008 6:13:30 PM Cliff said ..
Have you 'tweaked' your old exporter for MOSS 2007? If so, where did you post it?


On 8/28/2008 5:09:40 PM Michael said ..
I am also interested in finding out if you have created an exporter. I used Gary Lapointes 2003to2007 profile migrator stsadm.exe to migrate to 2007 but it does not work in a 2007to2007 scenario. I need to sync data between an intranet and an extranet.


On 9/1/2008 4:10:31 AM Bulat Gafurov said ..
> Yes ! But the code can be easily modified to add new users also.


Could you please point a direction in which user profiles can be created on a fly without synchronizing with AD? Say, we want to add external users to the portal, which are not and shoud not be added to AD? Any help would be appriciated.


On 9/17/2008 7:55:02 AM Saranya said ..
I have a inputs.xml which has users exported from another machine. But I am unable to import the user into my Dev box.


I get an exception - User not found.Could not load profile from database.


How do I tweak the code in order to import new users.


Thanks!


On 10/30/2008 3:03:00 PM said said ..
Hi,


i have the same error as Saranya "User not found.Could not load profile from database" Any updates !


On 12/17/2008 1:56:50 PM chandra said ..
Thank you Sahil!!!


Nice Tool


On 3/25/2009 9:31:59 PM Anonymous said ..
Would be nice to have actual mapping and input files for a default sharepoint installation. Mind posting them?


On 6/16/2009 10:27:10 PM mitg said ..
Can you provide the EXE version? I would like to migrate user profiles from a MOSS2007 to another MOSS2007.


On 10/4/2009 3:38:23 AM fariz said ..
hi, i also would like to know how to tweak the code to import new users. Please help


On 11/16/2010 7:16:05 PM steve said ..
How do i fetch bulk userprofile data using webservice?