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:
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.
13 thoughts on “Importing Profile Properties”
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!
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.
I probably did something stupid 🙂 – can you send me the XML for the property that failed?
Forgot to mention – no, I’m not currently addressing the ordering – just didn’t have time to deal with it.
Unfortunately, this doesn’t also do mapping to AD.
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.
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
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.
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?
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?
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.
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
Comments are closed.