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, August 14, 2008

Importing Profile Properties

The project that I'm currently on has a test environment in which many configurations were made to the user profile properties settings.  I just began the process of building out their production environment and was faced with a minor issue - how do I get all the settings that have been applied to the test environments profile properties migrated to the production environment in a reliable and repeatable way?  I took a look around and remembered that I already had a command to dump out the profile property settings into an XML file - gl-enumprofileproperties - so now I just needed another command that could take the output from my previous command and use it to import those settings into another farm.

Fortunately this turned out to be very easy (with one minor caveat).  I called this new command gl-importprofileproperties.  First lets take a look at a sample of the output generated by the gl-enumprofileproperties command:

<Properties>
  <Property>
    <Name>UserProfile_GUID</Name>
    <AllowPolicyOverride>False</AllowPolicyOverride>
    <ChoiceList />
    <ChoiceType>Off</ChoiceType>
    <DefaultPrivacy>Public</DefaultPrivacy>
    <Description />
    <DisplayName>Id</DisplayName>
    <DisplayOrder>1</DisplayOrder>
    <IsAdminEditable>False</IsAdminEditable>
    <IsAlias>False</IsAlias>
    <IsColleagueEventLog>False</IsColleagueEventLog>
    <IsImported>False</IsImported>
    <IsMultivalued>False</IsMultivalued>
    <IsReplicable>False</IsReplicable>
    <IsRequired>True</IsRequired>
    <IsSearchable>True</IsSearchable>
    <IsSection>False</IsSection>
    <IsSystem>True</IsSystem>
    <IsUpgrade>False</IsUpgrade>
    <IsUpgradePrivate>False</IsUpgradePrivate>
    <IsUserEditable>False</IsUserEditable>
    <IsVisibleOnEditor>False</IsVisibleOnEditor>
    <IsVisibleOnViewer>False</IsVisibleOnViewer>
    <Length>0</Length>
    <ManagedPropertyName>UserProfile_GUID</ManagedPropertyName>
    <MaximumShown>10</MaximumShown>
    <PrivacyPolicy>Mandatory</PrivacyPolicy>
    <Separator>Unknown</Separator>
    <Type>unique identifier</Type>
    <URI>urn:schemas-microsoft-com:sharepoint:portal:profile:UserProfile_GUID</URI>
    <UserOverridePrivacy>False</UserOverridePrivacy>
    <ImportMapping />
  </Property>
  <Property>
    <Name>SID</Name>
    <AllowPolicyOverride>False</AllowPolicyOverride>
    <ChoiceList />
    <ChoiceType>Off</ChoiceType>
    <DefaultPrivacy>Public</DefaultPrivacy>
    <Description />
    <DisplayName>SID</DisplayName>
    <DisplayOrder>2</DisplayOrder>
    <IsAdminEditable>False</IsAdminEditable>
    <IsAlias>False</IsAlias>
    <IsColleagueEventLog>False</IsColleagueEventLog>
    <IsImported>True</IsImported>
    <IsMultivalued>False</IsMultivalued>
    <IsReplicable>False</IsReplicable>
    <IsRequired>False</IsRequired>
    <IsSearchable>False</IsSearchable>
    <IsSection>False</IsSection>
    <IsSystem>True</IsSystem>
    <IsUpgrade>False</IsUpgrade>
    <IsUpgradePrivate>False</IsUpgradePrivate>
    <IsUserEditable>False</IsUserEditable>
    <IsVisibleOnEditor>False</IsVisibleOnEditor>
    <IsVisibleOnViewer>False</IsVisibleOnViewer>
    <Length>512</Length>
    <ManagedPropertyName>SID</ManagedPropertyName>
    <MaximumShown>10</MaximumShown>
    <PrivacyPolicy>OptIn</PrivacyPolicy>
    <Separator>Unknown</Separator>
    <Type>binary</Type>
    <URI>urn:schemas-microsoft-com:sharepoint:portal:profile:SID</URI>
    <UserOverridePrivacy>False</UserOverridePrivacy>
    <ImportMapping>
      <DSPropName>objectSID</DSPropName>
      <ConnectionName />
      <AssociationName />
    </ImportMapping>
  </Property>
  ...
</Properties>

