Sharepoint и PowerShell – скриптописание или программирование для администраторов. Часть 1.
28.01.2010 2 комментария
Вступление
Когда речь заходит о Sharepoint и расширении его функционала за счет создание рабочих процессов, обработки событий и прочего, многие администраторы сразу отмахиваются от всего этого с формулировкой “Уууу… Это же программировать надо!”. При этом эти же администраторы пишут скрипт на VBScript,JScript и Powershell, правда называя это не программированием, а скриптописанием. Так им проще. И понятия C#, C++ и т.д. вызывают священный трепет или откровенную боязнь. Меня всегда несколько удивляло. Возможно, мне в этом плане повезло больше – свою деятельность в ИТ я начинал именно с программирования, и лишь намного позже занялся администрированием.
Несколько лет тому назад я начал изучать язык Lua, особенностью которого было то, что этот на 100% скриптовый язык легко можно “встроить” в любое приложение. Т.е. при запуске откомпилированного приложения будет происходить обращение к тексту скрипта, который лежит в открытом виде и может быть легко отредактирован конечными пользователями. Примерно тогда же появился и PowerShell, который, с некоторыми оговорками, тоже умел такое делать. Но это было давно, и тогда главным отличием для меня между Lua и Powershell была производительность – выполнение скрипта в Lua отличалось от выполнения полностью откомпилированного кода на десятые доли процента (с учетом, что речь идет о С++ и его компиляторе, это действительно впечатляло). О производительности PowerShelll, думаю, лучше не рассуждать. Именно тогда родилась идея использовать скрипты внутри Sharepoint.
Собственно многие, кто занимается разработкой для Sharepoint, могут сказать “Зачем такой изврат?”. Аргументов несколько:
- Как я уже писал – доступность скриптовых языков для администраторов
- Отсутствие необходимости делать деплой проектов для серверах Sharepoint. Что очень помогает – больше не нужно перегружать IIS или пул приложений, меньше простоев – больше довольных пользователей.
- Отсутствие версий библиотек. С одной стороны это не всегда удобно, с другой — просто меньше головной боли.
- Возможность быстрого внесения изменений в функционал
И это далеко не всё. Единственное, что меня тогда остановило – отсутствие вменяемой библиотеки для работы Lua с .NET. Была библиотека LuaInterface, но её реализация “хромала” из-за использования не безопасных вызовов (читай “чистых” С-шных методов). Правда позже появилась версия LuaInterface за номер два, которая была полностью переписала на CLI, и она уже работала стабильно, но к этому моменту у меня в Sharepoint всё было написано на чистом C#, поэтому интеграция Lua и Sharepoint так и не состоялась.
С тех пор прошло немало времени, вышел PowerShellv2. И вот когда в очередной раз мне пришлось писать проект для Sharepoint, я вспомнил о своей давней идее. И опять выбор стоял между Lua и PowerShell. И выиграл PowerShell, причин тому много:
- Адаптация кода. Синтаксис PS и С# во многом схож, поэтому адаптировать уже написанный код гораздо проще. Lua похож на C, поэтому фактически пришлось бы переписывать всё заново.
- Поддержка .NET. Всё-таки .NET и PS это близнецы-братья, поэтому тут даже рассуждать нечего.
- Поддержка Sharepoint. К моменту запуска проекта была создано большое кол-во сторонних средств для интеграции PS и Sharepoint.
- Производительность. Lua конечно быстрее, но Sharepoint сам по себе мягко скажем не система реал-тайм обработки данных, поэтому я бы всё равно уперся в производительность .NET
Перед тем, как перейти к практической реализации, давайте подумаем, что обычно приходится программировать для Sharepoint. Это будут на 100% только мои рассуждения, у каждого может быть свой взгляд на этот вопрос:
- Workflow (Рабочие процессы). Это наиболее часто используемые элементы. К сожалению, Sharepoint Designer не всегда позволяет реализовать все задачи (н-р он просто не умеет делать State Machine worflow). Но РП – не самые лучшие кандидаты на скриптование в силу их архитектуры. Мы можем конечно использовать частично PowerShell, но он компиляции нас это не спасет.
- WebPart (веб-части) – компоненты страниц, которые позволяют отбражать на странице некую информацию. В этом аспекте, как говориться, всё сделано до меня. Есть масса реализация веб-частей, которые могут выполнять скирпты PowerShell
- List and ListItems Event Receivers (События списков и элементов списков). Крайне полезный, но недооцененный инструмент. Для PowerShell есть уже замечательное готовое решение iLoveSharepoint SharePoint PowerEventReceivers, о котором я напишу чуть позже.
- Timer Jobs (Задания таймера). Если вам нужно запускать операции в Sharepoint с какой-то периодичностью – это тот инструмент, который вам нужен. К тому же он – первый кандидат на скриптование
Сразу оговорюсь, что в статье я буду рассматривать только вызовы скриптов из самого Sharepoint. Про скрипты для Sharepoint, которые запускаются конечным пользователем, в интернете есть масса материала. Еще одно замечание для больших любителей и знатоков PowerShell – представленный код в статье носит скорее ознакомительный характер, и написан человеком, который много лет изучает C#. Соответственно там, где можно использовать конструкции C# вместо аналогов из PowerShell, будут использованы версии C#. Например я пишу [dateTime].Now вместо Get-Date, foreach() вместо | % {} и т.д.. На логику скрипта это никак не влияет. Также, если кто-то считает, что определенные конструкции можно заменить на более короткие или быстрые – не стесняемся, предлагаем.
Timer Jobs и PowerShell
Timer Jobs – основа основ в Sharepoint. Если не работает служба таймера – не работает добрая половина функционала Sharepoint. Фактически же Timer Job – это система планировщика, шедулера. Есть задачи, у каждой задачи есть расписание. Реализовать собственный Timer Job очень просто – достаточно написать класс, наследованный от SPJobDefinition, переопределить метод Execute и затем зарегистрировать его в Sharepoint (например с помощью TimerJobManager или скриптом).
Для того, чтобы выполнять скрипты PowerShell, нам необходимо:
- Создать класс-шаблон для запуска скриптов PowerShell
- Написать нужный скрипт
- Создать нужное кол-во задач в Sharepoint на базе нашего класса с помощью TimerJobManager, передав как параметр нужный скрипт
Для того, чтобы создать простейший класс вам потребуется (хотя вы можете этого не делать – я выложил готовый класс для использования):
- Установить Visual Studio 2008 (любая редакция, даже Express)
- Установить PowerShell V2 (можно конечно и V1, но зачем)
- Установить Powershell SDK V2 (не обращайте внимание на название файла – это не только примеры, но и сам SDK)
- Создать проект в VS2008 Class Library, используя .NET версии 3.5
- Добавить ссылки (Add reference) на “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI\Microsoft.SharePoint.dll” и “C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\System.Management.Automation.dll”
- Наследовать класс от Microsoft.SharePoint.Administration.SPJobDefinition
- Реализовать конструкторы и метод Exeсute
- Подписать проект, чтобы у него было Strong name (в свойствах проекта – закладка Singing, чекбокс Sign the assembly)
Подробнее о создании Timer Job можно почитать в MSDN.
Приведу код моего класса полностью:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using Microsoft.SharePoint;
6 using Microsoft.SharePoint.Administration;
7 using System.Management.Automation.Runspaces;
8 using System.IO;
9
10 namespace SPPowerShellJob
11 {
12 public class SPPowerShellJob : SPJobDefinition
13 {
14 /// <summary>
15 /// Текст скрипта
16 /// </summary>
17 [Persisted]
18 public string ScriptText = "";
19 /// <summary>
20 /// Локальный путь на сервере (полный) к скрипту
21 /// </summary>
22 [Persisted]
23 public string ScriptLocalPath = "";
24
25 private const string JobName = "PowerShell Job";
26
27 #region Constructors
28 public SPPowerShellJob() : base() {
29
30 }
31
32 public SPPowerShellJob(SPWebApplication webApp)
33 : base(JobName, webApp, null, SPJobLockType.Job)
34 {
35 this.Title = JobName;
36 }
37
38 public SPPowerShellJob(string name,
39 SPWebApplication webApplication,
40 SPServer server,
41 SPJobLockType lockType)
42 : base(name, webApplication, server, lockType)
43 {
44 this.Title = name;
45 }
46 #endregion
47
48 public override void Execute(Guid targetInstanceId)
49 {
50 string _scriptText = "";
51
52 if (String.IsNullOrEmpty(ScriptText))
53 {
54 //тело скрипта не указано, так что пытаемся считать его из файла
55 //Я не делаю обработку исключений, т.к. эти может заниматься и сам Sharepoint
56 //По хорошему конечно надо бы её делать
57 FileInfo scriptFile = new FileInfo(this.ScriptLocalPath);
58 StreamReader reader = scriptFile.OpenText();
59 _scriptText = reader.ReadToEnd();
60 reader.Close();
61 }
62 else
63 {
64 _scriptText = this.ScriptText;
65 }
66
67 Runspace runspace = null;
68 try
69 {
70 //Стандартная инициализация PowerShell
71 runspace = RunspaceFactory.CreateRunspace();
72 runspace.Open();
73 Pipeline pipeline = runspace.CreatePipeline();
74 runspace.SessionStateProxy.SetVariable("this", this);
75 pipeline.Commands.AddScript(_scriptText);
76
77 //Обычно Invoke вызывают, чтобы получить выходные данные
78 //но нам это совсем не нужно — у нас вся логика в скрипте
79 //Так что просто запускаем скипт
80 pipeline.Invoke();
81
82 }
83 catch (Exception er)
84 {
85 //Тут по желанию можно сделать обработку событий.
86 //Н-р запись в журнал системы или (для MOSS) в PortalLog.LogString()
87 throw new Exception("Error in SPPowerShellJob: " + er.Message, er);
88 }
89 finally
90 {
91 if (runspace != null)
92 {
93 runspace.Close();
94 runspace.Dispose();
95 }
96 }
97 }
98 }
99 }
Задание имеет два параметра – ScriptText и ScriptPath. Подразумевается, если указан текст скрипта, то выполнять надо его. Если текст не указан – читать скрипт с диска.
Собственно самое интересное, касаемо PowerShell происходит в строках 71-80. Мы создаем Runspace (окружение), открываем, создаем поток команд, добавляем в поток наш скрипт и запускаем на выполнение. Функция Invoke() возвращает значение, которые возвращает ей скрипт, но в нашем случае использовать их негде. Обратите внимание – мы в окружение передаем переменную this, которую затем можно использовать в скрипте ($this).
Собственно вот и всё. Наш класс готов, можно добавлять его в Sharepoint. Для этого необходимо добавить библиотеку в GAC (в Проводнике перетащите библиотеку в папку C:Windows\assembly), после чего установите TimerJobManager, перейдите в Центр Администрирования –> Операции (1) –> Manage Timer Jobs, выберите представление “Веб-приложение” (2), выберите нужное приложение (3) и нажмите нажмите Add Job (4):
Вам будет предложено ввести:
- Полное имя класса, которое включает в себя Namespace.Class, AssemblyName, Vesrion, Culture, PublicyToken. Н-р для моего класса это SPPowerShellJob.SPPowerShellJob, SPPowerShellJob, Version=1.0.0.0, Culture=neutral, PublicKeyToken=98bb5bf49f9d34cb.
- Имя задачи (которое будет отображаться в списке задач). Вводите любое понятное, но необходимо что бы оно было уникальным
- Тип блокировки (обычно это Job, см. документацию)
После этого нажмите Next, и, если всё в порядке, вы увидите окно с расписанием и параметрами задачи:
Добавите текст скрипта или путь (локальный относительно сервера, н-р c:\Scripts\TestScript.ps1), задайте нужное расписание и сохраните скрипт. Вот собственно и все.
Теперь немного о написании скриптов. Для примера приведу код, который всегда приводится, когда речь идет о заданиях таймера – код обхода веб-приложения и открытия каждого узла (это делает для того, что бы узел был всегда в кэше). Код на C# есть в MSDN:
1 public override void Execute(Guid targetInstanceId)
2 {
3 foreach (SPSite siteCollection in this.WebApplication.Sites)
4 {
5 WarmUpSiteCollection(siteCollection);
6
7 siteCollection.RootWeb.Dispose();
8 siteCollection.Dispose();
9 }
10 }
11
12 private void WarmUpSiteCollection(SPSite siteCollection)
13 {
14 WebRequest request = WebRequest.Create(siteCollection.Url);
15 request.Credentials = CredentialCache.DefaultCredentials;
16 request.Method = "GET";
17
18 WebResponse response = request.GetResponse();
19 response.Close();
20 }
Вот код на PowerShell, который нам необходимо вставить как параметр скрипта в наше задание:
1 function WarmUpSiteCollection ($siteCollection)
2 {
3 $request = [System.Net.WebRequest]::Create($siteCollection.Url)
4 $request.Credentials = [System.Net.CredentialCache].DefaultCredentials
5 $request.Method = "GET"
6
7 $response = $request.GetResponse()
8 $response.Close()
9 }
10
11 foreach ($siteCollection in $this.WebApplication.Sites)
12 {
13 WarmUpSiteCollection $siteCollection
14 $siteCollection.RootWeb.Dispose()
15 $siteCollection.Dispose()
16 }
Как видите, отличий не много.
Теперь у нас готов класс, который мы можем использовать в любое время добавлений заданий таймера в Sharepoint. Что можно с этим сделать – зависит от вашей фантазии. У меня, например, реализовано оповещение о приближающихся сроках по задачам и о просроченных задачах, плюс разные мелочи.
Собственно, выкладываю библиотеку класса. Её надо перетащить в c:\Windows\assembly в проводнике или зарегестрировать с помощью gacutil, после чего перезапустить службу Sharepoint Timer (SPTimerV3):
В следующей части я расскажу и событиях списков и элементов списков.
>Обратите внимание на вызов функций, в которые не передаются значения.Обратил, там ошибки :)Конструкция чтото::метод() используется для вызова статических методов класса. Обычные вызываются через точку: чтото.метод()Если не указывать в конце метода (), даже если он без параметров — метод вызван не будет, просто будет возвращено его описание, этакий мини рефлектор :)Итого, 7,8 строки должны быть:$response = $request.GetResponse()$response.Close()и 14,15:$siteCollection.RootWeb.Dispose()$siteCollection.Dispose()Вообще здорово, жду продолжения про вебпарты и эвентресиверы :)
Пасибо, поправил.