SharePoint 2010 Server

[SP2013] – Accéder aux données du Secure Store Service en C#

Posted on Updated on

Bonjour à tous,

Aujourd’hui un article technique pour vous montrer comment accéder aux données stockées dans le Secure Store Service de SharePoint Server 2013.

A quoi sert ce service ? Il suffit de suivre le lien : https://technet.microsoft.com/fr-fr/library/Ee806866.aspx

1/ Contexte général

Le but étant de stocker des données de type login, mot de passe, domaine, Pin, etc. afin d’accéder à des systèmes externe à SharePoint.

Habituellement, on utilise le Secure Store Service (SSS) de SharePoint pour enregistrer les credentials utilisés pour la mise en œuvre de listes externes dans SharePoint par le biais du Business Connectivity Service (BCS, ex-BDC). imaginons le cas où l’on souhaite afficher dans SharePoint une base de clients/fournisseurs qui provient d’une base de données SQL Server. Cette base peut utiliser un mode d’authentification SQL (et non Annuaire AD) et il faut donc envoyer un login/mot de passe pour pouvoir se connecter et requêter la base depuis SharePoint. C’est donc un SSO (Single Sign-On) mais Inter-Domaine (Cross-Domain) que nous propose Microsoft. Les données de cette base d’identifiants sont chiffrées via une clé générée par SharePoint lui-même (à partir d’une phrase secrète qu’il ne faut pas perdre !).

Dans le cas présent, le but est d’accéder à ce Secure Store Service afin de stocker, lire, mettre à jour les données d’un utilisateur, et proposer par exemple un formulaire de connexion, de mise à jour de ces données directement depuis un portail SharePoint.

2/ Architecture du service

Le Secure Store Service est une Application de Service proposée par SharePoint Server 2013 (Standard ou Enterprise). Elle peut se déployer par la console d’administration centrale ou PowerShell (voir l’article Technet précédent).

La clé de chiffrement permet de chiffrer les données stockées dans la base de données associée à l’application de service.

Les données sont stockées dans des Target Applications (Applications cibles). Chaque Target application stocke les données pour 1 application, par exemple :

  • Une Target Application pour la connexion à une base SQL Server
  • Une Target Application pour la connexion à une ferme Dynamics CRM
  • Une Target Application pour la connexion à SAP
  • Une Target Application pour la connexion à une base Oracle

Chacune de ces Target Application est définie par l’administrateur SharePoint où il doit fournir :

  1. ID de l’application cible : identifiant unique de la Target Application
  2. Nom complet : nom d’affichage de la Target Application
  3. Messagerie de contact : adresse e-mail de contact pour l’administrateur de cette Target Application
  4. Url de la page d’application cible : voir article Technet
  5. Type d’application cible : il s’agit ici d’indiquer si chaque utilisateur aura sa propre identification ou si l’on va définir une identification pour tout un groupe de personnes (individual ou group).

Ensuite vous devrez spécifier les champs que vous souhaitez enregistrer : champs d’identification Windows, champs d’identification standards (login/mot de passe), code Pin, domaine, etc.

Et enfin les administrateurs de la Target Application. Bref, tout cela est plutôt bien défini dans l’article Technet.

3/ Lire des credentials depuis mon code en C#

Voici enfin le cœur du problème. Comment accéder aux données stockées dans le SSS depuis mon code ?

J’ai tout d’abord défini une classe métier pour stocker quelques informations : Login, password, Login SharePoint, Nom Complet et E-mail de l’utilisateur :


namespace MonDev.LogMe.Utils
{
   public class SSSCredentials
   {
      public string Login { get; set; }
      public string Password { get; set; }
      public string SPLogin { get; set; }
      public string SPFullName { get; set; }
      public string SPEmail { get; set; }
   }
}

Ensuite, voici le code de la méthode GetCredentials que j’utilise. Les paramètres attendus sont :

  • site : collection de sites courante
  • applicationID : identifiant (interne) de la Target Application

   /// Get credentials for current connected user from Secure Store Service.
   /// current site collection, associated to a Secure Store Service at Web Application Level
   /// Target Application ID in Secure Store Service
   /// Returns Credentials if found, null otherwise.
   public static SSSCredentials GetCredentials(SPSite site, string applicationID)
   {
      //Initialize new Credentials structure
      SSSCredentials credentials = new SSSCredentials();
      credentials.SPEmail = site.RootWeb.CurrentUser.Email;
      credentials.SPFullName = !string.IsNullOrEmpty(site.RootWeb.CurrentUser.Name) ? site.RootWeb.CurrentUser.Name : site.RootWeb.CurrentUser.LoginName;
      credentials.SPLogin = site.RootWeb.CurrentUser.LoginName;

      //new SPSite is not mandatory, but it seems to be more efficient than use SPSite from parameters.
      using (SPSite currentSite = new SPSite(site.ID))
      {
         //Initialize a new SecureProvider to access to data into SSS
         ISecureStoreProvider provider = SecureStoreProviderFactory.Create();
         if (provider == null)
            throw new InvalidOperationException("Unable to get an ISecureStoreProvider");

         ISecureStoreServiceContext providerContext = provider as ISecureStoreServiceContext;
         providerContext.Context = SPServiceContext.GetContext(currentSite);

         try
         {
            using (SecureStoreCredentialCollection creds = provider.GetCredentials(applicationID))
            {
               if (creds != null)
               {
                  foreach (SecureStoreCredential cred in creds)
                  {
                     if (cred == null)
                        continue;

                     switch (cred.CredentialType)
                     {
                        case SecureStoreCredentialType.UserName:
                           credentials.Login = GetStringFromSecureString(cred.Credential);
                           break;
                        case SecureStoreCredentialType.Password:
                           credentials.Password = GetStringFromSecureString(cred.Credential);
                           break;
                        default:
                           break;
                     }
                  }
               }
            }
         }
         catch (SecureStoreException)
         {
            //No credentials are found into SSS, so return null
            return null;
         }
         catch (Exception e)
         {
            throw new Exception("A general error occured. Please contact your administrator. Error : " + e.Message);
         }
      }
      return credentials;
   }

La méthode doit être appelée comme suit :


private const string TargetApplicationID = "MaTargetApplication";
SSSCredentials credentials = SSSUtils.GetCredentials(SPContext.Current.Site, TargetApplicationID);