As you can see from the above XML there's really not much to - it's just a dump of all the properties (including read-only properties) that are found on the Microsoft.Office.Server.UserProfiles.Property object.  So, importing these settings back in is a simple matter of looping through the Property XML elements shown above, finding the right Property object, and then set each property value.  Here's the core code that accomplishes this:

   1: /// <summary>
   2: /// Imports the user profile properties using the provided input file.
   3: /// </summary>
   4: /// <param name="sspName">Name of the SSP.</param>
   5: /// <param name="input">The input.</param>
   6: /// <param name="removeMissing">if set to <c>true</c> [remove missing].</param>
   7: public static void Import(string sspName, string input, bool removeMissing)
   8: {
   9:     ServerContext serverContext;
  10:     if (string.IsNullOrEmpty(sspName))
  11:         serverContext = ServerContext.Default;
  12:     else
  13:         serverContext = ServerContext.GetContext(sspName);
  14:  
  15:     UserProfileConfigManager manager = new UserProfileConfigManager(serverContext);
  16:     PropertyCollection properties = manager.GetProperties();
  17:  
  18:     XmlDocument xmlDoc = new XmlDocument();
  19:     xmlDoc.Load(input);
  20:  
  21:     Log("Import started at {0}", DateTime.Now.ToString());
  22:  
  23:     try
  24:     {
  25:         bool displayOrderChanged = false;
  26:         foreach (XmlElement propElement in xmlDoc.SelectNodes("//Property"))
  27:         {
  28:             string propName = propElement.SelectSingleNode("Name").InnerText;
  29:             Property prop = properties.GetPropertyByName(propName);
  30:             bool isNew = false;
  31:             if (prop == null)
  32:             {
  33:                 Log("Progress: Creating property '{0}'.", propName);
  34:                 // We couldn't find a matching property so go ahead and create it
  35:                 prop = properties.Create(bool.Parse(propElement.SelectSingleNode("IsSection").InnerText));
  36:                 prop.Name = propName;
  37:                 isNew = true;
  38:                 SetProperties(prop, propElement, isNew, manager);
  39:                 properties.Add(prop);
  40:             }
  41:             Log("Progress: Setting properties for '{0}'.", propName);
  42:             try
  43:             {
  44:                 if (!isNew)
  45:                     SetProperties(prop, propElement, isNew, manager);
  46:                 prop.Commit();
  47:  
  48:                 SetDataMapping(propElement, prop, manager);
  49:  
  50:                 Log("Progress: Property '{0}' imported.", propName);
  51:             }
  52:             catch (Exception ex)
  53:             {
  54:                 Log("ERROR: {0}\r\n{1}", EventLogEntryType.Error, ex.Message, ex.StackTrace);
  55:             }
  56:         }
  57:  
  58:         if (removeMissing)
  59:         {
  60:             Log("Progress: Removing properties not included in the import...");
  61:             PropertyCollection workColl = manager.GetProperties();
  62:             foreach (Property prop in properties)
  63:             {
  64:                 if (xmlDoc.SelectSingleNode(string.Format("//Property[Name='{0}']", prop.Name)) == null)
  65:                 {
  66:                     Log("Progress: Removing property '{0}'.", prop.Name);
  67:                     workColl.RemovePropertyByName(prop.Name);
  68:                 }
  69:             }
  70:         }
  71:  
  72:         Log("Progress: Setting display order...");
  73:         properties = manager.GetProperties();
  74:         foreach (XmlElement propElement in xmlDoc.SelectNodes("//Property"))
  75:         {
  76:             string propName = propElement.SelectSingleNode("Name").InnerText;
  77:             Property prop = properties.GetPropertyByName(propName);
  78:  
  79:             if (!string.IsNullOrEmpty(propElement.SelectSingleNode("DisplayOrder").InnerText))
  80:             {
  81:                 int displayOrder = int.Parse(propElement.SelectSingleNode("DisplayOrder").InnerText);
  82:                 if (displayOrder != prop.DisplayOrder)
  83:                 {
  84:                     Log("Progress: Setting display order for '{0}' to '{1}'.", prop.Name, displayOrder.ToString());
  85:                     properties.SetDisplayOrderByPropertyName(prop.Name, displayOrder);
  86:                     displayOrderChanged = true;
  87:                 }
  88:             }
  89:             
  90:         }
  91:         if (displayOrderChanged)
  92:         {
  93:             Log("Progress: Committing display order.");
  94:             properties.CommitDisplayOrder();
  95:         }
  96:     }
  97:     catch (Exception ex)
  98:     {
  99:         Log("ERROR: {0}\r\n{1}", EventLogEntryType.Error, ex.Message, ex.StackTrace);
 100:     }
 101:     finally
 102:     {
 103:         Log("Import finished at {0}", DateTime.Now.ToString());
 104:     }
 105: }
 106:  
 107: /// <summary>
 108: /// Sets the properties.
 109: /// </summary>
 110: /// <param name="prop">The prop.</param>
 111: /// <param name="propXml">The prop XML.</param>
 112: /// <param name="isNew">if set to <c>true</c> [is new].</param>
 113: /// <param name="manager">The manager.</param>
 114: private static void SetProperties(Property prop, XmlElement propXml, bool isNew, UserProfileConfigManager manager)
 115: {
 116:     string displayName = propXml.SelectSingleNode("DisplayName").InnerText;
 117:     if (isNew)
 118:     {
 119:         prop.DisplayName = displayName;
 120:  
 121:         bool isMultivalued = bool.Parse(propXml.SelectSingleNode("IsMultivalued").InnerText);
 122:         if (isMultivalued != prop.IsMultivalued)
 123:             prop.IsMultivalued = isMultivalued;
 124:  
 125:         int length = int.Parse(propXml.SelectSingleNode("Length").InnerText);
 126:         if (prop.Length != length)
 127:             prop.Length = length;
 128:  
 129:         string type = propXml.SelectSingleNode("Type").InnerText;
 130:         if (type != prop.Type)
 131:             prop.Type = type;
 132:     }
 133:  
 134:     //prop.AllowPolicyOverride = bool.Parse(propXml.SelectSingleNode("AllowPolicyOverride").InnerText);
 135:     //prop.DisplayOrder = propXml.SelectSingleNode("DisplayOrder").InnerText;
 136:     //prop.IsAdminEditable = bool.Parse(propXml.SelectSingleNode("IsAdminEditable").InnerText);
 137:     //prop.IsImported = bool.Parse(propXml.SelectSingleNode("IsImported").InnerText);
 138:     //prop.IsRequired = bool.Parse(propXml.SelectSingleNode("IsRequired").InnerText);
 139:     //prop.IsSection = bool.Parse(propXml.SelectSingleNode("IsSection").InnerText);
 140:     //prop.ManagedPropertyName = propXml.SelectSingleNode("ManagedPropertyName").InnerText;
 141:     //prop.URI = propXml.SelectSingleNode("URI").InnerText;
 142:     
 143:     if (displayName != prop.DisplayName)
 144:         prop.DisplayName = displayName;
 145:     
 146:     Privacy defaultPrivacy = (Privacy) Enum.Parse(typeof (Privacy), propXml.SelectSingleNode("DefaultPrivacy").InnerText, true);
 147:     if (defaultPrivacy != prop.DefaultPrivacy)
 148:         prop.DefaultPrivacy = defaultPrivacy;
 149:  
 150:     string desc = propXml.SelectSingleNode("Description").InnerText;
 151:     if (desc != prop.Description)
 152:         prop.Description = desc;
 153:  
 154:     bool isAlias = bool.Parse(propXml.SelectSingleNode("IsAlias").InnerText);
 155:     if (isAlias != prop.IsAlias)
 156:         prop.IsAlias = isAlias;
 157:  
 158:     bool isColleagueEventLog = bool.Parse(propXml.SelectSingleNode("IsColleagueEventLog").InnerText);
 159:     if (isColleagueEventLog != prop.IsColleagueEventLog)
 160:         prop.IsColleagueEventLog = isColleagueEventLog;
 161:  
 162:     bool isReplicable = bool.Parse(propXml.SelectSingleNode("IsReplicable").InnerText);
 163:     if (isReplicable != prop.IsReplicable)
 164:         prop.IsReplicable = isReplicable;
 165:  
 166:     bool isSearchable = bool.Parse(propXml.SelectSingleNode("IsSearchable").InnerText);
 167:     if (isSearchable != prop.IsSearchable)
 168:         prop.IsSearchable = isSearchable;
 169:  
 170:     bool isUpgrade = bool.Parse(propXml.SelectSingleNode("IsUpgrade").InnerText);
 171:     if (isUpgrade != prop.IsUpgrade)
 172:         prop.IsUpgrade = isUpgrade;
 173:  
 174:     bool isUpgradePrivate = bool.Parse(propXml.SelectSingleNode("IsUpgradePrivate").InnerText);
 175:     if (isUpgradePrivate != prop.IsUpgradePrivate)
 176:         prop.IsUpgradePrivate = isUpgradePrivate;
 177:  
 178:     bool isUserEditable = bool.Parse(propXml.SelectSingleNode("IsUserEditable").InnerText);
 179:     if (isUserEditable != prop.IsUserEditable)
 180:         prop.IsUserEditable = isUserEditable;
 181:  
 182:     bool isVisibleOnEditor = bool.Parse(propXml.SelectSingleNode("IsVisibleOnEditor").InnerText);
 183:     if (isVisibleOnEditor != prop.IsVisibleOnEditor)
 184:         prop.IsVisibleOnEditor = isVisibleOnEditor;
 185:  
 186:     bool isVisibleOnViewer = bool.Parse(propXml.SelectSingleNode("IsVisibleOnViewer").InnerText);
 187:     if (isVisibleOnViewer != prop.IsVisibleOnViewer)
 188:         prop.IsVisibleOnViewer = isVisibleOnViewer;
 189:  
 190:     int maxShown = int.Parse(propXml.SelectSingleNode("MaximumShown").InnerText);
 191:     if (maxShown != prop.MaximumShown)
 192:         prop.MaximumShown = maxShown;
 193:  
 194:     PrivacyPolicy privacyPolicy = (PrivacyPolicy)Enum.Parse(typeof(PrivacyPolicy), propXml.SelectSingleNode("PrivacyPolicy").InnerText, true);
 195:     if (privacyPolicy != prop.PrivacyPolicy)
 196:         prop.PrivacyPolicy = privacyPolicy;
 197:  
 198:     MultiValueSeparator separator = (MultiValueSeparator)Enum.Parse(typeof(MultiValueSeparator), propXml.SelectSingleNode("Separator").InnerText, true);
 199:     if (separator != prop.Separator)
 200:         prop.Separator = separator;
 201:  
 202:     bool userOverridePrivacy = bool.Parse(propXml.SelectSingleNode("UserOverridePrivacy").InnerText);
 203:     if (userOverridePrivacy != prop.UserOverridePrivacy)
 204:         prop.UserOverridePrivacy = userOverridePrivacy;
 205:  
 206:     ChoiceTypes choiceType = (ChoiceTypes)Enum.Parse(typeof(ChoiceTypes), propXml.SelectSingleNode("ChoiceType").InnerText, true);
 207:     if (choiceType != prop.ChoiceType)
 208:         prop.ChoiceType = choiceType;
 209:  
 210:  
 211:     foreach (XmlElement choiceXml in propXml.SelectNodes("ChoiceList/Choice"))
 212:     {
 213:         if (prop.ChoiceList.FindTerms(choiceXml.InnerText, ChoiceListSearchOption.ExactMatch).Length == 0)
 214:         {
 215:             prop.ChoiceList.Add(choiceXml.InnerText);
 216:             // Settng choice list values doesn't mark the property as dirty so we have to manually mark it using reflection.
 217:             Utilities.SetFieldValue(prop, typeof(Property), "m_fIsChanged", true);
 218:         }
 219:     }
 220: }
 221:  
 222: /// <summary>
 223: /// Sets the data mapping.
 224: /// </summary>
 225: /// <param name="propXml">The prop XML.</param>
 226: /// <param name="prop">The prop.</param>
 227: /// <param name="manager">The manager.</param>
 228: private static void SetDataMapping(XmlElement propXml, Property prop, UserProfileConfigManager manager)
 229: {
 230:     PropertyMapCollection propertyMapping = manager.GetDataSource().PropertyMapping;
 231:     if (propXml.SelectSingleNode("ImportMapping") != null && propXml.SelectSingleNode("ImportMapping").ChildNodes.Count > 0)
 232:     {
 233:         PropertyMap map = propertyMapping[prop.Name];
 234:         
 235:         string dsPropName = propXml.SelectSingleNode("ImportMapping/DSPropName").InnerText;
 236:         string connectionName = propXml.SelectSingleNode("ImportMapping/ConnectionName").InnerText;
 237:         string associationName = propXml.SelectSingleNode("ImportMapping/AssociationName").InnerText;
 238:  
 239:         // Remove any mappings that are assigned to the current mapping.
 240:         if (!string.IsNullOrEmpty(dsPropName))
 241:         {
 242:             foreach (PropertyMap tempMap in propertyMapping)
 243:             {
 244:                 if (tempMap.DSPropName == dsPropName && 
 245:                     tempMap.ConnectionName == connectionName && 
 246:                     tempMap.AssociationName == associationName && 
 247:                     tempMap.PropName != prop.Name)
 248:                 {
 249:                     Log("Progress: Removing mapping associated with '{0}' so that it may be assigned to '{1}'.",
 250:                         tempMap.PropName, prop.Name);
 251:                     propertyMapping.Remove(tempMap.PropName);
 252:                     propertyMapping = manager.GetDataSource().PropertyMapping;
 253:                     break;
 254:                 }
 255:             }
 256:         }
 257:  
 258:         if (map == null)
 259:         {
 260:             Log("Progress: Adding import mapping to '{0}'.", prop.Name);
 261:             propertyMapping.Add(prop.Name, dsPropName, connectionName, associationName);
 262:         }
 263:         else
 264:         {
 265:             Log("Progress: Updating import mapping for '{0}'.", prop.Name);
 266:             if (map.DSPropName != dsPropName)
 267:                 map.DSPropName = dsPropName;
 268:  
 269:             if (map.ConnectionName != connectionName)
 270:                 map.ConnectionName = connectionName;
 271:  
 272:             if (map.AssociationName != associationName)
 273:                 map.AssociationName = associationName;
 274:  
 275:             map.Commit();
 276:         }
 277:     }
 278:     else if (propertyMapping[prop.Name] != null)
 279:     {
 280:         Log("Progress: Removing import mapping for '{0}'.", prop.Name);
 281:         propertyMapping.Remove(prop.Name);
 282:     }
 283: }

