SharePoint
Server 2010 is a great platform for storing information about the users
in an organization. Using the User Profile Service application and its
related components, you can configure SharePoint to store a variety of
information about users and synchronize that information with external
storage, such as Active Directory (AD) or any other data source to which
you can connect using Business Connectivity Services (BCS).
Users can then edit their information using the user profile
pages. The external storage sources will then be updated automatically
with this new information, thereby making the information more relevant
and timely throughout the organization.
However, allowing users to update their
own information introduces new problems when it comes to governance,
particularly in terms of security of information,
privacy, and ethical conduct. Fortunately, one component of the User
Profile Service application is the User Profile Change Service.
The User Profile Change Service isn’t a
true service but rather a series of tables within the User Profile
Service’s profile database and a timer job (i.e., User Profile Change
Cleanup Job) that’s used to clear stored changes older than 14 days.
By default, the User Profile Change
Cleanup Job runs once a day. You can adjust this frequency by editing
the schedule for this timer job.
You can use the User Profile Change
Service to view and report on changes. Unfortunately, however, there
isn’t a UI to view these changes, so you must use the SharePoint API to
query the system to view changes.
But what kind of changes can you query
the system for? The answer is simple: all user profile properties.
You can see the properties that are
available by navigating to the Manage Service Applications page in the
Central Administration site, selecting a User Profile Service
application, and clicking Manage to view the User Profile Management
page. (If a User Profile Service application doesn’t exist, you must
create one in order to store and manage user details.)
Click the Manage User Properties link to
see all the available properties. From here you can create, delete, or
edit any properties that you want your users to populate with data. When
a user edits any of the values for one of these properties, a change
record will be created.
However, it’s important to remember that
changes will persist for only 14 days, assuming the User Profile Change
Cleanup Job is running. If you need to keep these changes longer, you
can change the retention period by running a simple STSADM command.
(There’s no Windows PowerShell equivalent way to do this.) The following
example changes the retention period to 28 days:
stsadm -o profilechangelog
-userprofileapplication "My User Profile Service App"
-daysofhistory 28
-userprofileapplication "My User Profile Service App"
-daysofhistory 28
(Although the command wraps here, you'd enter it all on one line. The same holds true for the other commands that wrap.)
You can gain access to these change
records using the objects in the Microsoft.Office.Server.UserProfiles
namespace, which is part of the Microsoft.Office.Server.UserProfiles.dll
assembly.
Specifically, you use the
UserProfileManager, UserProfileChangeQuery, and UserProfileChangeToken
classes.
Retrieving User Profile Changes
To retrieve user profile changes, you
must first create an instance of the UserProfileManager class. This
class is the gateway for all programmatic manipulations of the user
profile properties and their properties.
To create an instance of this class, you
must provide it with a Microsoft.SharePoint.SPServiceContext object.
This context object makes sure that you’re working with the correct User
Profile Service application.
There are two ways to retrieve the SPServiceContext object:
- You can provide a specific service application proxy object and a site subscription identifier. (Site subscriptions are only relevant in multi-tenancy scenarios. Most users can use the default site subscription, which is essentially just an empty globally unique identifier—GUID.)
- You can provide a site collection that’s associated with the service application.
Listing 1 at the end of this article
demonstrates the first approach using PowerShell. Alternatively, you
could use code in which the URL to the site collection is passed to the
GetContext method. Either approach is perfectly acceptable.
Now that you have the SPServiceContext object, you can create the UserProfileManager object:
$manager = New-Object Microsoft.Office.Server.
UserProfiles.UserProfileManager $context
UserProfiles.UserProfileManager $context
The UserProfileManager class contains the GetChanges method. This method has three overloads:
- GetChanges(), which retrieves all changes
- GetChanges(UserProfileChangeToken), which retrieves all changes from a given date or event
- GetChanges(ProfileBaseChangeQuery), which retrieves specific changes given a query object
All the GetChanges overloads return a
UserProfileChangeCollection object. This object contains all the changes
stored as either a UserProfileChange object or one of its derivative
types:
- UserProfileColleagueChange
- UserProfileCustomChange
- UserProfileLinkItemChange
- UserProfileMembershipChange
- UserProfileOrganizationMembershipChange
- UserProfilePropertyValueChange
- UserProfileWebLogChange
Each object type stores the resultant
change in a manner most appropriate for the given type. It’s important
to note that a separate object will be returned for every change.
So, if a user edits two profile
properties, you’ll have at least two objects (one for each property). As
the phrase “at least” implies, you could have more than two objects.
For example, if the user edits the Skills
property by adding several skills, a separate change object will be
returned for each skill added (i.e., one object for each value entered).
Using the GetChanges Method
Let’s look at examples of how to use each
overload for the GetChanges method. Figure 1 shows an example of
running the GetChanges() method without any parameters.
Running the method in this manner will
return all changes in the log, which can be quite numerous.
Figure 1: Example of the GetChanges() method
You can reduce the number of changes
returned by passing in a UserProfileChangeToken object to the GetChanges
method. This object lets you retrieve changes starting from a specific
date.
To do so, you simply specify that date in the change token.
You can also retrieve all the changes
created after a certain change event. In this case, you need to specify
an arbitrary date and the change event’s ID because there’s no
constructor available that accepts only an event ID. Only the event ID
will be used; the date will be ignored.
Figure 2 shows an example of running the
GetChanges method when you pass in a UserProfileChangeToken object with
both the event ID (value of 32) and date ($date, whose value is
1/31/2011) set.
Alternatively, you could provide a value of [System.DateTime]::Now
for the $date variable, as the value itself is irrelevant when the
event ID is provided. (Note that the UserProfileChangeToken object
values can only be set by initializing the object through its
constructors.)
Figure 2: Example of the GetChanges(UserProfileChangeToken) method
Filtering in this manner, however, can
still result in a large number of records returned, particularly if
you’re only interested in a specific type of change. To address this
concern, you can use the third GetChanges overload and pass in a
UserProfileChangeQuery object.
This object gives you the ability to
specify exactly what type of changes you want returned. For example,
perhaps you only want to know when single-valued properties (such as the
AboutMe property) are added or updated. Using the
UserProfileChangeQuery object, you can specify what property types and
change types you care about.
The UserProfileChangeQuery object’s
constructor lets you quickly set all the filtering properties to false.
You can then enable just the properties of interest by setting them to
true.
For example, the code in Figure 3 sets all
the properties to false when it initializes the UserProfileChangeQuery
object, then sets the SingleValueProperty, Add, and Update properties to
True.
Figure 3: Example of the GetChanges(ProfileBaseChangeQuery) method
As you can see, retrieving user profile
changes is fairly simple, requiring knowledge of only a few classes and
their various members. With PowerShell, you can easily format the
returned data a variety of ways. For example, Figure 4 shows Figure 3’s
query results formatted into tables and grouped by account.
Figure 4: Example of formatted query results
Empowering Privileged Users to Monitor Changes
Using PowerShell to retrieve information
about user changes can be extraordinarily powerful and useful to
administrators. But to enforce governance policies, you must bring the
ability to monitor changes to privileged users (e.g., HR staff members)
and give them the responsibility and authority to properly act on this
data.
Let’s examine a real-world example that accomplishes the goal of empowering privileged users.
Suppose that an organization’s HR
department has employee policies set in place that govern the
communications of employees for email and other types of communication.
These policies are extended to the communications in employees’ personal
sites and their user profile information.
The HR department wants to monitor updates to visible fields like About me
to ensure that the employees are complying with the policies. Further,
it wants to have the freedom to review the changes from a central
location and receive an alert when a new change has been posted to the
change log.
The HR department’s requirements are easy
to achieve if you consider that once you’ve gathered the changes and
stored them in a list that HR staff members have access to, they can
manage the information and set alerts using the list’s functionality.
The only customizations you need to write are:
- A SharePoint timer job that queries the log on a regular basis and adds the query results to the list
- An administrative UI to manage the timer job
In this example, the list resides at the
root of the My Site host, but the list could reside anywhere in a real
implementation.
Creating the Timer Job
Let’s walk through the code that creates
the SharePoint timer job to see how it works. This example is based on
the Microsoft article “Creating Custom Timer Jobs in Windows SharePoint Services 3.0”.
The timer job begins by creating an
instance of the UserProfileWatcherTimerJob class, which inherits from
the SPJobDefinition class.
The SPJobDefinition class has a property
bag that lets you provide properties to the Execute method for
processing. In this case, the SubmitJob method is used to pass the
userProfileFields and mySiteHost properties to the timer job instance
and store them in the property bag, as you can see in the code that
Listing 2 at the end of this article shows.
The Execute method is called when the
timer job runs. As seen in Listing 3 at the end of this article, the
Execute method begins by reading the values of the timer job’s
properties. Next, it creates an instance of the worker class,
UserProfileWatcherWorker, passing in those values.
UserProfileWatcherWorker then provides
two methods, which the Execute method uses to query the user profile
change log and update the target list when changes matching the query
are found.
UserProfileWatcherWorker is the workhorse
of the timer job, so let’s take a close look it. This worker class
first declares several private fields to keep track of the user profile
fields and the age of the changes. It also declares a list to store
UserProfilePropertyValueChange objects:
public class UserProfileWatcherWorker
{
private string _mySitesHost;
private int _changeAge = 30;
private string _profileFields;
private List<UserProfilePropertyValueChange> _changes =
new List<UserProfilePropertyValueChange>(); }
{
private string _mySitesHost;
private int _changeAge = 30;
private string _profileFields;
private List<UserProfilePropertyValueChange> _changes =
new List<UserProfilePropertyValueChange>(); }
Next, it initializes the values that will
be used to get the UserProfileManager object and construct the change
query:
public UserProfileWatcherWorker(string profileFields, int queryInterval, string mySitesHost)
{
_mySitesHost = mySitesHost;
_changeAge = queryInterval;
_profileFields = profileFields;
}
{
_mySitesHost = mySitesHost;
_changeAge = queryInterval;
_profileFields = profileFields;
}
Finally, the UserProfileWatcherWorker worker class provides two methods:
- RetrieveUserProfileChanges. When called by the Execute method, RetrieveUserProfileChanges queries the user profile change log. As shown in Listing 4 at the end of this article, the RetrieveUserProfileChanges method begins by getting a reference to the My Site host and SPServiceContext object, after which it constructs a change token and change query. Like the PowerShell code in Figure 3, it uses the change token and change query to call the GetChanges(ProfileBaseChangeQuery) overload, which performs the actual query. Finally, RetrieveUserProfileChanges evaluates the changes collection for the field stored in the _profileFields variable. If the field name is found, the UserProfilePropertyValueChange object (propertyChange) is stored in the list of changes.
- LogProfileChange. When called by the Execute method, LogProfileChange writes the list of changes to the list configured in the timer job properties. As shown in Listing 5 at the end of this article, the LogProfileChange method begins by getting a reference to the target list through a utility function that ensures that the list exists. For each item in the list, the user profile is evaluated. If it’s not null, the method writes a list item for each change in the list of changes. Finally, the list web and site references are disposed.
Creating the Administrative UI
Administration of the timer job is
achieved through the use of a custom administration page that Figure 5
shows. The administration page, which inherits from the OperationsPage
class, is added to the solution in an ADMIN mapped folder.
Figure 5: Administration page
Navigation to the new administration page
is achieved by adding an elements.xml file that contains the
CustomAction element code in Listing 6 at the end of this article. The
CustomAction element adds a new menu item—Manage Aptillon SharePoint
User Profile Watcher—to the Timer Jobs section of the Monitoring page,
as seen in Figure 6.
Figure 6: New link to the administration page
When the new menu item is clicked, the
administration page loads. During the loading process, the code shown in
Listing 7 at the end of this article looks for a previous instance of
the timer job and initializes the values of the page controls if a timer
job is found.
In the administration page, the
privileged user or administrator can choose to disable or enable the
timer job. When a person selects Disabled and clicks OK, the OnClick
method gets a reference to the SharePoint Timer Service and deletes all
previous versions of the timer job. This prevents multiple instances of
the timer job.
When the privileged user or administrator
selects Enabled, enters the necessary information, and clicks OK, the
administration page runs code that tests for the existence of the
specified site and creates a schedule object based on the specified
settings.
A new instance of the
UserProfileWatcherTimerJob is created and the SubmitJob method is called
to update the properties, which the code in Listing 8 at the end of
this article shows. The privileged user or administrator is then
redirected to the Monitoring page in Central Administration.
Testing the Sample
Testing the sample code requires you to have administrative access to your SharePoint farm.
After deploying the code, select the new
Manage Aptillon SharePoint User Profile Watcher menu item in the Timer
Jobs section of the Monitoring page in Central Administration.
On the administration page, enable the
timer job, make sure the Profile Fields value is AboutMe, and set the
time value to 1 minute for testing purposes. Supply the address of your
farm’s My Site host and click OK. You’ll be returned to the Monitoring
page in Central Administration.
Figure 7: Edit the user profile’s About me field
Open your profile page and choose Edit My Profile. Edit the About me field, which Figure 7 shows, choose Save, then click Close.
After the time set on the administration
page has passed, navigate to the My Site host and click View All Site
Content. There will be a new list named User Profile Changes. When you
view that list, you’ll see a copy of your change, as Figure 8 shows.
Figure 8: Review the change made to the About me field
A Powerful Tool
The user profile change log is a powerful
tool for monitoring change in your environment and can be used to help
enforce governance policies for your My Site deployment.
Although SharePoint doesn’t provide a UI
for the user profile change log, the SharePoint API provides great
functions for accessing and using it to monitor user activity. You can
also use PowerShell to access and monitor the change log.
Listing 1: Code to retrieve the SPServiceContext object
$app = Get-SPServiceApplication | ? {
$_.Name -eq "My User Profile Service App"
}
$siteSub = [Microsoft.SharePoint. ?
SPSiteSubscriptionIdentifier]::Default
$context = [Microsoft.SharePoint. ?
SPServiceContext]::GetContext( ?
$app.ServiceApplicationProxyGroup, $siteSub)
$_.Name -eq "My User Profile Service App"
}
$siteSub = [Microsoft.SharePoint. ?
SPSiteSubscriptionIdentifier]::Default
$context = [Microsoft.SharePoint. ?
SPServiceContext]::GetContext( ?
$app.ServiceApplicationProxyGroup, $siteSub)
Listing 2: SPJobDefinition code with properties added
public class UserProfileWatcherTimerJob : SPJobDefinition{
public UserProfileWatcherTimerJob() {}
/// <summary>
/// Initializes a new instance of the <see
cref="UserProfileWatcherTimerJob"/> class.
/// </summary>
public UserProfileWatcherTimerJob(SPService service)
: base(Constants.UserProfileWatcherJobName, service, null,
SPJobLockType.Job)
{
Title = Constants.UserProfileWatcherJobName;
}
/// <summary>
/// Submits the job.
/// </summary>
public void SubmitJob(string userProfileFields, string mySitesHost,
SPSchedule schedule)
{
Properties[Constants.PropertyKeyProfileFields] =
userProfileFields;
Properties[Constants.PropertyKeyMySitesHost] = mySitesHost;
Schedule = schedule;
Update();
}
public UserProfileWatcherTimerJob() {}
/// <summary>
/// Initializes a new instance of the <see
cref="UserProfileWatcherTimerJob"/> class.
/// </summary>
public UserProfileWatcherTimerJob(SPService service)
: base(Constants.UserProfileWatcherJobName, service, null,
SPJobLockType.Job)
{
Title = Constants.UserProfileWatcherJobName;
}
/// <summary>
/// Submits the job.
/// </summary>
public void SubmitJob(string userProfileFields, string mySitesHost,
SPSchedule schedule)
{
Properties[Constants.PropertyKeyProfileFields] =
userProfileFields;
Properties[Constants.PropertyKeyMySitesHost] = mySitesHost;
Schedule = schedule;
Update();
}
Listing 3: Execute method
public override void Execute(Guid targetInstanceId){
string profileFields = Constants.ProfileFields;
int queryInterval = 30;
string mySitesHost = String.Empty;
Debug.WriteLine("[Aptillon] User Profile Watcher Job: Begin
Execute.");
try
{
//Get the properties from the Timer Job
profileFields =
this.Properties[Constants.PropertyKeyProfileFields]
.ToString();
queryInterval = (this.Schedule as SPMinuteSchedule).Interval;
mySitesHost =
this.Properties[Constants.PropertyKeyMySitesHost]
.ToString();
Debug.WriteLine(String.Format("[Aptillon] User Profile Watcher
Job: Fields={0}, Interval={1}, MySites Host={2}",
profileFields, queryInterval, mySitesHost));
}
catch (Exception ex)
{
Debug.WriteLine("[Aptillon] User Profile Watcher Job:
Error gettting Job Properties");
Debug.WriteLine("[Aptillon] User Profile Watcher Job: " +
ex.Message);
}
//Create an instance of the watcher class
UserProfileWatcherWorker changes =
new UserProfileWatcherWorker(profileFields,
queryInterval, mySitesHost);
//Get the changes from the log
changes.RetrieveUserProfileChanges();
//Process the changes and add them to the list
changes.LogProfileChange();
}
}
string profileFields = Constants.ProfileFields;
int queryInterval = 30;
string mySitesHost = String.Empty;
Debug.WriteLine("[Aptillon] User Profile Watcher Job: Begin
Execute.");
try
{
//Get the properties from the Timer Job
profileFields =
this.Properties[Constants.PropertyKeyProfileFields]
.ToString();
queryInterval = (this.Schedule as SPMinuteSchedule).Interval;
mySitesHost =
this.Properties[Constants.PropertyKeyMySitesHost]
.ToString();
Debug.WriteLine(String.Format("[Aptillon] User Profile Watcher
Job: Fields={0}, Interval={1}, MySites Host={2}",
profileFields, queryInterval, mySitesHost));
}
catch (Exception ex)
{
Debug.WriteLine("[Aptillon] User Profile Watcher Job:
Error gettting Job Properties");
Debug.WriteLine("[Aptillon] User Profile Watcher Job: " +
ex.Message);
}
//Create an instance of the watcher class
UserProfileWatcherWorker changes =
new UserProfileWatcherWorker(profileFields,
queryInterval, mySitesHost);
//Get the changes from the log
changes.RetrieveUserProfileChanges();
//Process the changes and add them to the list
changes.LogProfileChange();
}
}
Listing 4: RetrieveUserProfileChanges method
internal void RetrieveUserProfileChanges(){
Debug.WriteLine("[Aptillon] User Profile Watcher Job: Starting Retrieve");
using (SPSite site = new SPSite(_mySitesHost))
{
SPServiceContext context = SPServiceContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
DateTime tokenStart = DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(_changeAge));
UserProfileChangeToken changeToken = new UserProfileChangeToken(tokenStart);
UserProfileChangeQuery changeQuery = new UserProfileChangeQuery(false, true);
changeQuery.ChangeTokenStart = changeToken;
changeQuery.UserProfile = true;
changeQuery.SingleValueProperty = true;
changeQuery.MultiValueProperty = true;
UserProfileChangeCollection changes = profileManager.GetChanges(changeQuery);
Debug.WriteLine(String.Format("[Aptillon] User Profile Watcher Job: found {0}
changes.", changes.Count));
foreach (UserProfileChange change in changes)
{
if (change is UserProfilePropertyValueChange)
{
UserProfilePropertyValueChange propertyChange =
(UserProfilePropertyValueChange)change;
if (_profileFields.Split(',').Contains(propertyChange.ProfileProperty.Name))
{
_changes.Add(propertyChange);
}
}
}
Debug.WriteLine("[Aptillon] User Profile Watcher Job: Done with Retrieve");
}
}
Debug.WriteLine("[Aptillon] User Profile Watcher Job: Starting Retrieve");
using (SPSite site = new SPSite(_mySitesHost))
{
SPServiceContext context = SPServiceContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
DateTime tokenStart = DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(_changeAge));
UserProfileChangeToken changeToken = new UserProfileChangeToken(tokenStart);
UserProfileChangeQuery changeQuery = new UserProfileChangeQuery(false, true);
changeQuery.ChangeTokenStart = changeToken;
changeQuery.UserProfile = true;
changeQuery.SingleValueProperty = true;
changeQuery.MultiValueProperty = true;
UserProfileChangeCollection changes = profileManager.GetChanges(changeQuery);
Debug.WriteLine(String.Format("[Aptillon] User Profile Watcher Job: found {0}
changes.", changes.Count));
foreach (UserProfileChange change in changes)
{
if (change is UserProfilePropertyValueChange)
{
UserProfilePropertyValueChange propertyChange =
(UserProfilePropertyValueChange)change;
if (_profileFields.Split(',').Contains(propertyChange.ProfileProperty.Name))
{
_changes.Add(propertyChange);
}
}
}
Debug.WriteLine("[Aptillon] User Profile Watcher Job: Done with Retrieve");
}
}
Listing 5: LogProfileChange method
internal void LogProfileChange(){
if (_changes.Count == 0) return;
SPList list = Utilities.FetchOrCreateList(_mySitesHost);
if (list == null)
return;
try
{
foreach (UserProfilePropertyValueChange change in _changes)
{
UserProfile profile = change.ChangedProfile as UserProfile;
if (profile == null) continue;
try
{
Debug.WriteLine(string.Format("[Aptillon] User Profile
Watcher Job: Adding change details - Account={0},
Property={1}", change.AccountName,
change.ProfileProperty.Name));
SPListItem item = list.AddItem();
item["Title"] = String.Format("\"{0}\" changed \"{1}\"",
profile[PropertyConstants.PreferredName].Value,
change.ProfileProperty.Name);
item["PropertyName"] = change.ProfileProperty.Name;
item["PropertyValue"] =
profile[change.ProfileProperty.Name].Value;
//Log the user too
SPUser user = list.ParentWeb.EnsureUser(change.AccountName);
item["User"] = user;
item.Update();
Debug.WriteLine("[Aptillon] User Profile Watcher Job:
Add Complete");
}
catch (Exception ex)
{
Debug.WriteLine("[Aptillon] User Profile Watcher Job:
Error gettting Job Properties");
Debug.WriteLine("[Aptillon] User Profile Watcher Job: " +
ex.Message);
}
}
}
finally
{
list.ParentWeb.Dispose();
list.ParentWeb.Site.Dispose();
}
}
if (_changes.Count == 0) return;
SPList list = Utilities.FetchOrCreateList(_mySitesHost);
if (list == null)
return;
try
{
foreach (UserProfilePropertyValueChange change in _changes)
{
UserProfile profile = change.ChangedProfile as UserProfile;
if (profile == null) continue;
try
{
Debug.WriteLine(string.Format("[Aptillon] User Profile
Watcher Job: Adding change details - Account={0},
Property={1}", change.AccountName,
change.ProfileProperty.Name));
SPListItem item = list.AddItem();
item["Title"] = String.Format("\"{0}\" changed \"{1}\"",
profile[PropertyConstants.PreferredName].Value,
change.ProfileProperty.Name);
item["PropertyName"] = change.ProfileProperty.Name;
item["PropertyValue"] =
profile[change.ProfileProperty.Name].Value;
//Log the user too
SPUser user = list.ParentWeb.EnsureUser(change.AccountName);
item["User"] = user;
item.Update();
Debug.WriteLine("[Aptillon] User Profile Watcher Job:
Add Complete");
}
catch (Exception ex)
{
Debug.WriteLine("[Aptillon] User Profile Watcher Job:
Error gettting Job Properties");
Debug.WriteLine("[Aptillon] User Profile Watcher Job: " +
ex.Message);
}
}
}
finally
{
list.ParentWeb.Dispose();
list.ParentWeb.Site.Dispose();
}
}
Listing 6: CustomAction element
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction Id="Aptillon.SharePoint.UserProfileWatcher"
GroupId="TimerJobs"
Location="Microsoft.SharePoint.Administration.Monitoring"
Sequence="100"
Title="Manage Aptillon SharePoint User Profile Watcher"
Description="Manage the Aptillon SharePoint User Profile Watcher Timer Job.">
<UrlAction Url="_admin/Aptillon/UserProfileWatchSettings.aspx" />
</CustomAction>
</Elements>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction Id="Aptillon.SharePoint.UserProfileWatcher"
GroupId="TimerJobs"
Location="Microsoft.SharePoint.Administration.Monitoring"
Sequence="100"
Title="Manage Aptillon SharePoint User Profile Watcher"
Description="Manage the Aptillon SharePoint User Profile Watcher Timer Job.">
<UrlAction Url="_admin/Aptillon/UserProfileWatchSettings.aspx" />
</CustomAction>
</Elements>
Listing 7: Code to look for previous timer job instances
public partial class UserProfileWatchSettings : OperationsPage{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
jobStatusList.SelectedValue = "False";
userProfileFieldsTextBox.Text = Constants.ProfileFields;
intervalMinutesDropDownList.SelectedValue = "30";
mySitesHostTextBox.Text = String.Empty;
SPTimerService timerService = SPFarm.Local.TimerService;
if (null == timerService)
{
throw new SPException("The Farm's timer service cannot
be found.");
}
foreach (SPJobDefinition oldJob in timerService.JobDefinitions)
{
if (oldJob.Title == Constants.UserProfileWatcherJobName)
{
jobStatusList.SelectedValue = "True";
userProfileFieldsTextBox.Text =
oldJob.Properties[Constants.PropertyKeyProfileFields]
.ToString();
intervalMinutesDropDownList.SelectedValue =
(oldJob.Schedule as SPMinuteSchedule).Interval.ToString();
mySitesHostTextBox.Text =
oldJob.Properties[Constants.PropertyKeyMySitesHost]
.ToString();
break;
}
}
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
jobStatusList.SelectedValue = "False";
userProfileFieldsTextBox.Text = Constants.ProfileFields;
intervalMinutesDropDownList.SelectedValue = "30";
mySitesHostTextBox.Text = String.Empty;
SPTimerService timerService = SPFarm.Local.TimerService;
if (null == timerService)
{
throw new SPException("The Farm's timer service cannot
be found.");
}
foreach (SPJobDefinition oldJob in timerService.JobDefinitions)
{
if (oldJob.Title == Constants.UserProfileWatcherJobName)
{
jobStatusList.SelectedValue = "True";
userProfileFieldsTextBox.Text =
oldJob.Properties[Constants.PropertyKeyProfileFields]
.ToString();
intervalMinutesDropDownList.SelectedValue =
(oldJob.Schedule as SPMinuteSchedule).Interval.ToString();
mySitesHostTextBox.Text =
oldJob.Properties[Constants.PropertyKeyMySitesHost]
.ToString();
break;
}
}
}
}
Listing 8: Code to update properties
protected void SetTimerJobsButton_OnClick(object sender, EventArgs e){
SPTimerService timerService = SPFarm.Local.TimerService;
if (null == timerService)
{
throw new SPException("The Farm's timer service cannot
be found.");
}
// delete the job for the current web application
foreach (SPJobDefinition oldJob in timerService.JobDefinitions)
{
if (oldJob.Title == Constants.UserProfileWatcherJobName)
oldJob.Delete();
}
// Enable the Timer Job if the enabled button is chosen
if (jobStatusList.SelectedValue == "True")
{
string mySitesHost = mySitesHostTextBox.Text;
if (!SPSite.Exists(new Uri(mySitesHost)))
{
ErrorMessageLiteral.Text = "The specified my site
host does not exist.";
return;
}
// create a new instance of the job and schedule it
SPMinuteSchedule schedule = new SPMinuteSchedule();
schedule.BeginSecond = 0;
schedule.EndSecond = 59;
schedule.Interval =
Convert.ToInt32(intervalMinutesDropDownList.SelectedValue);
UserProfileWatcherTimerJob job =
new UserProfileWatcherTimerJob(timerService);
job.SubmitJob(userProfileFieldsTextBox.Text,
mySitesHost, schedule);
}
this.RedirectToOperationsPage();
}
SPTimerService timerService = SPFarm.Local.TimerService;
if (null == timerService)
{
throw new SPException("The Farm's timer service cannot
be found.");
}
// delete the job for the current web application
foreach (SPJobDefinition oldJob in timerService.JobDefinitions)
{
if (oldJob.Title == Constants.UserProfileWatcherJobName)
oldJob.Delete();
}
// Enable the Timer Job if the enabled button is chosen
if (jobStatusList.SelectedValue == "True")
{
string mySitesHost = mySitesHostTextBox.Text;
if (!SPSite.Exists(new Uri(mySitesHost)))
{
ErrorMessageLiteral.Text = "The specified my site
host does not exist.";
return;
}
// create a new instance of the job and schedule it
SPMinuteSchedule schedule = new SPMinuteSchedule();
schedule.BeginSecond = 0;
schedule.EndSecond = 59;
schedule.Interval =
Convert.ToInt32(intervalMinutesDropDownList.SelectedValue);
UserProfileWatcherTimerJob job =
new UserProfileWatcherTimerJob(timerService);
job.SubmitJob(userProfileFieldsTextBox.Text,
mySitesHost, schedule);
}
this.RedirectToOperationsPage();
}
Reference
http://sharepointpromag.com/sharepoint-2010/monitor-sharepoint-user-profile-changes