Rien de très compliqué toutefois je suis tombé sur quelques exemples de codes et aucun n’a fonctionné chez moi. A noter ici, pas besoin d’impersonation ou de SPSecurity.RunWithElevatedPrivileges(), bien au contraire. Il faut se connecter au provider avec les identifiants de l’utilisateur connecté au portail pour pouvoir récupérer ses identifiants !

 

4/ Ecrire dans le Secure Store Service

Si nous avons lu, il faut bien parvenir à écrire dans le SSS. Ici la méthode est la même pour l’ajout ou la modification de credentials dans le magasin du Secure Store Service. Les paramètres attendus sont :

  • site : collection de sites courante
  • applicationID : identifiant unique (interne) de la Target Application
  • windowslogin : login Windows ou identifiant de l’utilisateur dans le portail SharePoint
  • userName : nom d’utilisateur à stocker dans le Secure Store Service, dans la Target Application
  • userPassword : mot de passe à stocker dans le Secure Store Service, dans la Target Application

 


public static void SetCredentials(SPSite site, string applicationID, string windowslogin, string userName, string userPassword)
   {
      SPClaim claim = SPClaimProviderManager.CreateUserClaim(windowslogin, SPOriginalIssuerType.Windows);
      SecureStoreServiceClaim ssClaim = new SecureStoreServiceClaim(claim);

      SPServiceContext context = SPServiceContext.GetContext(SPServiceApplicationProxyGroup.Default, SPSiteSubscriptionIdentifier.Default);

      SecureStoreServiceProxy ssp = new SecureStoreServiceProxy();
      ISecureStore iss = ssp.GetSecureStore(context);

      IList applicationFields = iss.GetUserApplicationFields(applicationID);

      IList creds = new List(applicationFields.Count);

      using (SecureStoreCredentialCollection credentials = new SecureStoreCredentialCollection(creds))
      {
         foreach (TargetApplicationField field in applicationFields)
         {
            switch (field.CredentialType)
            {
               case SecureStoreCredentialType.UserName:
                  creds.Add(new SecureStoreCredential(GetSecureStringFromString(userName), SecureStoreCredentialType.UserName));
                  break;
               case SecureStoreCredentialType.Password:
                  creds.Add(new SecureStoreCredential(GetSecureStringFromString(userPassword), SecureStoreCredentialType.Password));
                  break;
               default:
                  break;
            }
         }
         iss.SetCredentials(applicationID, credentials);
      }
   }

La méthode doit être appelée comme suit :


SSSUtils.SetCredentials(SPContext.Current.Site, TargetApplicationID, SPContext.Current.Site.RootWeb.CurrentUser.LoginName, "LOGIN_A_STOCKER", "PASSWORD_A_STOCKER");

A savoir, il est important d’exécuter le code avec le compte de l’utilisateur connecté à SharePoint afin de pouvoir lire ses credentials ou écrire/mettre à jour ses credentials. Lorsque j’essayais d’écrire le code pour faire ces manipulations, je suis tombé sur pas mal de bouts de code où ils essayaient d’exécuter cela avec le compte système via de l’impersonation ou bien SPSecurity.RunWithElevatedPrivileges(). Cela n’a pas marché pour moi !

5/ Fonctions annexes

Dans les extraits de codes précédents, j’ai utilisé quelques méthodes supplémentaire pour encoder/décoder les chaines de caractères envoyées ou reçues depuis le Secure Store Service. Pour être totalement complet, les voici.

Cette première méthode permet de décoder une chaine encodée (SecureString) dans une chaine standard (String)


internal static string GetStringFromSecureString(SecureString secStr)
{
   if (secStr == null)
      return null;

   IntPtr pPlainText = IntPtr.Zero;
   try
   {
      pPlainText = Marshal.SecureStringToBSTR(secStr);
      return Marshal.PtrToStringBSTR(pPlainText);
   }
   finally
   {
      if (pPlainText != IntPtr.Zero)
         Marshal.FreeBSTR(pPlainText);
   }
}

Et bien sûr la méthode effectuant la manipulation inverse :


internal static SecureString GetSecureStringFromString(string str)
{
   if (str == null)
      return null;
   var str2 = new SecureString();
   char[] chArray = str.ToCharArray();
   for (int i = 0; i < chArray.Length; i++)
   {
      str2.AppendChar(chArray[i]);
      chArray[i] = '0';
   }
   return str2;
}

6/ Usings…

J’ai un peu oublié d’en parler au début, mais certains peuvent avoir des difficultés pour inclure les bonnes références et using nécessaires au projet (je ne parlerai pas des assemblies standards…).

Références :

  1. Microsoft.BusinessData (à aller cherche dans) : C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.BusinessData.dll
  2. Microsoft.Office.SecureStoreService (à aller rechercher dans le GAC) : C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.Office.SecureStoreService\v4.0_15.0.0.0__71e9bce111e9429c\Microsoft.Office.SecureStoreService.dll
  3. Microsoft.Office.SecureStoreService.Server.Security (à aller cherche dans) : C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.Office.SecureStoreService.Server.Security.dll

Il ne nous reste plus qu’à trouver un usage à tout ceci ! 🙂

Florent.

[SharePoint] – Script PowerShell pour afficher les tailles des bases de données

Posted on Updated on

Bonjour à tous,

Aujourd’hui un petit script bien utile qui vous permettra d’afficher la taille des bases de données d’une ferme SharePoint OnPremise.

Cela permettra à l’administrateur de ferme principalement de suivre l’évolution du stockage dans la ferme SharePoint, afin d’affiner ses quotas, optimiser son serveur / cluster SQL Server.

Le but est donc de créer un petit script qui parcourra les Web Applications, puis les bases de données qui leur sont associées (on ne sélectionne donc pas les bases de données des Applications de Service) et d’afficher le tout dans une GridView par exemple.

Dans cette démo, j’utiliserai l’environnement PowerShell ISE (Integrated Scripting Environment), avec Windows Server 2012 et une plateforme SharePoint Server 2013 Enterprise (Pré-SP1).

Première étape, lancer PowerShell ISE avec le compte Administrateur de la ferme SharePoint :

02

Et entrer le script tout fait 🙂

Ok, je vais détailler un peu son contenu :

Première étape, afin que l’on puisse lancer le script avec PowerShell, sans utiliser le SharePoint Management Shell, on ajoute l’import des Cmdlets SharePoint :