So I mentioned that there was one caveat that I discovered - turns out that it's more of a bug.  If you do an export and then immediately import using this command you will likely get an exception when it comes to importing the SPS-ProxyAddresses field.  Here's the specific exception you will see:

Progress: Setting properties for 'SPS-ProxyAddresses'.
Progress: Updating import mapping for 'SPS-ProxyAddresses'.
ERROR: The character length specified is invalid.
   at Microsoft.Office.Server.UserProfiles.Property._TypeValidate(DBAction action)
   at Microsoft.Office.Server.UserProfiles.Property._TypeValidate(IEnumerable enumAddPropertyList, IEnumerable enumUpdatePropertyList)
   at Microsoft.Office.Server.UserProfiles.Property._Update(SRPSite site, IEnumerable enumAddPropertyList, IEnumerable enumUpdatePropertyList, IEnumerable enumRemovePropertyList)
   at Microsoft.Office.Server.UserProfiles.Property.Commit()
   at Lapointe.SharePoint.STSADM.Commands.UserProfiles.ImportProfileProperties.Execute(String command, StringDictionary keyValues, String& output)

So what's the deal with this?  Simple - it's a bug.  Here's another way you can expose the bug - via the browser go to your user profile properties page and then click to edit the SPS-ProxyAddresses field.  Then, on the edit screen, simply click the "OK" button without making any changes - you'll get the following error:

clip_image002

The problem is that this particular field is a multi-value field which can only have a maximum length of 400 but the field length is set to 2048 and because it's a built-in system field we can't change the length to fix it and therefore we can't change anything about the property.

So, to deal with this I made it so that my code simply dumps out errors like this and moves on to the next item - but you will see this error every time you attempt an import - there's no way around it (you can't change the length of an existing field so you can't fix the problem to prevent the error).

The help for the command is shown below:

C:\>stsadm -help gl-importprofileproperties

stsadm -o gl-importprofileproperties


Imports user profile properties using the output of gl-enumprofileproperties.

Parameters:
        -inputfile <file to import results from>
        [-sspname <name of the SSP>]
        [-removemissing (removes properties missing from the import)

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-importprofileproperties MOSS 2007 Released: 8/14/2008
Updated: 8/22/2008

Parameter Name Short Form Required Description Example Usage
sspname ssp No The name of the SSP that the profile properties are associated with.  If omitted the default SSP will be used. -sspname SSP1

-ssp SSP1
inputfile input Yes The path to the XML file to use for import.  The XML must correspond to the XML generated by the gl-enumprofileproperties command. -inputfile c:\properties.xml

-input c:\properties.xml
removemissing rm No Deletes any properties that were not defined in the import file.  It's recommended that if you use this flag that you first backup the existing properties using the gl-enumprofileproperties command. -removemissing

-rm

The following is an example of how to import user profile properties:

stsadm -o gl-importprofileproperties -inputfile c:\properties.xml

Update 8/22/2008: I fixed a few bugs that were preventing the importing of new properties.  I also fixed it so that data mappings can be reassigned without having to run multiple times.  I also added support for setting the display order and for removing properties that do not exist in the import by passing in the -removemissing parameter.

12 comments:

Cody said...

Gary- As we have moved into production we have found more and more use for your commands. Thanks.

Does this command also order the properties the same way they were in the source SSP?

Thanks!

Cody said...

Gary- I decided to just try the command out in a dev environment to check the order, but as I went to import after running the enum command I got the following error on the first "new" property:

Progress: Creating property 'Initial'
ERROR: Object reference not set to an instance of an object.
at Microsoft.Office.Server.UserProfiles.Property._ActionValidate(DBAction action)
at Microsft.Office.Server.UserProfiles.PropertyCollection.Add(Property property)
at Lapoint.SharePoint.STSADM.Commands.UserProfiles.ImportProfileProperties.Import(String sspName, String input)
Import finished at 8/22/2008 2:25:00 PM
Operation completed successfully.

Of course there were many more properties, but it stopped on this one. We are mapping this new property to the AD "initials" attribute.

Any ideas? Thanks.

Gary Lapointe said...

I probably did something stupid :) - can you send me the XML for the property that failed?

