вторник, 27 ноября 2012 г.

6 правил логирования в SharePoint

Большая часть кода, написанного для SharePoint работает в крайне непредсказуемой среде. Полностью отлаженный на компьютере разработчика код может совершенно “необъяснимо” падать в целевой среде. В этой ситуации очень важно логировать происходящее в вашем коде.
Тема логирования очень любима многими разработчиками. Одни предпочитают писать свои велосипеды, другие пользуются готовыми фрейморками, третье используют возможности ОС.
Если вы разрабатываете решения для SharePoint, то вам необходимо придерживаться следующих правил:
1. Не используйте фреймворки
Даже если вы очень любите log4net, или NLog, или EventLog, или System.Diagnostics.Trace, или пишете свой велосипед, то никогда не используйте ничего из вышеперечисленного.
В SharePoint существует своя система логирования, называемая Unified Logging System (ULS). Если у вас возникает вопрос чем ULS лучше чем <подставьте сюда свой любимый фреймворк>, то вот основные причины:
  • ULS уже используется компонентами SharePoint, используя другую систему вам придется собирать логи в нескольких местах
  • ULS гибко настраивается из Central Administration и PowerShell
  • ULS умеет писать в Windows Event Log, не требуя при этом привилегий
  • ULS обеспечивает сжатие текстовых логов на диске
  • ULS не требует правки web.config (что очень важно для многосерверной фермы)
  • SharePoint умеет собирать логи ULS (и не только их) со многих серверов в одну базу
2.  В простых случаях не делайте ничего
SharePoint сделан так, что необработанные исключения внутри процессов SharePoint попадают в ULS.  В случае возникновения ошибки очень легко будет в ULS найти stack trace, а если включить уровень логированя VerboseEx, то и все запросы к базе со статистикой.
Для просмотра логов удобнее всего применять утилиту UlsViewer, для русской версии Windows Server может понадобиться патч.
Есть одно исключение из этого правила – веб-части. Если веб-часть в full trust падает с ошибкой, то пользователь увидит только страницу ошибки. Причем если ошибка вызвана персонализированными свойствами, то ошибку будет сложно устранить. Хорошим тоном при разработке веб-частей является не вывод исключения, а  отображение информации как ошибку исправить.
3. Используйте SPMonitoredScope везде
Класс SPMonitoredScope очень прост в использовании:


using (new SPMonitoredScope("My Monitored Scope"))
{
    // Do Stuff...
}

В лог пишутся сообщение при входе в scope и при выходе из него, также записывается время выполнения scope. По умолчанию сообщения от SPMonitoredScope  имеют уровень Verbose.
С помощью SPMonitoredScope  также можно логировать и другие параметры быстродействия и потребления ресурсов


using (new SPMonitoredScope("My Scope Name",1000,
   new SPRequestUsageCounter(3),
   new SPSqlQueryCounter(10)))
{
    //Do Stuff...
}
Если внутри scope время выполнения превысит 1000мс, или количество объектов SPRequest (SPWeb и SPSite) превысит 3, или количество запросов к базе превысит 10, то уровень логирования повысится до High. Кроме того в лог попадут фактические значения этих параметров, а также места создания объектов SPSite\SPWeb в виде stack trace и места где происходили запросы к БД.
Но самое важное заключается в том, что SPMonitoredScope попадает в Developer Dashboard.
Если написать такой код:


using (new SPMonitoredScope("My Monitored Scope"))
{               
    using (new SPMonitoredScope("My Sub-Monitored Scope"))
    {
        using (new SPMonitoredScope("My Sub-Sub-Monitored Scope"))
        {
        }
        using (new SPMonitoredScope("Another deep scope"))
        {
        }
    }
}
То в Developer Dashboard можно увидеть следующую картину:

Для запуска панели необходимо использовать команды:

STSADM
Открываем %ProgramFiles%\Common Files\Microsoft Shared Debug\Web Server Extensions\14\BIN и вводим соответствующие команды

Для включения: stsadm -o setproperty -pn developer-dashboard -pv on 
Для выключения: stsadm -o setproperty -pn developer-dashboard -pv off
По желанию (кнопка):  stsadm -o setproperty -pn developer-dashboard -pv ondemand

PowerShell

Для включения: (Get-SPFarm).PerformanceMonitor.DeveloperDashboardLevel = ”On”
Для выключения: (Get-SPFarm).PerformanceMonitor.DeveloperDashboardLevel = ”Off”
По желанию (кнопка): (Get-SPFarm).PerformanceMonitor.DeveloperDashboardLevel = ”OnDemand”
4. Используйте SPDiagnosticsService для записи в лог
Непосредственно в ULS можно писать с помощью класса SPDiagnosticsService.
Запись в Trace Log:


SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
diagSvc.WriteTrace(0,
    new SPDiagnosticsCategory("My Category",
        TraceSeverity.Monitorable,
        EventSeverity.Error),
    TraceSeverity.Monitorable,
    "An exception occurred: {0}",
    new object[] {ex});

В ULS для каждой записи указывается Area и Category. При таком вызове Area будет равна Unknown.
Также можно писать сразу в Event log:


SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
вiagSvc.WriteEvent(0,
        new SPDiagnosticsCategory("My Category",
            TraceSeverity.Monitorable,
            EventSeverity.Warning),
        EventSeverity.Error,
        "Exception occured {0}", new object[] {ex});
5. Наследуйтесь от SPDiagnosticsServiceBase для гибкой настройки логирования
Если вы создаете большой компонент для SharePoint, то имеет смысл создать свой сервис логирования (наследник класса SPDiagnosticsServiceBase). В нем можно определить свои Area и Category, а также более легковесные методы для логирования. В Central Administration и PowerShell можно будет задать уровень логирования для каждой категории или Area.


public class LoggingService : SPDiagnosticsServiceBase
{
    public static string DiagnosticAreaName = "My";
    private static LoggingService _Current;
    public static LoggingService Current
    {
        get
        {
            if (_Current == null)
            {
                _Current = new LoggingService();
            }
  
            return _Current;
        }
    }
  
    private LoggingService()
        : base("My Logging Service", SPFarm.Local)
    {
  
    }
  
    protected override IEnumerable<SPDiagnosticsArea> ProvideAreas()
    {
        List<SPDiagnosticsArea> areas = new List<SPDiagnosticsArea>
        {
            new SPDiagnosticsArea(DiagnosticAreaName, new List<SPDiagnosticsCategory>
            {
                new SPDiagnosticsCategory("WebParts", TraceSeverity.Unexpected, EventSeverity.Error)
            })
        };
  
        return areas;
    }
  
    public static void LogError(string categoryName, string errorMessage)
    {
        SPDiagnosticsCategory category = LoggingService.Current.Areas[DiagnosticAreaName].Categories[categoryName];
        LoggingService.Current.WriteTrace(0, category, TraceSeverity.Unexpected, errorMessage);
    }
}

Дальше просто:


LoggingService.LogError("WebParts", ex.ToString);
6. Используйте сервис диагностики для клиентских приложений
В SharePoint есть веб-сервис /_vti_bin/diagnostics.asmx. Он содержит всего один метод, который пишет данные в ULS в категорию SharePoint Foundation –> Unified Logging Service  с уровнем Verbose.
Если вы создаете приложение на Silverlight или Javascript, то другим образом довольно сложно обеспечить логирование, доступное разработчику и администратору.
В библиотеках javascript, предоставляемых SharePoint, есть функции
ULSOnError(msg, url, line) и ULSSendException(ex). Но просто вызвать их не получится. Перед каждым вызовом функции необходимо присвоить ULS.enable значение true.



ULS.enable = true;
ULSOnError("Hello trace", document.location.href, 0);

PS. Не забывайте что ULS создан для разработчиков и администраторов. Для пользователей иногда тоже надо хранить и показывать некоторый журнал того что происходило. В этом случае лучше всего работают списки.

Взято из: http://gandjustas.blogspot.com/2012/08/5-sharepoint.html

Как писать правильный код для SharePoint

