I. Introduction▲
Les tâches planifiées de Sharepoint permettent au développeur d'exécuter de manière récurrente certaines actions à la manière des tâches planifiées de Windows. Dans les anciennes versions de Sharepoint, il fallait d'ailleurs passer par le gestionnaire des tâches Windows pour effectuer des opérations régulières sur le(s) serveur(s) Sharepoint. Via le mécanisme de tâches intégré, il est désormais plus simple et plus pratique de centraliser toutes ces tâches dans un store spécifique.
Il est indiqué d'utiliser des tâches par exemple lorsque l'on souhaite synchroniser des données issues de systèmes externes tels que l'active directory, une base de données...,lorsque l'on souhaite activer des mécanismes d'archivage etc...
Dans cet exemple, nous allons créer un job qui supprimera automatiquement les éléments d'une liste lorsqu'ils auront été créés depuis plus d'un nombre de minutes défini en paramètre dans un fichier de config.
II. Les tâches intégrées▲
Dès que vous installez MOSS, pas mal de tâches sont déjà présentes dans le système. Elles sont accessibles via la centrale d'administration, dans l'onglet opération tel qu'illustré par la capture d'écran ci-dessous
Cet écran nous présente les tâches actuellement définies dans le système, l'application concernée par leur exécution et le type de fréquence (journalière, hebdomadaire etc...)
Il va sans dire que la tâche que vous créerez sera également présente dans cette liste.
III. Planification▲
Il existe plusieurs classes nous permettant de planifier une tâche. Voici le détail de chacune
Nom de la classe | Description | Exemple d'utilisation | Exécution |
---|---|---|---|
SPWeeklySchedule | Permet de planifier une tâche hebdomadaire |
SPWeeklySchedule JobSchedule = new SPWeeklySchedule(); JobSchedule.BeginDayOfWeek = DayOfWeek.Monday; JobSchedule.EndDayOfWeek = DayOfWeek.Monday; JobSchedule.BeginHour = 10; JobSchedule.BeginMinute = 15; JobSchedule.EndMinute = 25; JobSchedule.EndHour = 10; |
Tous les lundis à 10h15 |
SPDailySchedule | Permet de planifier une tâche journalière | SPDailySchedule JobSchedule = new SPDailySchedule();
JobSchedule.BeginHour = 10; JobSchedule.BeginMinute = 15; JobSchedule.EndMinute = 25; JobSchedule.EndHour = 10; |
tous les jours à 10h15 |
SPMonthlySchedule | Permet de planifier une tâche mensuelle |
SPMonthlySchedule JobSchedule = new SPMonthlySchedule(); JobSchedule.BeginDay = 1; JobSchedule.EndDay = 1; JobSchedule.BeginHour = 10; JobSchedule.EndHour = 10; JobSchedule.BeginMinute = 15; JobSchedule.EndMinute = 25; |
Le 1er jour de chaque mois à 10h15 |
SPYearlySchedule | Permet de planifier une tâche annuelle |
SPYearlySchedule JobSchedule = new SPYearlySchedule(); JobSchedule.BeginMonth = 1; JobSchedule.EndMonth = 1; JobSchedule.BeginDay = 1; JobSchedule.EndDay = 1; JobSchedule.BeginHour = 10; JobSchedule.EndHour = 10; JobSchedule.BeginMinute = 15; JobSchedule.EndMinute = 25; |
Tous les ans, le 1er janvier à 10h15 |
SPHourlySchedule | Permet de planifier une tâche toutes les heures |
SPHourlySchedule JobSchedule = new SPHourlySchedule(); JobSchedule.BeginMinute = 1; JobSchedule.EndMinute = 2; |
Toutes les heures à la première minute |
SPMinuteSchedule | Permet de planifier une tâche toutes les x minutes |
SPMinuteSchedule JobSchedule = new SPMinuteSchedule(); JobSchedule.BeginSecond = 1; JobSchedule.EndSecond = 59; JobSchedule.Interval = 2; |
Toutes les deux minutes |
Note:il est indispensable de définir les propriétés End....(EndMinute, EndHour etc...) car le temps réel auquel une tâche doit démarrer est calculé par Sharepoint en se basant sur ces données. Si vous ne spécifiez rien, votre job ne s'exécutera jamais.
IV. Construction du projet pas à pas▲
A l'heure actuelle, il n'existe pas de template spécifique pour créer des jobs. On peut utiliser une class library.
- Création d'un projet de type class library
- référencement de Microsoft.Sharepoint.dll qui se trouve dans le répertoire ISAPI de votre installation MOSS
- ajout des directives incluant les espaces de noms Microsoft.Sharepoint,Microsoft.Sharepoint.Administration et Microsoft.Sharepoint.Utilities
- ajout d'un fichier XML que l'on nommera feature.xml qui nous permettra d'ajouter notre job
- ajout d'une classe qui nous permettra d'ajouter notre job lors de l'activation de la feature et de le supprimer lors de la désactivation
- ajout d'un fichier .bat qui s'exécutera lors du post build pour déployer notre job
La structure résultante doit donc ressembler à ceci
IV-A. Corps de notre job - classe Suppression.cs▲
using
System;
using
System.
Collections.
Generic;
using
System.
Text;
using
Microsoft.
SharePoint;
using
Microsoft.
SharePoint.
Administration;
using
Microsoft.
SharePoint.
Utilities;
namespace
AutoSuppression
{
public
class
Suppression :
SPJobDefinition
{
//Constructeur vide nécessaire pour la séralisation
public
Suppression
(
)
{
}
//Constructeur qui sera exécuté lors de l'enregistrement du job
public
Suppression
(
string
JobName,
SPWebApplication WebApplication,
string
TargetSite,
string
TargetList,
string
MaxLifeTime)
:
base
(
JobName,
WebApplication,
null
,
SPJobLockType.
ContentDatabase)
{
this
.
Title =
JobName;
//Création des propriétés du job pour que l'info
//reçue de feature.xml persiste au sein du job.
this
.
Properties.
Add
(
"TargetSite"
,
TargetSite);
this
.
Properties.
Add
(
"TargetList"
,
TargetList);
this
.
Properties.
Add
(
"MaxLifeTime"
,
MaxLifeTime);
}
//Méthode polymorphe que l'on réécrit pour définir
//le corps d'exécution du job
public
override
void
Execute
(
Guid targetInstanceId)
{
//Temps vie max
int
MaxLifeTime =
0
;
//Liste ciblée par la suppression
string
TargetList =
null
;
//Site ciblé par la suppression
string
TargetSite =
null
;
base
.
Execute
(
targetInstanceId);
try
{
//Récupération des paramètres définis dans feature.xml
MaxLifeTime =
Convert.
ToInt16
(
this
.
Properties[
"MaxLifeTime"
].
ToString
(
));
TargetList =
this
.
Properties[
"TargetList"
].
ToString
(
);
TargetSite =
this
.
Properties[
"TargetSite"
].
ToString
(
);
//On obtient une référence de l'application web courante
SPWebApplication WebApplication =
this
.
Parent as
SPWebApplication;
if
(
WebApplication !=
null
)
{
//On pointe sur la DB de contenu
SPContentDatabase ContentDb =
WebApplication.
ContentDatabases[
targetInstanceId];
//On crée une référence vers la liste cible
SPList TargetListObject =
ContentDb.
Sites[
TargetSite].
RootWeb.
Lists[
TargetList];
//Date calculée en tenant compte de l'heure qui sera
//utilisée dans la requête CAML
string
CalculatedDate =
SPUtility.
CreateISO8601DateTimeFromSystemDateTime
(
DateTime.
Now.
AddMinutes
(-
MaxLifeTime)).
ToString
(
);
SPQuery Query =
new
SPQuery
(
);
//Requête
Query.
Query =
"<Where><Lt><FieldRef Name='Created' /><Value Type='DateTime' IncludeTimeValue='TRUE'>"
+
CalculatedDate +
"</Value></Lt></Where>"
;
//On parcourt le résultat et on supprime les items
SPListItemCollection Items =
TargetListObject.
GetItems
(
Query);
int
ItemCount =
Items.
Count;
for
(
int
i =
0
;
i <
ItemCount;
i++
)
{
SPListItem Item =
Items[
0
]
as
SPListItem;
Item.
Delete
(
);
}
}
}
catch
(
FormatException)
{
throw
new
ApplicationException
(
"Nombre de minutes incorrect"
);
}
catch
(
Exception Ex)
{
throw
new
ApplicationException
(
"Une erreur s'est produite:"
+
Ex.
Message);
}
}
}
}
Quatre choses importantes sont à retenir dans ce code
- Notre classe doit dériver de SPJobDefinition
- On doit impérativement définir un constructeur vide pour la sérialisation
- On doit ajouter des propriétés à notre job afin de faire persister les paramètres définis dans feature.xml
- On doit réécrire la méthode Execute() pour exécuter le corps de notre job
IV-B. Enregistrement du job - Fichiers EnregistrementJob.cs et feature.xml▲
Pour enregistrer la tâche, on peut utiliser une feature. Les features sont un moyen traditionnel d'encapsuler les composants. Il y a toutefois une petite nuance pour les tâches. Il faut éviter que n'importe qui puisse activer cette feature car l'enregistrement du job nécessite certains droits sur la db de config. Il faut dès lors s'assurer que ce soit un administrateur qui active la feature.
Pour s'en assurer, on peut déployer la feature en tant que feature "cachée" (hidden) afin qu'elle n'apparaisse pas dans la liste des features lorsque l'on va dans site settings => site features. Ceci force l'activation de la feature via stsadm, donc un accès console sur le serveur donc forcément en théorie un administrateur.
IV-B-1. Le fichier feature.xml▲
<?xml version="1.0" encoding="utf-8" ?>
<Feature
xmlns
=
"http://schemas.microsoft.com/sharepoint/"
Id
=
"8A6E09E8-2A7E-4cd4-8BE4-44883B737732"
Title
=
"Auto suppression d'éléments"
Description
=
"Supprime les éléments automatiquement"
Scope
=
"Web"
Hidden
=
"TRUE"
Version
=
"1.0.0.0"
ReceiverAssembly
=
"AutoSuppression, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a2ba62857f5113af"
ReceiverClass
=
"AutoSuppression.EnregistrementJob"
>
<Properties>
<Property
Key
=
"TargetSite"
Value
=
"url de votre site"
/>
<Property
Key
=
"TargetList"
Value
=
"le nom de la liste"
/>
<!--Nombre de minutes de vie-->
<Property
Key
=
"MaxLifeTime"
Value
=
"5"
/>
</Properties>
</Feature>
Ici, également quatre choses importantes à retenir
- Le scope (web) : ceci signifie que la feature sera activable/désactivable pour un site et non pour une collection
- Le paramètre Hidden (TRUE) : pour forcer l'activation par un administrateur, on cache la feature afin qu'elle n'apparaisse pas dans la liste dans les GUI
- ReceiverAssembly et ReceiverClass : ces paramètres nous permettent de dire que l'on souhaite que la classe EnregistrementJob soit exécutée lors d'actions spécifiques sur la feature
- Les propriétés : ce sont les paramètres que l'on passera à notre job. C'est une manière flexible de travailler.
Note: cela va de soi mais je l'indique quand même, vous devez bien sûr définir vos propres valeurs pour les paramètres TargetSite et TargetList
IV-B-2. Le code exécuté lors de l'activation/désactivation de la feature▲
using
System;
using
System.
Collections.
Generic;
using
System.
Text;
using
Microsoft.
SharePoint;
using
Microsoft.
SharePoint.
Administration;
namespace
AutoSuppression
{
class
EnregistrementJob :
SPFeatureReceiver
{
//Nom du job
const
string
JobName =
"AutoSuppression"
;
///
<
summary
>
/// Méthode appelée lors de l'activation de la feature
///
<
/summary
>
///
<
param
name
=
"properties"
><
/param
>
public
override
void
FeatureActivated
(
SPFeatureReceiverProperties properties)
{
SPWeb TargetWeb =
properties.
Feature.
Parent as
SPWeb;
//Suppression du job si il existe déjà
foreach
(
SPJobDefinition Job in
TargetWeb.
Site.
WebApplication.
JobDefinitions)
{
if
(
Job.
Name ==
JobName)
{
Job.
Delete
(
);
break
;
}
}
//Instanciation de notre job et passage des paramètres
//définis dans feature.xml
Suppression JobSuppression =
new
Suppression
(
JobName,
TargetWeb.
Site.
WebApplication,
properties.
Definition.
Properties[
"TargetSite"
].
Value,
properties.
Definition.
Properties[
"TargetList"
].
Value,
properties.
Definition.
Properties[
"MaxLifeTime"
].
Value);
//Création du schedule, dans notre cas, le job sera exécuté
//toutes les 2 minutes
SPMinuteSchedule JobSchedule =
new
SPMinuteSchedule
(
);
JobSchedule.
BeginSecond =
0
;
JobSchedule.
EndSecond =
59
;
JobSchedule.
Interval =
2
;
JobSuppression.
Schedule =
JobSchedule;
JobSuppression.
Update
(
);
}
///
<
summary
>
/// Méthode appelée lors de la désactivation de la feature.
///
<
/summary
>
///
<
param
name
=
"properties"
><
/param
>
public
override
void
FeatureDeactivating
(
SPFeatureReceiverProperties properties)
{
SPWeb TargetWeb =
properties.
Feature.
Parent as
SPWeb;
//Suppression du job
foreach
(
SPJobDefinition Job in
TargetWeb.
Site.
WebApplication.
JobDefinitions)
{
if
(
Job.
Name ==
JobName)
{
Job.
Delete
(
);
break
;
}
}
}
///
<
summary
>
/// Méthode abstraite qu'il faut au minimum déclarer
///
<
/summary
>
///
<
param
name
=
"properties"
><
/param
>
public
override
void
FeatureInstalled
(
SPFeatureReceiverProperties properties)
{
}
///
<
summary
>
/// Méthode abstraite qu'il faut au minimum déclarer
///
<
/summary
>
///
<
param
name
=
"properties"
><
/param
>
public
override
void
FeatureUninstalling
(
SPFeatureReceiverProperties properties)
{
}
}
}
Ici, quatres choses à retenir
- Il faut déclarer toutes les méthodes abstraites
- Il faut implémenter FeatureActivated et FeatureDeactivating afin d'enregistrer/supprimer notre job lors de l'activation/désactivation de la feature
- Il faut créer un job schedule pour déterminer la fréquence d'exécution. Comme spécifié dans la section III ne pas oublier de spécifier les propriétés End...(ici EndSecond)
IV-C. Déploiement du job via le fichier .bat▲
Vous devez signer la DLL (ceci n'est pas impératif) et avoir paramétré Visual Studio afin qu'il exécute le fichier .bat après un build.
REM ceci déploie la feature physiquement sur le disque
rd
/s /q "%CommonProgramFiles%
\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\AutoSuppression"
mkdir
"%CommonProgramFiles%
\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\AutoSuppression"
copy
/Y feature.xml "%CommonProgramFiles%
\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\AutoSuppression\"
REM Suppression et Ajout de la DLL dans la GAC
"%programfiles%
\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -uf http://urldevotresite
"%programfiles%
\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -if bin\Debug
\AutoSuppression.dll
REM Désinstallation/Désactivation/Installation/Activation de la feature
pushd
%programfiles%
\common files\microsoft shared\web server extensions\12\bin
stsadm -o deactivatefeature -filename AutoSuppression\feature.xml -url http://urldevotresite
stsadm -o uninstallfeature -filename AutoSuppression\feature.xml
stsadm -o installfeature -filename AutoSuppression\feature.xml -force
stsadm -o activatefeature -filename AutoSuppression\feature.xml -url http://urldevotresite
REM Redémarrage de IIS
iisreset
REM Redémarrage du service Timer (process OWSTimer.exe)
net
stop SPTimerV3
net
start
SPTimerV3
L'objet du fichier bat est de faciliter le déploiement. En paramétrant son exécution automatique via les post-build events de Visual Studio, vous automatisez le déploiement.
V. Et après, il se passe quoi?▲
Lorsque vous avez suivi les étapes décrites préalablement, vous devriez voir apparaître votre job dans la liste des jobs dans la centrale d'administration
Après une très brève attente, le job s'exécutera et vous pourrez voir son statut.
VI. Comment debugger une tâche?▲
Habituellement, lorsque l'on debugge un composant personnel dans Sharepoint, on s'attache à l'un des processus w3p.exe en charge d'exécuter le code de ceux-ci. Pour les tâches planifiées, il faut s'attacher au processus OWSTIMER.exe car c'est lui qui est responsable de l'exécution des tâches.
VII. Comment forcer l'exécution de notre job?▲
Vous l'aurez sans doute constaté, la centrale d'administration nous propose une interface affichant tous les jobs présents et nous donnant la possibilité d'en désactiver mais pas de les exécuter. La commande stsadm -o execadmsvcjobs permet d'exécuter tous les jobs inhérants à l'administration de Sharepoint mais pas des jobs personnels tels que celui présenté dans ce tutoriel.
Le seul moyen que je connaisse actuellement est l'API, on peut démarrer un job comme ceci:
using
(
SPSite TargetSite =
new
SPSite
(
"lesite"
))
{
foreach
(
SPJobDefinition Job in
TargetSite.
WebApplication.
JobDefinitions)
{
if
(
Job.
Name ==
"AutoSuppression"
)
{
Job.
Execute
(
TargetSite.
WebApplication.
ContentDatabases[
0
]
);
break
;
}
}
}
Si vous disposez du GUID du job, cette méthode est plus efficace
using
(
SPSite TargetSite =
new
SPSite
(
"lesite"
))
{
try
{
SPJobDefinition Job =
TargetSite.
WebApplication.
JobDefinitions[
new
Guid
(
"le guid"
)];
Job.
Execute
(
TargetSite.
WebApplication.
ContentDatabases[
0
]
);
}
catch
(
NullReferenceException)
{
throw
new
ApplicationException
(
"Le job n'existe pas"
);
}
catch
(
Exception Ex)
{
throw
new
ApplicationException
(
"problème lors de la tentative d'exécution:"
+
Ex.
Message);
}
}
Il est en théorie possible de faire la même chose en utilisant le nom du job plutôt que le GUID mais cela ne fonctionne pas...Lorsque l'on travaille avec le nom, on semble forcé de faire une boucle traversant la collection.
VIII. Téléchargement du projet▲
Vous pouvez télécharger le projet ici
Avant de déployer le projet, veillez à définir vos propres paramètres dans le fichier feature.xml ainsi que dans le fichier Install.bat