If ((Get-PSSnapIn -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null ) 
{ Add-PSSnapIn -Name Microsoft.SharePoint.PowerShell }

$host.Runspace.ThreadOptions = "ReuseThread"

Puis on démarre un scope pour les objets utilisés dans le script afin qu’ils soient automatiquement “Disposés” à la fin de l’exécution. Pour cela, nous allons utiliser les commandes (au début et à la fin du script) :

Start-SPAssignment –Global
Stop-SPAssignment –Global

Plus de détails ici : http://technet.microsoft.com/fr-fr/library/ff607664(v=office.15).aspx

On commence ensuite la construction dun tableau (Hashtable plus particulièrement) en parcourant les WebApplications, les bases de contenu et en calculant leur taille en GB :

$webApps = Get-SPWebApplication -IncludeCentralAdministration
foreach($WebApp in $spWebApps)
{
    $ContentDBs = $webApp.ContentDatabases
    foreach($ContentDB in $ContentDBs)
    {    
        $size = [Math]::Round(($ContentDB.disksizerequired/1GB),2)

        $DBdetails = New-Object PSObject
        $DBdetails | Add-Member -Name "Web Application Name" -MemberType NoteProperty -Value $WebApp.DisplayName
        $DBdetails | Add-Member -Name "Database Name" -MemberType NoteProperty -Value $ContentDB.Name
        $DBdetails | Add-Member -Name "Database Size (in Gb)" -MemberType NoteProperty -Value $size         $data += $DBdetails
    }
}

Puis pour terminer, on affiche le tout dans une GridView :

$DB = $data | Out-GridView -Title "SharePoint Databases Size" –PassThru

Il nous faut ajouter un try/catch pour gérer les exceptions… ce qui donne au final :

03

Puis on lance l’exécution du script (sauvegardé dans un fichier PS1) ou avec le bouton “Play” de PowerShell ISE :

05

Et voilà !

[SP2013] – Déployer le Machine Translation Service de SharePoint Server 2013

Posted on Updated on

Bonjour à tous.

Dans cet article je vais tenter de vous expliquer ce qu’est le Machine Translation Service de SharePoint Server 2013 mais également comment le déployer sur votre ferme SharePoint.

J’ai choisi de déployer et configurer un maximum de choses avec PowerShell dans le but d’automatiser le déploiement.

1/ Machine Translation Service : qu’est-ce que c’est ?

Le Machine Translation Service est une nouvelle application de service, apparue avec SharePoint 2013 et disponible dans les versions Standard et Enterprise.

Il permet de traduire automatiquement non seulement les sites (colonnes, listes, pages) mais également le contenu des documents qui seront stockés dans ce site, et ce à la demande des utilisateurs. D’ailleurs ce contenu peut être pré-traduit (via une planification et un TimerJob) ou bien à la volée sur la demande d’un utilisateur.

On comprendra rapidement l’intérêt de ce service lors du déploiement de portails de publication SharePoint utilisant les Variantes.

Plutôt intéressant sur le principe !

Vous trouverez également sur Technet quelques exemple d’appels à API du Machine Translation Service via du code client ou serveur : http://msdn.microsoft.com/fr-fr/library/office/jj163145(v=office.15).aspx.

De plus, ce service limite la taille des fichiers qui pourront être convertis :

Type de fichier / extensions Limite de taille (en Ko)
Texte (txt) 15 360
HTML (html, xhtml, xhtm, htm, aspx) 15 360
Word (doc, docx, docm, dot, dotx, dotm, rtf) 524 288
XLIFF (xlf) 15 360

2/ Prérequis avant le déploiement

Le déploiement de ce service impose quelques prérequis qu’il vous faudra valider ou mettre en œuvre au préalable.

A. Application de service : Gestion des applications

Effectivement, cette application de service Gestion des applications ‘'(App Management Service Application) doit être déployée et configurée sur votre ferme. Pour cela, je vous invite à suivre le guide de déploiement sur Technet : http://technet.microsoft.com/fr-fr/library/fp161236(v=office.15).aspx

B. Application de service : SharePoint Token Service

Le service d’authentification serveur à serveur (STS) doit être déployé et configuré dans le cas où vous travaillez sur une architecture Multi-Tenant. Pour cela, je vous invite à suite le guide de déploiement sur Technet : http://technet.microsoft.com/fr-fr/library/jj655400(v=office.15).aspx

C. Application de service : User Profile

Peut-être un peu plus étonnant, votre ferme doit avoir le Proxy de l’application de service User Profile déployé sur votre ferme SharePoint, et l’application de service correspondante doit être déployée, configurée et démarrée. Pour cela, je vous invite à suivre le guide de déploiement sur Technet : http://technet.microsoft.com/fr-fr/library/ee721052(v=office.15).aspx

D. Accès à internet

Evidemment, ce n’est pas SharePoint qui va effectuer les traductions demandées par les utilisateurs mais bien envoyées et traduites par Microsoft Translation Service. Et donc pour cela, une connexion Internet sur les serveurs SharePoint est requise (ceux où le service de traduction est démarré).

E. Compte de déploiement du service

Evidemment, pour pouvoir déployer et configurer le service, il faut avoir suffisamment de droits. Dans mon cas j’utilise le compte système (compte d’installation de la ferme SharePoint).

De manière générale, ce compte doit avoir les droits :

  • securityadmin sur l’instance SQL Server de votre ferme SharePoint
  • db_owner sur l’instance SQL Server de votre ferme SharePoint
  • Dans le groupe Administrateurs local de vos serveurs SharePoint où on lancera le déploiement

F. Compte du pool d’application

Dans l’exemple que je vais développer ci-après, j’utilise un nouveau pool d’application pour ce service. Il est nécessaire que le compte utilisé par ce pool ait accès au service User Profile avec le contrôle total. Pour cela, il faut se rendre dans la page de gestion du service de profil utilisateur dans la central admin et ajouter le compte avec contrôle total :

12

3/ Déploiement du service avec PowerShell

Ma ferme SharePoint est simple, elle est composée :

  • D’un serveur Controleur de Domaine (AD, windows server 2008R2 + DNS)
  • D’un serveur SharePoint Server 2013 Enterprise EN (+ language pack FR) sans SP1 (Windows Server 2012)

Voici les applications de service déployées sur ma ferme :

02

Une fois les prérequis vérifiés, nous pouvons lancer le SharePoint Management Shell en s’étant au préalable loggé sur le serveur avec le compte qui a les bons droits :

01

Nous allons commencer par créer un nouvel application pool (possible d’utiliser un existant)  et positionner les variables utiles :

#Variables
$servicename = "Machine Translation Service"
$serviceproxyname = $servicename + " Proxy"
$databasename = "SharePoint_MTS"
$databaseserver = "SP2013\SQL2012"
$apppoolname = "SharePoint_MTS_AppPool"
$apppoolaccount = "demo\spappservice"

$apppool = New-SPServiceApplicationPool -Name $apppoolname -Account $apppoolaccount

03

NB : (source Technet) Le compte utilisé par le pool d’applications doit également disposer d’autorisations de contrôle total sur l’application de service de profil utilisateur. Si vous créez un pool d’applications et un compte, veillez à ajouter le compte à la liste des comptes qui peuvent utiliser l’application de service de profil utilisateur, puis accordez les autorisations de contrôle total à ce compte. Pour plus d’informations, voir Limiter ou activer l’accès à une application de service (SharePoint 2013).

Puis l’on va créer l’application de service avec la commande :

New-SPTranslationServiceApplication -Name $servicename -DatabaseName $databasename -DatabaseServer $databaseserver -ApplicationPool $apppoolname –Default

04

05

L’application de service est maintenant créée, il faut démarrer le service sur le serveur :

Get-SPServiceInstance | where-object {$_.TypeName -eq "Machine Translation Service"} | Start-SPServiceInstance

06

07

4/ Configuration du service avec PowerShell

L’application de service étant créée, nous pouvons maintenant la configurer avec PowerShell.

Pour cela, toujours dans le SharePoint Management Shell, nous allons fixer :

  • La fréquence d’exécution du TimerJob de traduction : 15min
  • Le nombre maximal d’essais pour la traduction : 5
  • Le nombre de jours de conservation des travaux de traduction terminés (historique) : 30
  • Le nombre maximal de demandes de traductions synchrones : 100
  • Le nombre maximal de documents à convertir avant le redémarrage de la conversion : 300

Pour cela, nous allons utiliser la commande suivante :

Set-SPTranslationServiceApplication -Identity $servicename -EnableAllFileExtensions -UseDefaultlnternetSettings -TimerJobFrequency 15 -MaximumTranslationAttempts 5 -JobExpirationDays 30 -MaximumSyncTranslationRequests 100 -RecycleProcessThreshold 300 -DisableBinaryFileScan 1

08

Notez bien qu’il faut redémarrer le service juste après, comme précisé dans le Management Shell. Et donc :

Get-SPServiceInstance | where-object {$_.TypeName -eq "Machine Translation Service"} | Stop-SPServiceInstance

Get-SPServiceInstance | where-object {$_.TypeName -eq "Machine Translation Service"} | Start-SPServiceInstance

09

Les paramètres sont bien appliqués dans les paramètres de l’application de service :

10

11

Voilà, c’est bon pour la configuration !

5/ Vérification de la traduction

Pour vérifier le bon fonctionnement de tout cela, j’ai créé une nouvelle collection de sites de type Publishing portal, en Anglais :

13

Sur la page d’accueil du site, on propose directement un lien “Make your site multilingual”. Nous allons pouvoir créer les variantes et leurs étiquettes directement depuis cette page (ou depuis les paramètres du site). Je définis donc une variante par défaut en anglais et une en français :

14

Lors de la création du label français, on propose d’utiliser les service du Machine Translation Service : 15

16

17

Lorsque les étiquettes de variations sont créées, il faut patienter quelques minutes afin que le TimerJob qui créée les hiérarchies soit exécuté. Pour forcer la chose, il vous suffira de lancer ce job manuellement depuis la console d’administration centrale de SharePoint, dans la section “Monitoring” > “Review job definitions” et de trouver le job appelé “Variations Create Hierarchies Job Definition” associé à la WebApplication sur laquelle le portail de publication est créé. Puis de cliquer sur le bouton “Run Now”.

Les variations sont maintenant créées :

18

Et nous pouvons maintenant aller sur les deux variations EN / FR :

19

20

Dans le ruban, dans l’onglet VARIANTES, on a maintenant le bouton “Traduire automatiquement” :

21

 

Un message apparait nous informant que quelques configurations sont encore nécessaires et nous seront notifiés par e-mail :22

A noter au passage que la traduction n’est pas possible depuis le site anglais, puisque c’est la variation “maitre” (pas d’onglet VARIANTES) :

23

Vous pouvez également suivre l’avancement de la traduction via le bouton “Etat de la traduction” dans l’onglet VARIANTES

24

 

Et avec des documents ?  Je me rends donc sur le site Français et j’upload dans la bibliothèque de documents Documents un fichier Word (docx) contenant la recette du cheesecake en anglais. Ce fichier s’appelle cheesecake.docx, et volontairement j’utilise un compte collaborateur sur ma collection de sites appelé “Lenny” :

29

25

A noter la présence du champ “Etat de la traduction” qui est par défaut à la valeur “Traduction effectuée”. J’ai modifié la valeur pour mettre “Traduction demandée”. Pour autant la traduction ne sera pas lancée automatiquement.

Je sélectionne alors l’élément et je clique sur VARIANTES dans le ruban, et je clique sur Traduire automatiquement > Elements sélectionnés uniquement :

26

La traduction démarre :

27

La traduction est terminée, le champ Etat de la traduction est maintenant à la valeur “Traduction effectuée” :

28

Ouvrons le fichier pour voir ce qu’il en est ! Le fichier est “bien” traduit. En effet, cela ne vaudra jamais une belle traduction littéraire, mais cela dit ça reste compréhensible :

30

Vous pouvez également suivre l’état d’avancement de la traduction en utilisant le bouton “Etant de la traduction” dans l’onglet VARIANTES :

31

32

 

Voilà, c’est tout pour aujourd’hui !

[SP2010 – SP2013] – Stocker des données de configuration

Posted on Updated on

Bonjour à tous,

Lorsque nous développons des applications pour SharePoint, il nous arrive bien souvent de vouloir sauvegarder des données de configuration. Ces données peuvent être de simples valeurs (texte, nombre, heure, timestamp, URL, etc.) mais également des données plus complexes comme des objets métiers.

SharePoint 2010 et 2013 (et 2007) nous offrent un certain nombre de possibilités, plus ou moins adaptées suivant les cas. Je vais essayer d’être exhaustif, mais il y en a certainement d’autres !

1/ Liste de configuration

La première solution est de stocker les paramètres de configuration dans une liste de configuration, hébergée sur un site web SharePoint et même sur la console d’Administration centrale de SharePoint.

Pour cela, l’idée et de créer une définition de liste avec Visual Studio qui comprend principalement 2 colonnes :

  • Une colonne Key (clé) : permettant d’identifier uniquement le paramètre, afin de le retrouver plus facilement via une requête CAML par exemple. Sa configuration pourrait être : type Texte, obligatoire
  • Une colonne Value (valeur) : permettant de stocker la valeur au format texte. Sa configuration pourrait être : type Texte (ou texte multiligne), obligatoire
  • Des colonnes supplémentaires comme une description du paramètre, une langue, etc. peuvent être également de la partie.

L’avantage de créer une définition de liste mais également directement une instance (déployées et créées par une Feature, à la racine de la collection de site généralement) et que l’on connait l’identifiant unique du modèle (souvent 10000, 10001, …) que nous fixons nous-même ainsi que l’URL de l’instance créée. Il nous sera donc plus facile de retrouver cette instance par code.

Nous pourrons aussi déployer des données via la création de l’instance via les Rows proposées dans la déclaration de l’instance.

Un autre avantage est de proposer une interface graphique, via le navigateur pour éditer les données… l’’’interface standard de SharePoint !

Et bien sûr, la sécurité et la gestion des droits seront de la partie pour gérer plus finement ces ensembles de clés/valeurs stockées dans la liste.

On peut également penser à développer une classe de service permettant d’accéder à cette instance de liste, récupérer une valeur, ajouter, supprimer, mettre à jour.

Un inconvénient reste l’accès à cette instance de liste depuis un autre site où il faudra recourir à de l’impersonation, des accès Cross-Site et surtout entre WebApplication etc.

Mais également qu’on ne pourra stocker que des types simples ou bien il faudra sérialiser / désérialiser des objets pour les stocker.

Donc pour résumer :

  • Une solution Visual Studio
  • Une définition de liste
  • Une instance de liste
  • Une colonne clé unique
  • Une colonne valeur
  • Des colonnes supplémentaires
  • Des données poussées lors du déploiement
  • Une classe de service pour les accès par code

Voici un extrait d’une classe de service permettant de récupérer l’instance de la liste en question, il vous restera à développer le Get / Update / Add / Delete :

public class ListHelper
{
public static SPList GetParamsList(SPWeb web)
{
SPList resultList = null;
if (web != null)
{
try
{
SPSecurity.RunWithElevatedPrivileges(() =>
{
using (SPSite currentSite = new SPSite(web.Site.ID))
{
using (SPWeb currentWeb = currentSite.OpenWeb(currentSite.RootWeb.ID))
{
SPList list = currentWeb.GetList(ListConstants.ListUrl);

if (list != null)
resultList = list;
}
}
});
return resultList;
}
catch (Exception ex)
{
throw new SPException(“An error has occured. Please contact your administrator : ” + ex.Message);
}
}
else
return null;
}
}

NB : ListConstants est une classe qui contient des paramètres de configuration de ma solution Visual Studio.

2/ Property Bags

Cette seconde solution est également souvent envisagée par les développeurs. Elle permet de stocker des paramètres à plusieurs niveaux :

  • Farm (SPFarm)
  • Web Application (SPWebApplication)
  • Site Collection (SPSite)
  • Site web (SPWeb)
  • Liste (SPList)

Pour faire simple, le property bag est une Hashtable (clé/valeur), sans paramètre supplémentaire, où l’on pourra stocker des types simples sous forme de texte (ou bien en sérialisant / désérialisant les objets).

Les données sont stockées soit dans la base de données de configuration soit dans la base de données de contenu (suivant objet).

Par contre, SharePoint ne propose aucun accès graphique à ces données (pour l’édition par exemple). Toutefois on peut penser au projet SharePoint Property Bag Settings 2010 disponible sur Codeplex : http://pbs2010.codeplex.com/.

Ici également, il faut penser à l’accessibilité des données à partir d’un autre site, d’une autre WebApplication et donc utiliser de l’impersonation, une classe de service pour accéder aux données.

Voici comment mettre à jour une propriété dans le property bag d’un objet SPWeb :

if(web.Properties[“Key”] == null)
{
web.Properties.Add(“Key”, “Value”);
web.Properties.Update();
}

string storedValue = web.Properties[“Key”];
web.Properties[“Key”] = “NewValue”;
web.Properties.Update();

3/ Session / cookie

La troisième solution est de recourir au stockage de données dans la session de l’utilisateur ou dans un cookie ou dans le Viewstate.

Ici, les objets ne sont donc pas stockés dans SharePoint mais bien dans des objets spécifiques à chaque utilisateur et ne pourront pas être partagés entre-eux. Il faudra donc charger les données, les mettre à jour pour chaque utilisateur.

Cela ne correspond donc pas exactement aux cas fonctionnels précédents, mais il est bon de le citer car c’est parfois bien pratique de stocker des données temporaires et qui peuvent permettre d’accélerer le chargement des pages.

Pour le cas de la session, il vous faudra penser à activer la session ASP.Net sur les serveurs IIS hébergeant SharePoint (http://technet.microsoft.com/fr-fr/library/ff607857(v=office.15).aspx).

La session permet de stocker des type simples sous forme de string et nous devrons sérialiser/désérialiser les objets.

Pour stocker une valeur dans la session de l’utilisateur, on peut utiliser le script suivant :

if(HttpContext.Current.Session[“MyKEY”] == null)
{
HttpContext.Current.Session.add (“MyKEY”, “MyVALUE”);
}

4/ Fichiers de configuration

Quatrième option, utiliser le fichier de configuration de la WebApplication : web.config pour stocker des paramètres, ici aussi de type texte. Pour cela, Microsoft fournit une API permettant d”insérer des clés, de le lire, supprimer. En général, on stocke ces paramètres dans la section AppSettings du fichier, ou en créant une section spécifique (qu’il faudra également penser à créer).

Le simple fait de déployer des paramètres à cet emplacement redémarrera le pool IIS afin qu’ils soient pris en compte. Ce redémarrage se fera automatiquement grâce à un Handler positionné par IIS sur le fichier web.config et donc aucune action du développeur ne sera demandée. Attention que cela ne pose pas de problème aux utilisateurs (indisponibilité de quelques secondes / minutes, le temps du redémarrage).

Quelques bugs ont été constatés lors de l’utilisation de cette méthode. Je vous invite à faire une petite recherche à ce sujet.

Généralement, le développeur déploie ces modifications lors de l’activation d’une Feature :

public override void FeatureActivated(SPFeatureReceiverProperties properties) {

SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;

SPWebConfigModification myModification = new SPWebConfigModification();

myModification.Path = “configuration/appSettings”;    myModification.Name = “add [@key=’mySetting’] [@value=’myValue’]”;

myModification.Sequence = 0;

myModification.Owner = “DOMAIN\OWNERLOGIN”;

myModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;

myModification.Value = “”;    webApp.WebConfigModifications.Add(myModification);

webApp.Update();

webApp.Farm.Services.GetValue().ApplyWebConfigModifications();

}

Il faut bien sur penser à nettoyer lors de la désactivation de la Feature !

 

5/ Objets persistés : SPPersistedObject

Cette cinquième méthode, de mémoire, est arrivée avec SharePoint 2010. Elle permet de stocker des objets complexes, de manière hiérarchique, dans une source de données globale à la plateforme, et peut donc être partagée entre les utilisateurs. Ces objets seront stockés dans la base de données de configuration de la plateforme SharePoint (base de donnée SharePoint_Config).

C’est donc une méthode évoluée en regard des précédentes ! Quelques contraintes sont toutefois à remarquer. En effet, les objets stockés doivent hériter de la classe SPPersistedObject, une annotation (GUID) doit être précisée sur la classe (identifiant unique de l’objet), un constructeur par défaut et un constructeur avec deux paramètres (string => nom de l’objet, SPPersistedObject => l’objet en lui-même). et enfin vos propriétés personalisées. Rien d’insurmontable donc !

Quelques avertissement cependant : le hierarchical store n’est accessible qu’au Farm Account de la ferme SharePoint, et donc pas à tous les utilisateurs. Pensez donc à utiliser l’impersonation pour accéder aux données !

NB : les timerjobs SharePoint sont exécutés avec ce Farm Account, c’est donc un moyen pratique de stocker les données de configuration pour ces TimerJobs !

[Guid(“4DB9AF54-1339-424A-BE2D-648940610283”)]
public class MyPersistedClass : SPPersistedObject
{
public MyPersistedClass()
{
}
public MyPersistedClass(string name, SPPersistedObject parent) : base(name, parent)
{
}
[Persisted]
string Value1;
[Persisted]
string Value2;
[Persisted]
int Value3;
}

Et pour accéder aux données :

SPFarm farm = SPFarm.Local;
PersistedClass rootClass = new PersistedClass(“MyFirstItem”, farm);
rootClass.Value1 = “BLA BLA BLA”;
rootClass.Value2 = “PLOP PLOP PLOP”;
rootClass.Value3 = 8;
rootClass.Update(); PersistedClass childClass = new PersistedClass(“MyFirstItemChild”, rootClass);
childClass.Value1 = “YOUPI”;
childClass.Update(); SPFarm farm = SPFarm.Local;
PersistedClass retrievedValue = farm.GetChild(“MyFirstItem”);

 

6/ Cache Distribué (SP2013)

Dans cette sixième méthode, nous allons aborder une nouveauté de SharePoint 2013 : le Cache Distribué (Distributed Cache) qui peut être une bonne alternative pour le stockage d’objets complexes.

En effet ce dernier propose de maintenir en cache des données / objets sur une durée fixée et paramétrable lors de l’ajout des données dans ce cache et de partager ces données entre les différents serveurs de la ferme SharePoint, entre les utilisateurs.

Pour utiliser ce cache, je suis tombé sur une classe toute prête (et testée dans un projet) qui vous permet d’ajouter, lire, supprimer et modifier les données stockées. Vous la trouverez ici : https://github.com/Almond-Labs/SP2013-Starter/blob/master/Source/AL.Sharepoint.Core/Cache/CacheManager.cs

Il vous suffira de recréer cette classe dans votre projet Visual Studio, de référencer les assemblies nécessaires (recherchez les dans le répertoire 15) et de compiler. Ca fait gagner du temps…

Avantage également de cette source, l’auteur fournit des méthodes génériques pour stocker des objets… et donc nous pourrons stocker des objets complexes dans le cache, sans les sérialiser.

A noter dans la méthode Put qu’un TimerSpan est passé en paramètre. Il permet de fixer la durée de conservation des données dans le cache distribué :

public static void Put<T>(string key, T value, TimeSpan timeSpan)

Par exemple, nous appelerons la méthode Put qui permettra de stocker l’objet MyObjectInstance de type MyObject pendant 1h30. :

CacheManager.Put<MyObject> (“MyKEY”, MyObjectInstance, 1,30,0);

Cette méthode a donc l’avantage de stocker directement des objets durant une période fixée, partageable entre les utilisateurs.

 

7/ Application de Service

Cette dernière méthode est un peu bourrine plus complexe. Elle demande le développement d’une application de service (Service Application) pour SharePoint, proposant un magasin de données centralisé à toute la ferme SharePoint. (et même entre fermes avec du multi-tenant).

Pour cela, je vous conseille de vous tourner vers Technet : http://msdn.microsoft.com/en-us/library/gg193964(v=office.14).aspx ou encore http://msdn.microsoft.com/fr-fr/library/gg543112(v=office.14).aspx.

Ce n’est pas chose aisée, mais dans certains cas cela peut répondre à votre besoin. Par exemple nous pourrions imaginer que nous souhaitons stocker un catalogue important de données de configuration, spécifiques à chaque utilisateur.

Nous utiliserons pour cela une base de données spécifique déployée par l’application de  services, des WebServices afin d’accéder aux données depuis chacun de sites, WebParts, composants personnalisés ou même des applications SharePoint (Apps). Sans oublier la possibilité de requêter ce service depuis une application métier, sur des terminaux mobiles, etc.

[SP2010 – SP2013] – Modifier la période de rétention des corbeilles

Posted on Updated on

 

Bonjour à tous,

Un petit article aujourd’hui pour apprendre à modifier la période de rétention de la corbeille utilisateur, sur une Web Application spécifique.

Petit rappel au passage, 2 niveaux de corbeilles sont proposés dans SharePoint (Server ou Foundation), et ce depuis…. presque toujours !

Ces deux niveaux de corbeilles ont des particularités :

Le premier niveau est aussi appelée “corbeille utilisateur” et est donc personnelle à chaque utilisateur. Pour faire le parallèle cela correspond à la corbeille “Windows” de l’utilisateur. Les documents supprimés par un utilisateur dans SharePoint sont déplacés dans cette corbeille où ils resteront pour une durée définie. Par défaut, SharePoint limite cette période à 30 jours et les documents supprimés y sont donc conservés durant cette période. C’est ce paramètre que nous allons apprendre à modifier.

Le second niveau de corbeille est aussi appelé “Corbeille de l’administrateur”. Les documents ayant atteint la période de rétention du premier niveau de corbeille sont déplacés dans cette corbeille (et non pas supprimés définitivement). Ils y sont conservés… non pas dans le temps mais par rapport au quota fixé sur la collection de sites. Par défaut, le ratio est fixé à 50%. Par exemple, si le quota de la collection de sites est fixé à 1Go, l’administrateur pourra conserver 500Mo de documents supprimés. Ensuite les plus anciens documents sont collectés puis supprimés.

 

Nous allons donc voir comment modifier la période de conservation du niveau 1 de la corbeille. Pratique pour automatiser le paramétrage de vos Web Applications par exemple. Oui, parce que ce paramètre est bien disponible sur la Web Application.

Première chose à faire, charger les Cmdlets pour SharePoint. :

If ((Get-PSSnapIn -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null )
{ Add-PSSnapIn -Name Microsoft.SharePoint.PowerShell }
$host.Runspace.ThreadOptions = “ReuseThread”

 

Puis fixer les quelques paramètres nécessaires : Url de la Web Application et rétention souhaitée :

$webAppUrl = “http://URL DE VOTRE WEB APPLICATION”
$ConservationInDays = 90

 

Puis on récupère et on affiche la rétention actuellement appliquée sur la Web Application :

$Webapp = Get-SPWebApplication -Identity $webAppUrl $days = $Webapp.RecycleBinRetentionPeriod

Write-Host(“Conservation actuelle ” + $days + ” jours”) -ForegroundColor Yellow

 

Et enfin modifier la période de rétention si elle est différente de celle fixée :

if($days -ne $ConservationInDays)

{

$Webapp.RecycleBinRetentionPeriod = $ConservationInDays $Webapp.Update()

Write-Host(“La période de conservation a été fixée à ” + $ConservationInDays + ” jours”) -ForegroundColor Green

}

Ce qui donne :

01

02

 

Et pour changer le quota appliqué à la corbeille de niveau 2 (pour le fixer à 30% par exemple) :

$Webapp.SecondStageRecycleBinQuota = 30

$Webapp.Update()

03

 

Et voilà !

[SP2010 & SP2013] – Envoyer les résultats de l’analyseur de santé et d’intégrité par e-mail

Posted on Updated on

 

Bonjour à tous.

Cela fait quelques temps que je n’avais rien publié, je reviens avec un script bien pratique permettant d’envoyer les résultats produits par l’analyseur de santé et d’intégrité de SharePoint (SharePoint Health Analyzer) par e-mail, à l’administrateur par exemple.

Le but étant de ne plus avoir à se connecter à l’administration centrale de SharePoint pour consulter et monitorer l’état de la plateforme, mais d’automatiser cela par un envoi de courrier électronique.

Pour cela, nous allons écrire un script permettant de récupérer le contenu des résultats, récupérer les paramètres d’envoi de courrier électronique et enfin automatiser le tout via une tâche planifiée. Et bien sûr compatible avec SharePoint 2010 et SharePoint 2013 !

1/ Récupérer les paramètres d’envoi d’e-mail

NB : le script doit être exécuté avec un compte possédant les droits nécessaires pour effectuer les opérations. Dans mon cas c’est l’administrateur de ferme SharePoint.

Pour cela, c’est très simple. Nous allons écrire un script PowerShell permettant de récupérer l’instance du site d’administration centrale de SharePoint puis récupérer les paramètres de courrier sortant (Outgoing e-mail parameters).

Pour récupérer la Web Application de l’administration centrale, soit on connait l’URL soit on ne la connait pas.

Dans le cas où on la connait (il faudra la modifier lorsque l’on changera de ferme… et si elle change d’url / serveur) :

$w = Get-SPWebApplication http://sitename

Si on ne la connait pas, et si on veut faire un script portable (mon cas) :

$CAWebApp = (Get-SPWebApplication -IncludeCentralAdministration) | ? { $_.IsAdministrationWebApplication -eq $true }

$CARootWeb = Get-SPWeb -Identity $CAWebApp.Url

Ce qui donne :

01

On récupère bien l’adresse du site Web (racine) de la Web Application contenant la centrale d’administration de notre ferme SharePoint.

Ensuite, nous allons récupérer les paramètres :

  • L’adresse de provenance (From)
  • L’adresse (le nom) du serveur d’envoi d’e-mails (renseigné dans la console d’administration centrale de SharePoint).
$from = $CAWebApp.OutboundMailReplyToAddress

$mailserver = $CAWebApp.OutboundMailServiceInstance.Server.Address

Et également fournir l’adresse où l’on souhaite envoyer le courrier électronique :

$to = "admin.sharepoint@demo.local"

02

2/ Récupérer le contenu du SharePoint Health Analyzer

Le SharePoint Health Analyzer enregistre ses résultats dans une liste SharePoint dont l’adresse est statique (toujours la même), dans le site racine de l’administration centrale SharePoint.

Il suffit donc de se connecter à cette liste, d’en récupérer le contenu pour le formater dans le corps du courrier électronique.

Récupérons l’instance de la liste de résultats :

$list = $CARootWeb.GetList("\Lists\HealthReports")

03

Il nous reste ensuite à récupérer le contenu de cette liste et le formater dans un contenu HTML qui deviendra le corps de l’e-mail. Pour récupérer le contenu, rien ne vaut une petite requête CAML !

Ici nous voulons récupérer seulement les cas qui ne sont pas en réussite (succès), c’est-à-dire les problèmes. Il faut donc filtrer sur un champ qui s’appelle “HealthReportSeverity” (internalName, en anglais dans mon ca), en ne récupérant que les items où la valeur est différente de “4 – Success” :

$where = "<Where><Neq><FieldRef Name='HealthReportSeverity' /><Value Type='Text'>4 - Success</Value></Neq></Where>"
$query = New-Object Microsoft.SharePoint.SPQuery
$query.Query = $where
$items = $list.GetItems($query)

Dans mon cas, je récupère 3 items ($items.Count) :

04

3/ Construire le contenu du courrier électronique

Le contenu étant récupéré, il nous faut construire le corps du message en HTML (très basique !). Pour ma part, je suis parti sur quelque chose de simple: un tableau.

Ce tableau contiendra :

  • Le nom de l’erreur avec un lien pointant vers le détail (vers la page DispForm.aspx de SharePoint)
  • Le Type (catégorie) d’erreur
  • Le contenu du message
  • Le type d’erreur : avertissement, erreur, etc.
  • La date d’exécution (de la tâche planifiée SharePoint)

Nous allons également ajouter un peu de texte avant et après le tableau pour préciser :

  • La date et l’heure courante
  • Le nom du rapport
  • Une signature

On construit l’entête et la signature :

NB : Attention à l’encodage des simples et double quotes !!! => Passer par NotePad / Notepad++, ça vous aidera 🙂

$entete = "Bonjour, voici le rapport d’exécution de l’analyseur de santé et d’intégrité de la ferme SharePoint : " + $CAWebApp.Farm.Name

$titre = "<Title>" + $entete + "</Title>"

$signature = "Bonne journée, <br/><br/>L’équipe SharePoint."

05

Puis nous allons construire le tableau en itérant sur la collection d’Items chargée via la requête CAML :

$corps = "<h2>" + $entete + "</h2><br />"

$corps = $corps + "<table cellspacing='5' cellpadding='5' style='width: 100%;border:1px solid #bbbbbb'>" 
foreach ($item in $items)
{
$itemTitle = $item.Title
$itemUrl = $CARootWeb.Url + ($list.Forms | where { $_.Type -eq "PAGE_DISPLAYFORM" }).ServerRelativeUrl + "?ID=" + $item.ID

$itemSeverity = $item["Severity"]
$itemCategory = $item["Category"]
$itemExplanation = $item["Explanation"]
$itemModified = $item["Modified"]

$corps = $corps + "<tr>"
$corps = $corps + "<td><a href=`"" + $itemUrl + "`">" + $item.Title + "</a></td>"
$corps = $corps + "<td>" + $itemCategory + "</td>"
$corps = $corps + "<td>" + $itemExplanation + "</td>"
$corps = $corps + "<td>" + $itemSeverity + "</td>"
$corps = $corps + "<td>" + $itemModified + "</td>"
$corps = $corps + "</tr>"
}

$corps = $corps + "</table><br /><br />" + $signature

$corpshtml = ConvertTo-Html –Head $titre –Body $corps

06

07

4/ Envoyer un e-mail en PowerShell

Ensuite il nous faut envoyer cet e-mail via des cmdlets PowerShell. Pour cela, nous allons recourir à l’API System.Net.Mail bien connue dans le monde Microsoft :

$CARootWeb.Dispose()
Send-MailMessage –From $from –To $to –Subject $titre –BodyAsHtml $corps –SmtpServer $mailserver

Puis fermer la connexion sur la Web Application :

$CARootWeb.Dispose()

Attention, dans cet exemple j’ai configuré le service SMTP sur mon serveur SharePoint, et en renseignant correctement les paramètres d’e-mails sortants dans la console d’administration centrale de SharePoint.

5/ Automatiser l’envoi par une tâche planifiée Windows

Dernière étape, l’automatisation. Pour cela nous allons créer une tâche planifiée Windows, exécutée sur l’un des serveurs SharePoint de la ferme. Dans mon cas ce sera sur le serveur hébergeant l’Administration Centrale de SharePoint.

Cette tâche planifiée devra lancer PowerShell, puis appeler le script. Il nous faut donc modifier à nouveau ce script pour intégrer l’appel du Snap-In permettant de charger le contexte SharePoint dans PowerShell (ce que fait le Management Shell de SharePoint, automatiquement).

Je vous invite à vous reporter à http://technet.microsoft.com/fr-fr/library/ee806878(v=office.15).aspx.

Nous allons alors ajouter en haut du fichier du script PowerShell :

if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" –ErrorAction SilentlyContinue) -eq $null)
{
Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}

Et enregistrer le tout dans un fichier avec l’extension ps1. Pour moi SPHealthAnalyzerSendMail.ps1, que je tente d’exécuter avec PowerShell (et non pas le Management Shell de SharePoint :

14

 

Voici le fichier obtenu, vous pouvez le télécharger ici : http://1drv.ms/1ot9VJI

Créons donc cette tâche planifiée ! Pour cela, il faut ouvrir l’assistant de création des tâches panifiées de Windows Server :

Dans Démarrer > Outil d’administration > Tâches planifiées :

image

image

 

Cliquer sur “créer une tâche” dans le panneau Actions à droite et donnez-lui un nom, description, et surtout le compte exécutant la tâche (l’administrateur de la ferme dans mon cas) :

image

 

Aller dans l’onglet Triggers (déclencheur) et fournir une planification (schedule), dans mon cas chaque jour à 2h00 du matin :

image

 

Puis aller dans l’onglet Actions et entrer les paramètres :

  • Commande : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
  • Arguments : –command “.\\SPHealthAnalyzerSendMail.ps1”
  • Répertoire de démarrage : Répertoire où est enregistré votre fichier sur le serveur, dans mon cas dans le répertoire 14 == C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14

image

 

Enregistrer le tout, l’assistant vous demande le mot de passe du compte utilisé pour lancer la tâche :

image

 

La tâche est créée :

image

 

Faites un clic droit, Démarrer pour tester :

image

 

Les résultats de l’exécution sont présentés dans la fenêtre en bas :

image

 

Voici l’e-mail reçu… C’est simple et épuré dira-t’on. Si vous rencontrez des problèmes d’encodage pour les caractères accentués par exemple, n’hésitez pas à jouer avec la fonction :

[System.Web.HttpUtility]::HtmlEncode(“MON TEXTE A ENCODER”)

15

 

Il n’y a plus qu’à revoir un peu le Html envoyé dans l’e-mail pour peaufiner, et c’est tout bon !