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.

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:

 1public 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 
38private 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):

 1<Properties ssp="SSP1">
 2  ...
 3  <Property>
 4    <Name>CollegeMajor</Name>
 5    <AllowPolicyOverride>True</AllowPolicyOverride>
 6    <ChoiceList />
 7    <ChoiceType>Off</ChoiceType>
 8    <DefaultPrivacy>Public</DefaultPrivacy>
 9    <Description />
10    <DisplayName>College Major</DisplayName>
11    <DisplayOrder>5202</DisplayOrder>
12    <IsAdminEditable>True</IsAdminEditable>
13    <IsAlias>False</IsAlias>
14    <IsColleagueEventLog>False</IsColleagueEventLog>
15    <IsImported>False</IsImported>
16    <IsMultivalued>False</IsMultivalued>
17    <IsReplicable>False</IsReplicable>
18    <IsRequired>False</IsRequired>
19    <IsSearchable>True</IsSearchable>
20    <IsSection>False</IsSection>
21    <IsSystem>False</IsSystem>
22    <IsUpgrade>False</IsUpgrade>
23    <IsUpgradePrivate>False</IsUpgradePrivate>
24    <IsUserEditable>True</IsUserEditable>
25    <IsVisibleOnEditor>True</IsVisibleOnEditor>
26    <IsVisibleOnViewer>False</IsVisibleOnViewer>
27    <Length>255</Length>
28    <ManagedPropertyName>CollegeMajor</ManagedPropertyName>
29    <MaximumShown>10</MaximumShown>
30    <PrivacyPolicy>OptIn</PrivacyPolicy>
31    <Separator>Comma</Separator>
32    <Type>string</Type>
33    <URI>urn:schemas-microsoft-com:sharepoint:portal:profile:CollegeMajor</URI>
34    <UserOverridePrivacy>False</UserOverridePrivacy>
35    <ImportMapping />
36  </Property>
37  ...
38</Properties>

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.

  1public 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).