Gary Lapointe said...

Forgot to mention - no, I'm not currently addressing the ordering - just didn't have time to deal with it.

Anonymous said...

Unfortunately, this doesn't also do mapping to AD.

Gary Lapointe said...

Actually, it should be handling the AD mapping - at least it worked for me when I tested it. Also - per an earlier comment - it does now support ordering.

Jeremy Thake said...

Mate great work, as usual...just one thing...I've got your latest download and noticed that I get this error:

ERROR: The ChoiceType field cannot be changed.
at Microsoft.Office.Server.UserProfiles.Property.set_ChoiceType(ChoiceTypes va
lue)
at Lapointe.SharePoint.STSADM.Commands.UserProfiles.ImportProfileProperties.Se
tProperties(Property prop, XmlElement propXml, Boolean isNew, UserProfileConfigMa
nager manager)
at Lapointe.SharePoint.STSADM.Commands.UserProfiles.ImportProfileProperties.Im
port(String sspName, String input, Boolean removeMissing)


The reason I get this is because on an update you are trying to set this which you can't. Same goes for:
Name
Type
Length
ChoiceType
IsMultiValued

Be great to get an update of this ;-)

Cheers,
Jeremy
sharepointdevwiki.com

Gary Lapointe said...

Jeremy - thanks for the info - technically ChoiceType is only partially read-only - it depends on the value you specify. I've modified my code to better account for this. I was already handling the other field variables (which are read-only on an update but not for a new property). I'll try to get my updated code out tonight.

Gwen said...

It's a very good code, I save 5 hours of boring runbooks procedure (reorder, change name, link to AD fields, ...) with this command!

Thank you very much! Can we hope a section import for next releases?

Guy K said...

Gary,

We ran importprofileproperties and specified the shared services name. It seems to run fine, the properties are added. When I run enumprofileproperties and export, the exported file does not reflect the changes. The bottom line is the Profile Import Process will not import the BDC data. We checked the privilages on the user runing the import and it seems fine. Any ideas?

Gary Lapointe said...

Guy - I've not done any testing with this when using the BDC as a data source so honestly I'm not sure what the issue is.

Laks said...

Hello Gary

I have read your post and it was very useful. I am trying to work on a similiar requirement in MOSS 2010.

I kindly request you to provide valuable comments on adding choice list for a new user profile property in MOSS 2010. Since we dont have the option to add the choice list property through the central admin, I am not able to assign the this specific type for the new property.

Please Let me know, Thanks !

-Laks