MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Thursday, November 8, 2007

User Profile Properties

After running our upgrade and inspecting the user profile properties on the SSP that we created it was quickly discovered that some properties needed to be modified. Some of the changes were as simple as determining whether or not the property should be visible on the profile page or whether the user should be allowed to edit the property and some of them needed to be mapped to the corresponding field in Active Directory.

As part of my upgrade script I needed to make sure that these fields were set appropriately. You can set these fields via the browser by going to the SSP administration site and selecting "User profiles and properties" under the "User Profiles and My Sites" section. From there click "View profile properties" to see and edit the various properties.

In order to make these changes in batch during the upgrade I created two commands - the first was just to help me debug and is useful if you wish to get a dump of all the existing properties: gl-enumprofileproperties. The second command actually handles the editing of the properties: gl-editprofileproperty. The code for this is similar in nature to that of the gl-enumprofileprivacypolicies and gl-setprofileprivacypolicy commands - in fact I started with the code from those commands as a template and modified as needed. The one simplification was that I now only had one place to look for objects of interest - UserProfileConfigManager. The commands I created are detailed below.

1. gl-enumprofileproperties

This command works exactly like the gl-enumprofileprivacypolicies command but removes the privacy policy objects and adds additional information specific to the Property object:

   1: public override int Run(string command, StringDictionary keyValues, out string output)
   2: {
   3:  output = string.Empty;
   4:  
   5:  InitParameters(keyValues);
   6:  
   7:  ServerContext serverContext = ServerContext.GetContext(Params["sspname"].Value);
   8:  UserProfileConfigManager manager = new UserProfileConfigManager(serverContext);
   9:  PropertyCollection properties = manager.GetProperties();
  10:  
  11:  StringBuilder sb = new StringBuilder();
  12:  
  13:  XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
  14:  xmlWriter.Formatting = Formatting.Indented;
  15:  
  16:  xmlWriter.WriteStartElement("Properties");
  17:  xmlWriter.WriteAttributeString("ssp", Params["sspname"].Value);
  18:  
  19:  foreach (Property item in properties)
  20:  {
  21:   AddXml(xmlWriter, item, manager);
  22:  }
  23:  
  24:  xmlWriter.WriteEndElement();
  25:  xmlWriter.Flush();
  26:  
  27:  output += sb.ToString();
  28:  
  29:  if (Params["outputfile"].UserTypedIn)
  30:  {
  31:   File.WriteAllText(Params["outputfile"].Value, output);
  32:   output = string.Empty;
  33:  }
  34:  
  35:  return 1;
  36: }
  37:  
  38: private static void AddXml(XmlTextWriter xmlWriter, Property item, UserProfileConfigManager manager)
  39: {
  40:  xmlWriter.WriteStartElement("Property");
  41:  
  42:  xmlWriter.WriteElementString("Name", item.Name);
  43:  xmlWriter.WriteElementString("AllowPolicyOverride", item.AllowPolicyOverride.ToString());
  44:  xmlWriter.WriteStartElement("ChoiceList");
  45:  if (item.ChoiceList != null)
  46:  {
  47:   foreach (string s in item.ChoiceList.GetAllTerms(true))
  48:   {
  49:    xmlWriter.WriteElementString("Choice", s);
  50:   }
  51:  }
  52:  xmlWriter.WriteEndElement();
  53:  xmlWriter.WriteElementString("ChoiceType", item.ChoiceType.ToString());
  54:  xmlWriter.WriteElementString("DefaultPrivacy", item.DefaultPrivacy.ToString());
  55:  xmlWriter.WriteElementString("Description", item.Description);
  56:  xmlWriter.WriteElementString("DisplayName", item.DisplayName);
  57:  xmlWriter.WriteElementString("DisplayOrder", item.DisplayOrder.ToString());
  58:  xmlWriter.WriteElementString("IsAdminEditable", item.IsAdminEditable.ToString());
  59:  xmlWriter.WriteElementString("IsAlias", item.IsAlias.ToString());
  60:  xmlWriter.WriteElementString("IsColleagueEventLog", item.IsColleagueEventLog.ToString());
  61:  xmlWriter.WriteElementString("IsImported", item.IsImported.ToString());
  62:  xmlWriter.WriteElementString("IsMultivalued", item.IsMultivalued.ToString());
  63:  xmlWriter.WriteElementString("IsReplicable", item.IsReplicable.ToString());
  64:  xmlWriter.WriteElementString("IsRequired", item.IsRequired.ToString());
  65:  xmlWriter.WriteElementString("IsSearchable", item.IsSearchable.ToString());
  66:  xmlWriter.WriteElementString("IsSection", item.IsSection.ToString());
  67:  xmlWriter.WriteElementString("IsSystem", item.IsSystem.ToString());
  68:  xmlWriter.WriteElementString("IsUpgrade", item.IsUpgrade.ToString());
  69:  xmlWriter.WriteElementString("IsUpgradePrivate", item.IsUpgradePrivate.ToString());
  70:  xmlWriter.WriteElementString("IsUserEditable", item.IsUserEditable.ToString());
  71:  xmlWriter.WriteElementString("IsVisibleOnEditor", item.IsVisibleOnEditor.ToString());
  72:  xmlWriter.WriteElementString("IsVisibleOnViewer", item.IsVisibleOnViewer.ToString());
  73:  xmlWriter.WriteElementString("Length", item.Length.ToString());
  74:  xmlWriter.WriteElementString("ManagedPropertyName", item.ManagedPropertyName);
  75:  xmlWriter.WriteElementString("MaximumShown", item.MaximumShown.ToString());
  76:  xmlWriter.WriteElementString("PrivacyPolicy", item.PrivacyPolicy.ToString());
  77:  xmlWriter.WriteElementString("Separator", item.Separator.ToString());
  78:  xmlWriter.WriteElementString("Type", item.Type);
  79:  xmlWriter.WriteElementString("URI", item.URI);
  80:  xmlWriter.WriteElementString("UserOverridePrivacy", item.UserOverridePrivacy.ToString());
  81:  
  82:  xmlWriter.WriteStartElement("ImportMapping");
  83:  PropertyMapCollection propertyMapping = manager.GetDataSource().PropertyMapping;
  84:  PropertyMap map = propertyMapping[item.Name];
  85:  
  86:  if (map != null)
  87:  {
  88:   xmlWriter.WriteElementString("DSPropName", map.DSPropName);
  89:   xmlWriter.WriteElementString("ConnectionName", map.ConnectionName);
  90:   xmlWriter.WriteElementString("AssociationName", map.AssociationName);
  91:  }
  92:  xmlWriter.WriteEndElement();
  93:  
  94:  xmlWriter.WriteEndElement();
  95: }

