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, September 18, 2008

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 :)