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
  2using System;
  3using System.Collections.Specialized;
  4using System.Text;
  5using Lapointe.SharePoint.STSADM.Commands.SPValidators;
  6using Microsoft.Office.Server;
  7using Microsoft.Office.Server.UserProfiles;
  8using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
  9using Microsoft.SharePoint;
 10using System.Net;
 11 
 12namespace 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 NameAvailabilityBuild Date
gl-setpictureurlMOSS 2007Released: 9/18/2008
Parameter NameShort FormRequiredDescriptionExample Usage
pathpYesThe 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"
sspnamesspNoThe name of the SSP. If not specified then the default SSP will be used.-sspname SSP1, -ssp SSP1
usernameuNoThe 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"
overwriteowNoIf provided then existing data values will be overwritten. If omitted then any profile objects with existing data will be ignored.-overwrite, -ow
ignoremissingdataignoreNoIf 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
validateurlvalidateNoIf 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 🙂