The syntax of the command can be seen below:

C:\>stsadm -help gl-enumprofileproperties

stsadm -o gl-enumprofileproperties

Lists the user profile properties associated with the SSP.

Parameters:
        -sspname <name of the SSP>
        [-outputfile <file to output results to>]

Here's an example of how to output all the profile information to a file:

stsadm –o gl-enumprofileproperties -sspname "SSP1" -outputfile "c:\properties.xml"

Running the above command will produce results similar to the following (I've abbreviated the output for the sake of space):

<Properties ssp="SSP1">
  ...
  <Property>
    <Name>CollegeMajor</Name>
    <AllowPolicyOverride>True</AllowPolicyOverride>
    <ChoiceList />
    <ChoiceType>Off</ChoiceType>
    <DefaultPrivacy>Public</DefaultPrivacy>
    <Description />
    <DisplayName>College Major</DisplayName>
    <DisplayOrder>5202</DisplayOrder>
    <IsAdminEditable>True</IsAdminEditable>
    <IsAlias>False</IsAlias>
    <IsColleagueEventLog>False</IsColleagueEventLog>
    <IsImported>False</IsImported>
    <IsMultivalued>False</IsMultivalued>
    <IsReplicable>False</IsReplicable>
    <IsRequired>False</IsRequired>
    <IsSearchable>True</IsSearchable>
    <IsSection>False</IsSection>
    <IsSystem>False</IsSystem>
    <IsUpgrade>False</IsUpgrade>
    <IsUpgradePrivate>False</IsUpgradePrivate>
    <IsUserEditable>True</IsUserEditable>
    <IsVisibleOnEditor>True</IsVisibleOnEditor>
    <IsVisibleOnViewer>False</IsVisibleOnViewer>
    <Length>255</Length>
    <ManagedPropertyName>CollegeMajor</ManagedPropertyName>
    <MaximumShown>10</MaximumShown>
    <PrivacyPolicy>OptIn</PrivacyPolicy>
    <Separator>Comma</Separator>
    <Type>string</Type>
    <URI>urn:schemas-microsoft-com:sharepoint:portal:profile:CollegeMajor</URI>
    <UserOverridePrivacy>False</UserOverridePrivacy>
    <ImportMapping />
  </Property>
  ...
</Properties>


2. gl-editprofileproperty

This command actually gave me a bit of trouble because I couldn't get large parts of the internal Microsoft code disassembled to see what exactly they were doing (Reflector seems to choke on large chunks of the SharePoint API). The piece that gave me the most trouble is the mapping of a property to a data sources fields. If you have only one connection (Active Directory specifically is what I tested against) then the code and command is really simple. What I'm uncomfortable with is the use of the connectionname and associationname parameters which I use to build the property map based on what I could gleam from the IL code. I do feel like there's something I'm missing with the usage of these properties (PropertyMap.ConnectionName and PropertyMap.AssociationName) - if anyone finds any issues with their use please forward on to me - my test environment only included Active Directory as a source (which was the master source) so I wasn't able to test the usage of these properties.

   1: public override int Run(string command, StringDictionary keyValues, out string output)
   2: {
   3:  output = string.Empty;
   4:  
   5:  InitParameters(keyValues);
   6:  
   7:  string name = Params["name"].Value;
   8:  
   9:  ServerContext serverContext = ServerContext.GetContext(Params["sspname"].Value);
  10:  UserProfileConfigManager manager = new UserProfileConfigManager(serverContext);
  11:  PropertyCollection properties = manager.GetProperties();
  12:  Property property = properties.GetPropertyByName(name);
  13:  
  14:  if (property == null)
  15:  {
  16:   // We couldn't find using the system name so try using the display name.
  17:   foreach (Property item in properties)
  18:   {
  19:    if (item.DisplayName.ToLowerInvariant() == name.ToLowerInvariant())
  20:    {
  21:     if (property != null)
  22:      throw new ArgumentException(string.Format("Duplicate properties found matching the name {0}.  Use the system name instead (use enumprofileprivacypolicies to get system names).", name));
  23:     property = item;
  24:    }
  25:   }
  26:  }
  27:  
  28:  if (property == null)
  29:  {
  30:   throw new ArgumentException(string.Format("Could not find User Profile property '{0}'", name));
  31:  }
  32:  
  33:  
  34:  if (Params["description"].UserTypedIn)
  35:   property.Description = Params["description"].Value;
  36:  
  37:  if (Params["displayname"].UserTypedIn)
  38:   property.DisplayName = Params["displayname"].Value;
  39:  
  40:  if (Params["alias"].UserTypedIn)
  41:   property.IsAlias = bool.Parse(Params["alias"].Value);
  42:  
  43:  if (Params["showchangesincolleaguetrackerwebpart"].UserTypedIn)
  44:   property.IsColleagueEventLog = bool.Parse(Params["showchangesincolleaguetrackerwebpart"].Value);
  45:  
  46:  if (Params["indexed"].UserTypedIn)
  47:   property.IsSearchable = bool.Parse(Params["indexed"].Value);
  48:  
  49:  if (Params["isusereditable"].UserTypedIn)
  50:   property.IsUserEditable = bool.Parse(Params["isusereditable"].Value);
  51:  
  52:  if (Params["isvisibleoneditor"].UserTypedIn)
  53:   property.IsVisibleOnEditor = bool.Parse(Params["isvisibleoneditor"].Value);
  54:  
  55:  if (Params["isvisibleonviewer"].UserTypedIn)
  56:   property.IsVisibleOnViewer = bool.Parse(Params["isvisibleonviewer"].Value);
  57:  
  58:  if (Params["maximumshown"].UserTypedIn)
  59:   property.MaximumShown = int.Parse(Params["maximumshown"].Value);
  60:  
  61:  if (Params["separator"].UserTypedIn)
  62:   property.Separator = (MultiValueSeparator)Enum.Parse(typeof(MultiValueSeparator), Params["separator"].Value, true);
  63:  
  64:  
  65:  if (Params["mapping"].UserTypedIn)
  66:  {
  67:   PropertyMapCollection propertyMapping = manager.GetDataSource().PropertyMapping;
  68:  
  69:   if (!string.IsNullOrEmpty(Params["mapping"].Value))
  70:   {
  71:    PropertyMap map = propertyMapping[property.Name];
  72:  
  73:    if (map == null)
  74:    {
  75:     string connectionName = string.Empty;
  76:     string associationName = string.Empty;
  77:     if (Params["connectionname"].UserTypedIn)
  78:      connectionName = Params["connectionname"].Value;
  79:     if (Params["associationname"].UserTypedIn)
  80:      associationName = Params["associationname"].Value;
  81:  
  82:     propertyMapping.Add(property.Name, Params["mapping"].Value, connectionName, associationName);
  83:    }
  84:    else
  85:    {
  86:     map.DSPropName = Params["mapping"].Value;
  87:     if (Params["connectionname"].UserTypedIn)
  88:      map.ConnectionName = Params["connectionname"].Value;
  89:     if (Params["associationname"].UserTypedIn)
  90:      map.AssociationName = Params["associationname"].Value;
  91:     map.Commit();
  92:    }
  93:   }
  94:   else
  95:    propertyMapping.Remove(property.Name);
  96:  }
  97:  
  98:  property.Commit();
  99:  
 100:  return 1;
 101: }

The syntax of the command can be seen below:

C:\>stsadm -help gl-editprofileproperty

stsadm -o gl-editprofileproperty

Edits a profile property.

Parameters:
        -sspname <name of the SSP>
        -name <name of the profile property to edit>
        [-description <property description>]
        [-displayname <display name>]
        [-alias <true | false>]
        [-showchangesincolleaguetrackerwebpart <true | false>]
        [-indexed <true | false>]
        [-isusereditable <true | false>]
        [-isvisibleoneditor <true | false>]
        [-isvisibleonviewer <true | false>]
        [-separator <comma | semicolon>]
        [-maximumshown <maximum number of values to show before displaying ellip
sis for multi-value property>]
        [-mapping <data source field to map to (case sensitive) - leave empty to
 clear mapping>]
        [-connectionname <source data connection>]
        [-associationname <data source associated entity>]

Here's an example of how to edit the CollegeMajor profile property identified above:

stsadm –o gl-editprofileproperty -sspname "SSP1" -name "CollegeMajor" -description "College Major" -displayname "College Major" -alias false -showchangesincolleaguetrackerwebpart false -indexed false -isusereditable true -isvisibleoneditor true -isvisibleonviewer true -mapping collegeMajor

Note that the above command assumes that "collegeMajor" exists in Active Directory (case matters).

10 comments:

Peter said...

Is there a command to edit a user's profile property? e.g. I want to change a user's Manager.

Gary Lapointe said...

Peter - no there isn't. Your best bet is to use PowerShell.

arden said...

Hi! Is there a way, to generate My Site (and My Profile) to every user? As far as I know, it is generated when the user clicks on My Home... - but we want to have all users get My Profile, so we can link to it and set properties etc.
Thanks!

arden said...

Automatic MySite generation, I found it:
http://jopx.blogspot.com/2006/12/automatic-my-site-creation-with.html

Dave said...

Hi Gary, i'm trying to run the command

stsadm –o gl-enumprofileproperties -sspname "SSP1" -outputfile "c:\properties.xml"

but it just errors saying "Command line error", any idea what could cause that?

Gary Lapointe said...

retype the command by hand - don't paste it in - you're getting the wrong hyphen with the -o.

Senti said...

Hi gary
Is there a way to script mapping/adding/modifying user's profile properties with AD.

Using GUI is too annoying and slow and i have to do this multiple time in my 7 different test environments.
Takes a long time..

thank you

Gary Lapointe said...

You could combine my profile property stsadm commands with some powershell. Would need more specifics to be able to help you beyond that.

Epic said...

Any thoughts on writing something for 2010? I'm dreading having to click 1 million times to get all of the properties added and in the right order.

Gary Lapointe said...

I have plans to migrate my user profile commands to 2010 but just haven't had time. I may work on them in the next couple of weeks though as I have some stuff I need for a demo that I'm putting together.