1. Никаких visual web part, используйте sandboxed visual web part (актуален для Visual Studio 2010, в 2012 такие веб-части создаются по-умолчанию)
2. Никаких ссылок на скрипты и css в разметке, используйте классы CssRegistratoin и ScriptLink
3. Получение списков только через web.GetList(SPUrlUtility.CombineUrls(web.Url, listRelativeUrl)
4. Списки для веб-частей лежат в корневом сайте, доступ к которому только через SPContext.Current.Site.RootWeb
5. Никаких вызов конструкторов SPSite и site.OpenWeb без крайней необходимости
6. В JS учитывайте что может быть много экземпляров веб-частей на странице, передавайте в скрипты id элементов с помощью control.ClientID
7. Так как веб-части на страницу могут быть добавлены после постбека – не используйте Page.IsPostBack для проверки первой загрузки
8. Лучший вариант – грузите данные в createchildcontrols
9. Используйте репитеры вместо склейки HTML в коде
10. Вставляйте везде SPMonitoredScope
11. Структурируйте код так, чтобы загрузка данных была отделена от генерации HTML (чтобы можно было кеширование прикрутить)
12. Отключайте по возможности ViewState
13. Обрабатывайте ошибки, чтобы при ошибке в веб-части не падала страница. Надо обрабатывать SPException, FileNotFoundException (сайт не найден, нет доступа), DirectoryNoFoundException (список не найден, нет доступа)
14. Если напишите try с пустым catch – оторву йайтса
15. Ошибки пишите в лог и выводите в красном Literal на страницу
16. Используйте только exception.ToString()
17. Корректно обрабатывайте случаи отсутствия элементов при выводе
18. На странице может быть две одинаковые веб-части и они должны корректно работать

Сделаем Javascript приятнее

.NET разработчики обычно не любят javascript, считают его убогим языком, приводящим к куче ошибок. И они в чем-то правы… Основной причиной называют динамическую типизацию и интерпретируемость javascript. На самом деле разработчика нужна не типизация и компиляция, а проверка кода до запуска и подсказки по коду. Обе возможности доступны в Visual Studio с расширениями.
Проверка кода до запуска
Для этого в галерее расширений visual studio есть два расширения:
  1. JSLint – встроенный в VS инструмент проверки кода javascript, который можно найти по адресу http://www.jslint.com/
  2. Javascript parser – расширение, которое парсит js код на лету (читай показывает ошибки) и предоставляет навигацию по коду.
Эти два расширения позволяют отловить наиболее частые ошибки в коде.


Кроме того рекомендую установить jscript editor extensions чтобы редактор был больше похож на редактор кода C#.
Подсказки по коду Intellisence
Эта возможность доступна начиная с Visual Studio 2008, но о ней до сих пор знает катастрофически малая часть разработчиков.  Чтобы intellicence для js заработал в visual studio, необходимо visual studio дать подсказку откуда брать файлы.
Если вы пишите javascript код в html\aspx\ascx\materpage файлах, то vs автоматически подтягивает все .js файлы из тегов script и серверного контрола ScriptManager. Далее VS парсит файлы и пытается построить подсказку по коду. Если же нужно обновить подсказку, то можно нажать ctrl+shift+j.
Если же код пишется в .js файле, то файлы, которые надо анализировать для построения подсказки надо указать в специальном теге:


///<reference path="c:\scripts\jquery-1.5.1.js"/>
Имя файла можно указать любое, в том числе url с какого-либо CDN.
В VS11 можно не указывать в каждом файле теги script или reference, а задать в настройках самой студии. Но в VS 2010 такой возможности нет.
Также visual studio поддерживает документирующие комментарии для JS, как и для .NET языков. Подробнее описано по ссылке: http://msdn.microsoft.com/en-us/library/bb385682.aspx
Подсказка по коду позволяет набирать код на javascript быстрее и без ошибок в именах классов и функций.
Причем здесь SharePoint?
Клиентская объектная модель SharePoint для JS вполне успешно понимается студией. Это помогает разработчикам писать код работы с ClientOM на js.
Чтобы все заработало нужно подключить следующие файлы:


///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\MicrosoftAjax.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\init.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\core.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Core.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Runtime.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\CUI.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Ribbon.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Exp.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.UI.Rte.debug.js"/>
Причем порядок файлов важен, но какой правильный – никто сказать не может, надо проверять.
И все, дальше можно писать код на js, а студия будет подсказывать имена всех классов и функций клиентской объектной модели.






Весь код:

///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\MicrosoftAjax.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\init.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\core.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Core.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Runtime.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\CUI.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Ribbon.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Exp.debug.js"/>
///<reference path="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.UI.Rte.debug.js"/>
function showWebTitle() {
    var ctx = SP.ClientContext.get_current();
    var web = ctx.get_web();
    ctx.load(web, 'Title');
    ctx.executeQueryAsync(function () {
        alert(web.get_title());
    }, function myfunction(o, ex) {
        alert(ex.get_message());
    });
}
Развертывание скриптов
Самый простой способ сделать ваш скрипт доступным в SharePoint – с помощью sandboxed решения и элемента CustomAction.
Для этого надо создать новый проект, поместить в него один элемент –модуль, поместить в него файл скрипта  и написать в манифесте следующий xml:


  <Module Name="Module1">
  <File Path="Module1\JScript1.js" Url="Module1/JScript1.js" />
</Module>
  <CustomAction Location="ScriptLink"
                ScriptSrc="~site/Module1/JScript1.js"/>
</Elements>
Далее можно нажать f5, написать прямо в строке адреса вызов функции и посмотреть результат. При этом работает интерактивная отладка в Visual Studio.
Скрипты в веб-частях
Если вы собираетесь писать скрипты в веб-частях, то вы можете написать кучу тегов script, аналогично reference, но это приведет к загрузке всех файлов при размещении веб-части на форме, что нежелательно.
Есть очень хороший способ с этим бороться:
Директивы условной компиляции приведут к тому что теги script не попадут в результрующую разметку, а VisualStudio игнорирует эти директивы и нормально показывает intellisence.
Вот код:
 <% #if UNDEFINED %>
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\MicrosoftAjax.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\init.debug.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\core.debug.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Core.debug.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Runtime.debug.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.debug.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\CUI.debug.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Ribbon.debug.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.Exp.debug.js" />
    <script type="text/javascript" src="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\SP.UI.Rte.debug.js" />
    <% #endif %>

Развертывание решений SharePoint с помощью PowerShell (часть 3)

Теперь пришло время поговорить о том, как же оживлять написанные нами скрипты.
Сегодня мы создаем первый скрипт и в процессе разберемся со следующими моментами:
  1. Создаем PowerShell скрипты.
  2. Подписывание скриптов.
  3. Редактирование скриптов.
  4. Создание запланированных заданий.
1. Создание скриптов
Скрипт на powershell является обычным текстовым файлом с расширением “.ps1” поэтому правой кнопкой мыши нажимаем “Создать” – “Текстовый документ”, вводим имя файла, присваиваем ему расширение PS1, нажимаем Enter.
Примечание: По умолчанию, из соображений безопасности, при двойном клике он не запускается а открывается в текстовом редакторе. Проверьте снята ли у вас галка “Скрывать расширения у зарегистрированных файлов”.

Если указанная выше опция не установлена то создание скриптов доступно прямо из окна проводника (вы можете менять расширение на нужное).
Если указанная опция включена то вам придется менять расширение файла любым доступным методом, например из командной строки или из файлового менеджера.

Практика

Создайте в папке C:\TEMP скрипт 1.ps1 со следующим содержимым
Write-Host "Hello!"
Запустите powershell (Win+R, вводим powershell.exe и нажимаем enter), перейдите с папку командой cd C:\TEMP и попробуйте запустить скрипт командой .\1.ps1
В результате powershell ответит ошибкой.
PS C:\temp> .\1.ps1 File C:\temp\1.ps1 cannot be loaded because the execution of scripts is disabled on this system. Please see “get-help about_signing” for more details. At line:1 char:8 + .\1.ps1 <<<< + CategoryInfo : NotSpecified: (:) [], PSSecurityException + FullyQualifiedErrorId : RuntimeException
Подобное поведение связано с тем что в настройках по умолчанию, считается что только команды могут  выполняться интерактивно, а выполнение скриптов запрещено.
Естественно, в случае необходимости уровень выполнения политики (по умолчанию установлен Restricted) можно изменить.

Установка уровня политики ExecutionPolicy

Данное действие можно смело назвать процедурой настройки powershell с целью изменения текущего уровня безопасности который задается параметром «ExecutionPolicy», который можно изменить в реестре.
Он расположен по следующему пути:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell]
Однако, гораздо проще воспользоваться встроенной в powershell командой:
Get-ExecutionPolicy
Существует шесть уровней политики ExecutionPolicy:
Restricted
Это конфигурация PowerShell по умолчанию. Этот вариант означает, что никакие скрипты не могут быть запущены. Единственное, что можно делать в PowerShell при такой настройке — это выполнять одиночные команды.
AllSigned
Эта настройка позволяет выполнять скрипты. Скрипт должен быть подписан сертификатом от доверенного источника. Перед выполнением подписанного скрипта будет выводится предупреждение.
RemoteSigned
Эта настройка позволяет выполнять скрипты, но требует, чтобы все скрипты и файлы конфигурации, загруженные из Интернета, были подписаны сертификатом от доверенного источника. Скриптам, выполняющимся на локальном компьютере, подпись не требуется. Предупреждений перед запуском скриптов не выводится.
Unrestricted
Такая настройка используется чеще всего но она потенциально опасна и крайне не рекомендована. Такая настройка позволяет запускать неподписанные скрипты, включая загруженые из Интернета.
Bypass
Ничто не блокируется и никаких предупреждений не выводится.
Undefined 
Удаляет присвоенную в настоящий момент политику.
В случае необходимости уровень разрешений запуска изменяется командой:
PS C:\temp> Set-ExecutionPolicy RemoteSigned
Execution Policy Change The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose you to the security risks described in the about_Execution_Policies help topic. Do you want to change the execution policy? [Y] Yes [N] No [S] Suspend [?] Help (default is “Y”): y
В зависимости от специфики выполняемых задач, в случаях, когда выполняются преимущественно сценарии собственного написания я бы рекомендовал использовать RemoteSigned. В случае когда выполняются сценарии, полученные из внешних источников стоит использовать  AllSigned.

2. Подписывание скриптов

Для подписывания скрипта нам понадобится программа makecert.exe она входит в состав Microsoft .NET Framework SDK или Microsoft Windows Platform SDK.
Скачиваем пакет, устанавливаем.
Открываем командную строку (Win+r – cmd.exe) и вводим:
cd "C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Bin\"
Создаем нужные сертификаты.
makecert -n "CN=PowerShell Local Certificate Root" -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -sv root.pvk root.cer -ss Root -sr localMachine
makecert -pe -n "CN=PowerShell User" -ss MY -a sha1 -eku 1.3.6.1.5.5.7.3.3 -iv root.pvk -ic root.cer
В результате должны получится два файла root.cer и root.pvk.
После этого проверим, удачно ли всё прошло.
PS C:> Get-ChildItem cert:\CurrentUser\My -codesign
Необходимые сертификаты созданы, остается подписать наш скрипт.
Для этого возьмём из справочного (get-help about_signing) руководства готовый пример кода.
## sign-file.ps1
## Sign a file
param([string] $file=$(throw "Please specify a filename."))
$cert = @(Get-ChildItem cert:\CurrentUser\My -codesigning)[0]
Set-AuthenticodeSignature $file $cert
В качестве переменной $file указываем имя нашего скрипта. Если при подписывании скрипта возвращается ошибка UnknownError то сохраните скрипт в кодировке UTF-8.
Результатом нашей работы получился файл следующего вида:
Write-Host “Hello!”
# SIG # Begin signature block
# MIIOPAYJKoZIhvcNAQcCoIIOLTCCDikCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUukjlMS+jNOxKqyd+oixh1rVp
…..
# Jj+zcmnLYpEMAPjaP8MFurZIgWroOUNFPKLQMKWHau1kmxPnKNrroAD/zG/0dIjg
# W04RHGBT3csXoEcwCA6oOSmyptp6rLtRn9AjR9epWj4SGgBrdiGZIp2vWZkOzGGa
# SIG # End signature block
Попробуем его запустить…
Нас спросят о том что нужно ли этому издателю доверять. Да! Кто бы сомневался… мы себе доверяем и разрешаем все.
PS C:\TEMP\> .\1.ps1
Do you want to run software from this untrusted publisher?
The file C:\TEMP\1.ps1 is published by CN=PowerShell User. This publisher is not
trusted on your system. Only run scripts from trusted publishers.
[V] Never run  [D] Do not run  [R] Run once  [A] Always run  [?] Help
(default is "D"):A

Hello!
В дальнейшем мы можем запускать скрипты, подписанные созданным издателем. Однако, если мы выложим свой подписанный файл в открытый доступ или отправим его товарищам, то при включённой политике AllSigned он получит такое предупреждение:
PS C:\TEMP\> .\1.ps1
The file C:\TEMP\> .\1.ps1 cannot be loaded. The signature of the certificate can not be verified.
Так как для удаленного хоста он подписан неизвестно кем и доверия к нему нет никакого. Поэтому коллегам придется его посмотреть и при необходимости подписать самим.
В случае если в вашей организации уже есть развернутый доменный Центр Сертификации то ваша задача значительно облегчается до следующих пунктов:
  • Получаем доменный сертификат для подписывания (Code Sign)
  • Выполняем следующий скрипт:
    $cert = @(dir cert:\CurrentUser\My -codesigning)[0]
    Set-AuthenticodeSignature $file $cert
    Где $file – путь к скрипту и $cert – объект сертификата.
3. Редактирование скриптов
Для Windows 7 и Windows Server 2008 R2 есть встроенный инструмент под названием Интегрированная Среда Сценариев Windows PowerShell (ISE), которую можно назвать официальным GUI для написания скриптов на powershell. Функционал ISE весьма широк - цветовая разметка синтаксиса, заполнение вкладок, визуальная отладка, совместимость с Юникодом и хорошая контекстная справка.
Установка ISE требует инсталляции .Net 3.5 Service Pack 1 о чем вам сообщают при выборе компонентов.
Для серверной системы проблем с редактированием скриптов так же нет, только требуется доустановить нужный компонент.

Для запуска Windows PowerShell ISE традиционным способом необходимо выбрать “Start”->”Accessories”->”Windows PowerShell”, и нажать Windows PowerShell ISE. Я предпочитаю более простой метод – в командной строке набираем powershell_ise.exe и все.
Для Windows XP powershell_ise вызывается несколько иначе – “Start” -> “Programs” – > “Accessories”->”Windows PowerShell”, и нажать Windows PowerShell ISE.
Если рассматривать сторонние редакторы я могу смело рекомендовать вам PowerGUI от Quest Software.

4. Создание запланированных заданий

Большинство скриптов используются для автоматизации задач администратора и естественно запускать их руками не то что бы лень, скорее это просто неправильно…
Поэтому необходимо такие задания запланировать. Для этого необходимо соответствующим образом настроить запуск вашего скрипта.
Приведу пример для Windows Server 2003:
  • Cоздаем новую задачу, условно назовем ее “Example Task”.
  • Выбираем исполняемый файл: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
  • Задаем имя и интервал повторения.
  • Задаем учетную запись, от имени которой будет выполняться задача.
  • Ставим галочку “Оpen advanced properties…” и нажимаем Finish.
  • В поле “Run” дописываем параметры запуска: -noprofile -command C:\Scripts\TestScript.ps1
Естественно есть и второй вариант, его можно использовать но в ряде случаев это нежелательно из соображений безопасности, например содержимое такого файла можно подменить для того что бы выполнить нужные действия контексте другого пользователя.
  • Создаем cmd-файл для запуска PS-скрипта:
    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -noprofile -command C:\Scripts\TestScript.ps1
  • Задаем имя и интервал повторения.
  • Задаем учетную запись, от имени которой будет выполняться задача.

Развертывание решений SharePoint с помощью PowerShell (часть 2)

После того как мы ранее попробовали создать свой инсталяционный скрипт с нуля, я хочу привести реальный (рабочий) пример и на мой взгляд один из самых лучших скриптов от господина Octavie van Haaften

1. Установка

param([string]$url="http://intranet.octavie.local") 
 
$solutionName = "Octavie.SharePoint.Intranet.wsp"
$solutionFile = ".$solutionName"
 
$solution = Add-SPSolution $solutionFile
 
if ( $solution.ContainsWebApplicationResource ) { 
    Write-Host -ForegroundColor Green "Deploying $solutionName to $url"
    Install-SPSolution -Identity $solutionName -GacDeployment -CasPolicies -Force -Webapplication $url
} 
else { 
    Write-Host -ForegroundColor Green "Deploying $solutionName"
    Install-SPSolution -Identity $solutionName -GacDeployment -CasPolicies -Force
} 
 
$solution = Get-SPSolution $solutionName
 
if ($solution.Deployed -eq $false ) { 
    $counter = 1 
    $maximum = 50 
    $sleeptime = 2 
    while( ($solution.JobExists -eq $true ) -and ( $counter -lt $maximum  ) ) { 
        write-host -ForegroundColor yellow "Please wait..."
        sleep $sleeptime
        $counter++ 
    } 
} 
 
Write-Host ""
Write-Host -ForegroundColor Green "$solutionName is deployed"  

2. Удаление
 
param([string]$url="http://intranet.octavie.local") 
 
$solutionName = "Octavie.SharePoint.Intranet.wsp"
 
$solution = Get-SPSolution $solutionName
 
Write-Host -ForegroundColor Green "Removing $solutionName"
Write-Host ""
 
if( $solution.Deployed -eq $true) { 
 
    if ( $solution.ContainsWebApplicationResource ) { 
        Uninstall-SPSolution -Identity $solutionName -Confirm:$false -Webapplication $url
    } 
    else { 
        Uninstall-SPSolution -Identity $solutionName -Confirm:$false
    } 
    $counter = 1 
    $maximum = 50 
    $sleeptime = 2 
    while( $solution.JobExists -and ( $counter -lt $maximum ) ) { 
        Write-Host -ForegroundColor yellow "Retracting $solutionName. Please wait..."
        sleep $sleeptime
        $counter++ 
    } 
} 
 
Write-Host ""
Write-Host -ForegroundColor Green "$solutionName is retracted."
Write-Host ""
 
Remove-SPSolution -Identity $solutionName -Force -Confirm:$false
 
Write-Host -ForegroundColor Green "$solutionName removed." 
 
Продолжение следует...  

Развертывание решений SharePoint с помощью PowerShell (часть 1)

После прочтения кучи книг, статей и просмотра видео, обойдя все подводные камни, и сделав наконец свое решение на SharePoint у вас появляется вопрос: “а как его запустить у заказчика?” Visual Studio предательски сама активирует фичи решения по F5 и деактивирует при завершении отладки. А как тоже сделать у заказчика? А если у вас доступа на машину заказчика нет?
Для начала стоит подробнее рассмотреть что же требуется для развертывания вашего решения. Обычно развертывание состоит из нескольких шагов:
  1. Добавление WSP-решений уровня фермы
  2. Добавление sandboxed решений
  3. Активация фич
  4. Выполнение дополнительных действий
В идеале дополнительные действия должны совершаться при активации фич, поэтому вычеркнем их из данного сценария.
Вроде все просто. Вооружившись справочником по командам PowerShell  для SharePoint попробуем написать скрипт:

 
Add-SPSolution -LiteralPath "SomePath\MySolution.wsp"
Install-SPSolution -Identity "mysolution.wsp" -GACDeployment 
Enable-SPFeature MyFeature -Url http://localhost/

Сразу куча проблем:
  1. Работает только из SharePoint Management Shell
  2. Add-SPSolution  не понимает относительных путей
  3. На localhost не будет нужного сайта или надо будет разворачивать в другое место
  4. Sandboxed решения так добавить не получится
Чтобы не заставлять пользователя запускать Management Shell можно написать в начале

 
Add-PSSnapin Microsoft.SharePoint.Powershell

Но после этого появляется ошибка при запуске из Management shell…Чтобы обойти проблему нужно проверять загружено ли расширение

 
if(!(Get-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction:SilentlyContinue))
{
    Add-PSSnapin Microsoft.SharePoint.Powershell
}

Вместо hardcoded параметра сайта можно запрашивать параметр в скрипте с помощью оператора param.  А вот чтобы получить текущий путь скрипта надо использовать:


param($siteUrl= $(Read-Host "siteUrl"))
 
$dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$name = "mysolution.wsp"
$path = "$dir\$name"
 
if(!(Get-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction:SilentlyContinue))
{
    Add-PSSnapin Microsoft.SharePoint.Powershell
}
 
Add-SPSolution -LiteralPath $path
Install-SPSolution $name -GACDeployment
Enable-SPFeature MyFeature -Url $siteUrl
 
Так уже лучше, но тут есть подводный камень. Install-SPSolution в многосерверной конфигурации не сразу развертывает решение, фичи становится доступны когда развертывание завершится на всех серверах. Поэтому надо писать так:


$solution = Install-SPSolution $name -GACDeployment
while($solution.Deployed –eq $false)
{
    sleep -s 1
}

Кроме того очень важно делать установочные (и удаляющие) скрипты идемпотентными, чтобы запуск скрипта много раз давал такой же эффект как запуск скрипта один раз. Для этого можно применить тот же подход, что и при загрузке командлетов SharePoint.


param($siteUrl= $(Read-Host "siteUrl"))
 
$dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$name = "MySolution.wsp"
$path = "$dir\$name"
$featureId = "6F9619FF-8B86-D011-B42D-00CF4FC964FF" #GUID from Wikipedia
 
if(!(Get-PSSnapin Microsoft.SharePoint.Powershell -ErrorAction:SilentlyContinue))
{
    Add-PSSnapin Microsoft.SharePoint.Powershell
}
 
$solution = Get-SPSolution $name -ErrorAction:SilentlyContinue
 
if(!$solution)
{   
    $solution = Add-SPSolution -LiteralPath $path
}
 
if($solution.Deployed –eq $false)
{
    Install-SPSolution $solution -GACDeployment
    while($solution.Deployed –eq $false)
    {
        sleep -s 1
    }
}
 
if(!(Get-SPFeature $featureId -Web $siteUrl -ErrorAction:SilentlyContinue)) #or -Site, -WebApplication, -Farm
{
    Enable-SPFeature $featureId -Url $siteUrl
}
 
И подобный код нужно повторить для каждого решения и фичи.
Для sandboxed решений будут использоваться другие командлеты Add-SPUserSolution, Install-SPUserSolution и Get-SPUserSolution. Им необходимо дополнительным параметром передать url коллекции сайтов.
Полученный таким образом скрипт можно запускать как из SharePoint Management Shell, так и из explorer. 

Продолжение следует...

Выполнение JavaScript onload() на SharePoint странице

Для регистрации своего события, которое выполниться при загрузке страницы, в SharePoint есть _spBodyOnLoadFunctionNames массив. Когда страница загружается, обработчик события выполняет каждую функцию, чье имя есть в этом массиве.
<script type="text/javascript">
    _spBodyOnLoadFunctionNames.push("FunctionName");
    function FunctionName()
    {
      // Code
    }
script>

Об обработчиках событий элементов списка

Обработчики событий элементов списка  это классы, унаследованные от SPItemEventReceiver. В классе содержится много методов, почти все они делятся на две группы: Pre-события – методы, оканчивающиеся на -ing, и Post-события – методы, оканчивающиеся на -ed. Все события принимают один аргумент экземпляр класса SPItemEventProperties.
Это теоретический минимум, который необходимо знать для начала.

Коварные AfterProperties и BeforeProperties

Класс SPItemEventProperties содержит пару свойств: AfterProperties и BeforeProperties. На первый взгляд очень хорошие свойства, которые позволяют с небольшими усилиями реализовать множество сценариев. Но на деле все не так…
  1. Для начала необходимо запомнить, следующие таблицы:
    Список BeforeProperties AfterProperties properties.ListItem
    ItemAdding Пусто Новые значения null
    ItemAdded Пусто Новые значения Новые значения
    ItemUpdating Пусто Новые значения Старые значения
    ItemUpdated Пусто Новые значения Новые значения
    ItemDeleting Пусто Пусто Старые значения
    ItemDeleted Пусто Пусто null

    Библиотека BeforeProperties AfterProperties properties.ListItem
    ItemAdding Пусто Пусто null
    ItemAdded Пусто Пусто Новые значения
    ItemUpdating Старые значения Новые значения Старые значения
    ItemUpdated Старые значения Новые значения Новые значения
    ItemDeleting Пусто Пусто Старые значения
    ItemDeleted Пусто Пусто null

    Поведение списков и библиотек документов разное и отличается от предполагаемого.
    Как видите из таблиц выше, BeforeProperties почти бесполезное свойство.
  2. AfterProperties и BeforeProperties содержат свойство ChangedProperties. Это свойство показывает измененные поля в самой коллекции, а не в элементе.
  3. Свойство-индексатор AfterProperties и BeforeProperties принимает Internal Name поля, а не Display Name как в properties.ListItem.
  4. AfterProperties и BeforeProperties поддерживают нетипизированный IEnumerable, но нигде в документации не указано какого типа возвращается перечисление. Опыты показывают что возвращается DictionaryEntry.
  5. AfterProperties и BeforeProperties возвращают все значения в виде строк. Кроме того для boolean типа поля может быт возвращено "-1" в качестве значения, а строки возвращаются в универсальном формате и при парсинге автоматически переводятся в текущую локаль.
  6. При изменении элемента списка в коде в AfterProperties попадают только измененные значения, а при сохранении формы – все значения формы.
  7. При сохранении формы с RichText полем в AfterProperties попадает HTML со всеми заглавными буквами в названиях тегов.

Отмена действия

Pre-события позволяют отменить действие. Для этого необходимо в properties.Cancel присвоить true, присвоить необходимые значения свойствам properties.Status и properties.ErrorMessage. Но и тут есть особенности:
  1. Если вы собираетесь отменить действие, то не вызывайте базовую реализацию метода-обработчика. Иначе отмена не произойдет.
  2. Если вы поставите статус CancelWithError, то будет выкинуто стандартное исключение, которое в режиме отладки отображается желтым экраном смерти.
  3. Если хотите показать свое сообщение об ошибке, то сделайте CancelWithRedirect, но учтите что в таком случае управление не вернется к вызывающему коду.
  4. Если необходимо выполнение разных способов отмены, то анализируйте SPContext и его свойства FormContext и ViewContext.
  5. Отмена работает всегда, для любой учетной записи, в том числе системной. Желательно позволять выполнять действие (не отменять его) администратору коллекции сайтов и учетной записи "SHAREPOINT\system".

Конкурентное выполнение обработчиков событий

Пайплайн обработки событий, картинка ниже кратко его иллюстрирует.

Как видно обработчики Post-событий могут запускаться параллельно. Если будут параллельно запускаться несколько обработчиков, изменяющих сам элемент, то может появиться состояние гонки. Класс SPListItem снабжен механизмом так называемой “оптимистичной конкуренции”. В случая если другой потом успел поменять значение в базе между моментом считывания и записью данных, то выпадет исключение. Но исключения в пост-обработчиках не очень эффективны, так как нет возможности как-либо сигнализировать об ошибке, если только это не синхронный обработчик.
  1. По возможности не изменяйте элемент списка из асинхронного пост-обработчика.
  2. Перехватывайте SPListDataValidationException. Если поймали такое исключение, то выполните properties.InvalidateListItem, а потом снова код обновления элемента.
  3. Используйте следующий блок кода в пост-обработчиках, чтобы не вызвать их циклического запуска

    try
    {
        this.EventFiringEnabled = false;
        //...здесь вызов Update...
    }
    finally
    {
        this.EventFiringEnabled = true;
    }
  4. Также можно использовать SystemUpdate, чтобы не вызывать обработчики событий и не менять время последнего изменения элемента.

К сожалению это далеко не все грабли, которые встречаются при обработке событий. С прочими граблями можно ознакомиться тут:http://msdn.microsoft.com/en-us/library/aa979520.aspx