Set User Profile Picture URL Property using STSADM (revisited)
A little over a year ago I created one of my first STSADM commands, gl-setpictureurlnewpath, which I developed in order to enable me to set the picture URL property for user profile objects to a new path as the result of the upgrade which resulted in the images going to a new library. This time I needed to do set the property but there was no existing data so the existing command I had wouldn't work as is. I considered reworking the command to accommodate both scenarios but in the end decided to just leave the existing one alone and create a new command which I called gl-setpictureurl.
Updating a user profile object through code is actually really simple - you just get an instance of the UserProfileManager object and then either loop through the items in the collection or use the GetUserProfile method to retrieve a specific UserProfile object. Once you have the object you can edit any of the properties using simple indexer notation (userProfile["PictureURL"].Value = url). Once you've updated the appropriate properties you call the Commit() method on the user profile object.
1: #if MOSS
2: using System;
3: using System.Collections.Specialized;
4: using System.Text;
5: using Lapointe.SharePoint.STSADM.Commands.SPValidators;
6: using Microsoft.Office.Server;
7: using Microsoft.Office.Server.UserProfiles;
8: using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
9: using Microsoft.SharePoint;
10: using System.Net;
11:
12: namespace Lapointe.SharePoint.STSADM.Commands.UserProfiles
13: {
14: public class SetPictureUrl : SPOperation
15: {
16: /// <summary>
17: /// Initializes a new instance of the <see cref="SetPictureUrl"/> class.
18: /// </summary>
19: public SetPictureUrl()
20: {
21: SPParamCollection parameters = new SPParamCollection();
22: parameters.Add(new SPParam("path", "p", true, null, new SPNullOrNonEmptyValidator(), "Please specify the path."));
23: parameters.Add(new SPParam("sspname", "ssp", false, null, new SPNonEmptyValidator(), "Please specify the SSP name."));
24: parameters.Add(new SPParam("username", "u", false, null, new SPNonEmptyValidator(), "Please specify the username."));
25: parameters.Add(new SPParam("overwrite", "ow"));
26: parameters.Add(new SPParam("ignoremissingdata", "ignore"));
27: parameters.Add(new SPParam("validateurl", "validate"));
28:
29: StringBuilder sb = new StringBuilder();
30: sb.Append("\r\n\r\nSets the picture URL path for user profiles. The following variables may be used for dynamic replacement: \"$(username)\", \"$(domain)\", \"$(email)\", \"$(firstname)\", \"$(lastname)\", \"$(employeeid)\".\r\n\r\nParameters:");
31: sb.Append("\r\n\t-path <path to new photo (i.e., \"http://intranet/hr/EmployeePictures/$(username).jpg\") - leave blank to clear>");
32: sb.Append("\r\n\t[-sspname <name of the SSP>]");
33: sb.Append("\r\n\t[-username <DOMAIN\\name>]");
34: sb.Append("\r\n\t[-overwrite]");
35: sb.Append("\r\n\t[-ignoremissingdata]");
36: sb.Append("\r\n\t[-validateurl]");
37:
38: Init(parameters, sb.ToString());
39: }
40: #region ISPStsadmCommand Members
41:
42: /// <summary>
43: /// Gets the help message.
44: /// </summary>
45: /// <param name="command">The command.</param>
46: /// <returns></returns>
47: public override string GetHelpMessage(string command)
48: {
49: return HelpMessage;
50: }
51:
52: /// <summary>
53: /// Runs the specified command.
54: /// </summary>
55: /// <param name="command">The command.</param>
56: /// <param name="keyValues">The key values.</param>
57: /// <param name="output">The output.</param>
58: /// <returns></returns>
59: public override int Execute(string command, StringDictionary keyValues, out string output)
60: {
61: output = string.Empty;
62: Verbose = true;
63:
64: string username = null;
65:
66: if (Params["username"].UserTypedIn)
67: username = Params["username"].Value;
68: string path = Params["path"].Value;
69:
70: ServerContext context = ServerContext.Default;
71: if (Params["sspname"].UserTypedIn)
72: context = ServerContext.GetContext(Params["sspname"].Value);
73:
74: bool overwrite = Params["overwrite"].UserTypedIn;
75: bool ignoreMissingData = Params["ignoremissingdata"].UserTypedIn;
76: bool validateUrl = Params["validateurl"].UserTypedIn;
77:
78: UserProfileManager profManager = new UserProfileManager(context);
79:
80: if (string.IsNullOrEmpty(username))
81: SetPictures(profManager, path, overwrite, ignoreMissingData, validateUrl);
82: else
83: SetPicture(profManager, username, path, overwrite, ignoreMissingData, validateUrl);
84:
85: return OUTPUT_SUCCESS;
86: }
87:
88: #endregion
89:
90: /// <summary>
91: /// Sets the pictures to the specified path for all user profiles.
92: /// </summary>
93: /// <param name="profManager">The prof manager.</param>
94: /// <param name="path">The path.</param>
95: /// <param name="overwrite">if set to <c>true</c> [overwrite].</param>
96: /// <param name="ignoreMissingData">if set to <c>true</c> [ignore missing data].</param>
97: /// <param name="validateUrl">if set to <c>true</c> validate the url.</param>
98: public static void SetPictures(UserProfileManager profManager, string path, bool overwrite, bool ignoreMissingData, bool validateUrl)
99: {
100: foreach (UserProfile profile in profManager)
101: {
102: SetPicture(profile, path, overwrite, ignoreMissingData, validateUrl);
103: }
104: }
105:
106: /// <summary>
107: /// Sets the picture URL for the specfied user.
108: /// </summary>
109: /// <param name="profManager">The prof manager.</param>
110: /// <param name="username">The username.</param>
111: /// <param name="path">The path.</param>
112: /// <param name="overwrite">if set to <c>true</c> [overwrite].</param>
113: /// <param name="ignoreMissingData">if set to <c>true</c> [ignore missing data].</param>
114: /// <param name="validateUrl">if set to <c>true</c> validate the url.</param>
115: public static void SetPicture(UserProfileManager profManager, string username, string path, bool overwrite, bool ignoreMissingData, bool validateUrl)
116: {
117: if (!string.IsNullOrEmpty(username))
118: {
119: if (!profManager.UserExists(username))
120: {
121: throw new SPException("The username specified cannot be found.");
122: }
123: UserProfile profile = profManager.GetUserProfile(username);
124: SetPicture(profile, path, overwrite, ignoreMissingData, validateUrl);
125: }
126: else
127: throw new ArgumentNullException("username", "The username parameter cannot be null or empty.");
128: }
129:
130: /// <summary>
131: /// Sets the picture.
132: /// </summary>
133: /// <param name="up">Up.</param>
134: /// <param name="path">The path.</param>
135: /// <param name="overwrite">if set to <c>true</c> [overwrite].</param>
136: /// <param name="ignoreMissingData">if set to <c>true</c> [ignore missing data].</param>
137: /// <param name="validateUrl">if set to <c>true</c> validate the url.</param>
138: public static void SetPicture(UserProfile up, string path, bool overwrite, bool ignoreMissingData, bool validateUrl)
139: {
140: if (up["PictureURL"].Value != null && !string.IsNullOrEmpty(up["PictureURL"].Value.ToString()) && !overwrite)
141: {
142: Log("\"{0}\" already contains a picture URL. Specify -overwrite to replace existing settings.",
143: up["AccountName"].Value.ToString());
144: return;
145: }
146: if (string.IsNullOrEmpty(path))
147: {
148: path = string.Empty;
149: }
150: else
151: {
152: if (path.Contains("$(username)"))
153: {
154: if (up["UserName"] != null && up["UserName"].Value != null)
155: path = path.Replace("$(username)", up["UserName"].Value.ToString());
156: else
157: {
158: if (up["AccountName"] != null && up["AccountName"].Value != null)
159: path = path.Replace("$(username)", up["AccountName"].Value.ToString().Split('\\')[1]);
160: else
161: {
162: if (!ignoreMissingData)
163: throw new ArgumentException(string.Format("Unable to determine username from existing profile data ({0}).", up.ID));
164: return;
165: }
166: }
167: }
168:
169: if (path.Contains("$(domain)"))
170: {
171: if (up["AccountName"] != null && up["AccountName"].Value != null)
172: path = path.Replace("$(domain)", up["AccountName"].Value.ToString().Split('\\')[0]);
173: else
174: {
175: if (!ignoreMissingData)
176: throw new ArgumentException(string.Format("Unable to determine domain from existing profile data ({0}).", up.ID));
177: return;
178: }
179: }
180:
181: if (path.Contains("$(email)"))
182: {
183: if (up["WorkEmail"] != null && up["WorkEmail"].Value != null)
184: path = path.Replace("$(email)", up["WorkEmail"].Value.ToString());
185: else
186: {
187: if (!ignoreMissingData)
188: throw new ArgumentException(string.Format("Unable to determine email from existing profile data ({0}).", up.ID));
189: return;
190: }
191: }
192:
193: if (path.Contains("$(firstname)"))
194: {
195: if (up["FirstName"] != null && up["FirstName"].Value != null)
196: path = path.Replace("$(firstname)", up["FirstName"].Value.ToString());
197: else
198: {
199: if (!ignoreMissingData)
200: throw new ArgumentException(string.Format("Unable to determine first name from existing profile data ({0}).", up.ID));
201: return;
202: }
203: }
204:
205: if (path.Contains("$(lastname)"))
206: {
207: if (up["LastName"] != null && up["LastName"].Value != null)
208: path = path.Replace("$(lastname)", up["LastName"].Value.ToString());
209: else
210: {
211: if (!ignoreMissingData)
212: throw new ArgumentException(string.Format("Unable to determine lastname from existing profile data ({0}).", up.ID));
213: return;
214: }
215: }
216:
217: if (path.Contains("$(employeeid)"))
218: {
219: if (up["EmployeeID"] != null && up["EmployeeID"].Value != null)
220: {
221: path = path.Replace("$(employeeid)", up["EmployeeID"].Value.ToString());
222: }
223: else
224: {
225: if (!ignoreMissingData)
226: throw new ArgumentException(string.Format("Unable to determine Employee ID from existing profile data ({0}).", up.ID));
227: return;
228: }
229: }
230: }
231:
232: if (validateUrl)
233: {
234: Log("Validating URL \"{0}\" for \"{1}\".", path, up["AccountName"].Value.ToString());
235:
236: //Create a request for the URL.
237: HttpWebRequest request = (HttpWebRequest)WebRequest.Create(path);
238: request.AllowAutoRedirect = false;
239: request.Credentials = CredentialCache.DefaultCredentials;
240: HttpWebResponse serverResponse = (HttpWebResponse)request.GetResponse();
241: if (serverResponse.StatusCode != HttpStatusCode.OK)
242: {
243: Log("Unable to find picture. Setting PictureURL property to empty string.");
244: path = string.Empty;
245: }
246: serverResponse.Close();
247: }
248:
249: Log("Setting picture for \"{0}\" to \"{1}\".", up["AccountName"].Value.ToString(), path);
250:
251: up["PictureURL"].Value = path;
252: up.Commit();
253: }
254:
255: }
256: }
257: #endif
The help for the command is shown below:
C:\>stsadm -help gl-setpictureurl
stsadm -o gl-setpictureurl
Sets the picture URL path for user profiles. The following variables may be used for dynamic replacement: "$(username)"
, "$(domain)", "$(email)", "$(firstname)", "$(lastname)", "$(employeeid)".
Parameters:
-path <path to new photo (i.e., "http://intranet/hr/EmployeePictures/$(username).jpg") - leave blank to clear>
[-sspname <name of the SSP>]
[-username <DOMAIN\name>]
[-overwrite]
[-ignoremissingdata]
[-validateurl]
|
The following table summarizes the command and its various parameters:
| Command Name | Availability | Build Date |
|---|---|---|
| gl-setpictureurl | MOSS 2007 | Released: 9/18/2008
Updated: 4/17/2009 |
| Parameter Name | Short Form | Required | Description | Example Usage |
|---|---|---|---|---|
| path | p | Yes | The path to the images. To substitute dynamic data use the following strings variables within the path: $(username), $(domain), $(email), $(firstname), $(lastname), $(employeeid). The variable names are case sensitive. | -path "http://intranet/hr/EmployeePictures/$(username).jpg"
-p "http://intranet/hr/EmployeePictures/$(username).jpg" |
| sspname | ssp | No | The name of the SSP. If not specified then the default SSP will be used. | -sspname SSP1
-ssp SSP1 |
| username | u | No | The account name associated with the specific user profile to update. If omitted then all user profiles will be updated. | -username "domain\user"
-u "domain\user" |
| overwrite | ow | No | If provided then existing data values will be overwritten. If omitted then any profile objects with existing data will be ignored. | -overwrite
-ow |
| ignoremissingdata | ignore | No | If specified then do not error if a specified variable value cannot be found. Note that if the value is not found then the property value will not be set. | -ignoremissingdata
-ignore |
| validateurl | validate | No | If specified then perform a web request to see if the resultant URL is valid. If the result is not valid then the property value will be set to an empty string. | -validaturl
-validate |
The following is an example of how to set the picture URL property of all user profiles:
stsadm -o gl-setpictureurl -path "http://intranet/hr/EmployeePictures/$(username).jpg" -overwrite
Updated 4/17/2009: The command has been updated to include the employeeid variable as well as the ignoremissingdata and the validateurl parameters. Thanks to Tim Griepp for providing some code that I used to motivate myself to look at this command again
Set Picture URL Property
Within our SPS 2003 environment we stored employee pictures in a picture library. We then set the users Picture URL profile property to point to their picture in this library. The problem was that as soon as we upgraded the paths to these pictures no longer worked. Our pictures were previously found under the path "http://intranet/HumanResources/EmployeePictures/" but after the upgrade the path changed to http://intranet/Topics/Divisions/HumanResources/EmployeePictures/.
So we needed a way to reset the path for all the users in the profile database to reflect this new path (note that in the end we intend to move the photos to a different location as the Topics and Divisions sites that were created will be dumped). My solution was a fairly simple command, called gl-setpictureurlnewpath, which takes in the path to the new picture library and resets all profile users PictureUrl property to reflect this new path. I assume that all the filenames are not changing and that all the photos are in this single library (if your environment has users with photos in different libraries then you'll want to modify this code to take in another variable to help you determine which ones to change). The core code that does this is shown below:
1: public int Run(string command, StringDictionary keyValues, out string output)
2: {
3: ...
4: UserProfileManager profManager = new UserProfileManager(ServerContext.GetContext(sspname));
5: foreach (UserProfile profile in profManager)
6: {
7: if (SetPicture(profile["PictureURL"], librarypath))
8: profile.Commit();
9: }
10: ...
11: }
12: private static bool SetPicture(UserProfileValueCollection val, string libraryPath)
13: {
14: string currentUrl;
15: if (val.Value == null)
16: return false;
17: else
18: currentUrl = val.Value.ToString();
19:
20: if (string.IsNullOrEmpty(currentUrl))
21: return false;
22:
23: string newUrl = libraryPath.TrimEnd('/') + "/";
24: if (currentUrl.IndexOf('/') < 0)
25: newUrl += currentUrl.Trim();
26: else
27: {
28: string file = currentUrl.Substring(currentUrl.LastIndexOf('/'), currentUrl.Length - currentUrl.LastIndexOf('/'));
29: newUrl += file.Trim('/');
30: }
31: val.Value = newUrl;
32:
33: return true;
34: }
The syntax of the command I created to do this can be seen below.
C:\>stsadm -help gl-setpictureurlnewpath
stsadm -o gl-setpictureurlnewpath
Fixes the pathing for all user photos or a single user's photo as the result of moving a picture library (assumes file names have not changed).
Parameters:
-sspname <name of the 2007 SSP>
-librarypath <path to new photo library (i.e., "http://intranet/HumanResources/Employee%20Pictures/")>
[-username <NT account name>]
Here’s an example of how to change the picture url path for all users:
stsadm –o gl-setpictureurlnewpath –sspname “SSP1” –librarypath "http://intranet/hr/EmployeePictures"
Check out the books I've contributed to at Amazon.com: