Sharepoint, PowerShell и пара сотен нервных клеток

“Слався Шел, слався Шел” (с)
Перефразированная цитата из фильма MiB

Все мы любим Sharepoint. Он любит нас, но “какой-то своей любовью”. С сегодняшнего дня я еще очень люблю PowerShell. Теперь по порядку.

Как известно, Sharepoint не очень хорошо относится к локализации. А вернее он её ну совсем не любит. Н-р вот такой кусок кода запросто может выдать ошибку:

using (SPSite site = new SPSite("http://localhost"))
{
     using (SPWeb web = site.OpenWeb())
     {
           SPListItem item = web.Lists["Документы"].GetItemById(1);
           object fieldValue = item["МегаПоле"]; //вот тут будет ошибка
     }
}

Всё дело в том, что поля можно получать только по внутреннему имени. А внутреннее имя крайне не любит русские буквы, и заменяет их кодами. Н-р в нашем примере item["МегаПоле"] принимает вид item["_x041c__x0435__x0433__x0430__x04"]. Не очень читаемо. По этой причине все разработчики создают поля с внутренними именами на английском.

Но тут есть еще одна засада. Создали мы столбец, дали ему внутреннее имя “MegaField”, а DisplayName дали ему “МегаПоле”, добавили его в столбцы узла. Но вот нам захотелось добавить это поле в список (или в тип содержимого, не важно). При добавлении из веб-интерфейса в списке создается новый столбец, потомок нашего. А столбцы создаются, используя DisplayName. Т.е. в списке наш столбец опять будет иметь имя "_x041c__x0435__x0433__x0430__x04". Приходится перед добавлением переименовать столбец в его внутреннее имя, добавлять, после чего опять возвращать имя обратно. Но это мелочи. Главное в том, что если вы уже добавили столбец, поменять ему внутреннее имя можно будет только удалив столбец из списка (или из типа содержимого), что означает удаление всех данных этого столбца. Не радостная картина.

Так вот сегодня мне достался худший из таких случаев – в системе документооборота, с несколькими рабочими процессами и EventReceiver-ами, в один из списков было добавлено поле с русским DisplayName. В результате, в части списках поле нужно было получать как item[“StaticFieldName”], а в этом одном – item["_x041c__x0435__x0433__x0430__x04”]. Но весь код, естественно, оперировал только с англоязычными названиями. Хуже всего было то, что это заметили после нескольких месяцев использования, и это поле фигурировало уже в нескольких сотнях записей. Еще одним отягчающим обстоятельством было то, что этот список был библиотекой документов с включенной версионностью и требованием извлечения файлов перед редактирование.

Передо мной была поставлена задача – заменить столбец на англоязычный, при этом не потеряв его содержание. Сервер был боевой, т.е. ни о каких установках дебагеров, а уж тем более VisualStudio, речи просто не велось. И вот тут я вспомнил про PowerShell.

После некоторого раздумия был разработан следующий алгоритм:

  1. Проходим по всему списку, для каждого элемента получаем строку в виде “%ID%|%FieldValue%”, и записываем эти строки в файл.
  2. Удаляем столбец из списка, добавляем его с английским названием
  3. Считываем строку из файла, находим элемент по ID и записываем его свойство
  4. Для каждого элемента вызываем SystemUpdate(false), чтобы сделать обновление без изменения версии (в том числе и в не извлеченных элементах)

В итоге у меня получились вот такие скрипты:

GetValues.ps1

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") | Out-Null

$site = New-Object Microsoft.Sharepoint.SPSite("http://localhost")
$web = $site.OpenWeb("/doccenter")

$web.Lists["Документы"].Items | foreach {
    $val = $_["_x041c_x0435_x0433_x0430_x04"]

    if($val -ne $Null)
    {
    $temp = $_.ID.ToString() + "|" + $_["_x041c_x0435_x0433_x0430_x04"].ToString()
    Write-Output $temp
    }    
}

SetValues.ps1

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") | Out-Null

$site = New-Object Microsoft.Sharepoint.SPSite("http://localhost")
$web = $site.OpenWeb("/doccenter")
$list = $web.Lists["Документы"]

Get-Content dump.txt | foreach {
    $ar = $_.Split("|")
    
    $item = $list.GetItemByID($ar[0])
    $item["StaticFieldNames"] = $ar[1]
    $item.SystemUpdate($False)
}

Порядок работы:

  1. Запускаем скрипт .\GetValues.ps1 | Out-File dump.txt
  2. Проверяем, что значения получены верно
  3. Удаляем столбец
  4. Добавляем его заново, предварительно поменяв DisplayName на английский
  5. Запускаем скрипт .\SetValues.ps1
  6. Проверяем результат (н-р можно заново запустить GetValues, изменив в нем имя столбца – данные на выходе должны совпасть)
  7. Переименовываем столбец обратно

С учетом того, что поле у меня было кастомное, на базе MultiValue, а документов было более 800, я считаю, что отделался легким испугом. За что спасибо PowerShell!

PS Скрипты далеки от идеалов программирования, но с учетом того, что были сделаны за десять минут и, главное, отлично работают, вполне подходят :)

Реклама

8 Responses to Sharepoint, PowerShell и пара сотен нервных клеток

  1. Artem says:

    А кто полгода назад рассказывал, что не понимает, зачем нужен PowerShell, когда есть C#? =))))

  2. Anton says:

    Будем говорить честно, здесь отличия от шарпа минимальны. И PS использовался только в связи с тем, что не было студии.Плюс SCSM, там всё на PS.

  3. Vasily says:

    >PS Скрипты далеки от идеалов программирования, но с учетом того, что были сделаны за десять минут и, главное, отлично работают, вполне подходят :)Этим скрипты и отличаются от "языков программирования". Можно 2 часа писать красивый код на C# который сделает всё за 1 секунду, или написать некрасивый, медленный но работающий скрипт за 10 минут, который справится с задачей за 5 секунд ;)Но до кучи пара замечаний на будущее ;)Было бы удобно не прописывать имя поля (да и другие параметры) в скрипте, а сделать просто первой строкой:param ($FieldName = "_x041c_x0435_x0433_x0430_x04")тогда можно будет вызывать и без параметров (значение по умолчанию указано), и с ними: .\\GetValues.ps1 -field TestWrite-Output можно опускать, он и так подразумевается по умолчанию. То есть можно писать неWrite-Output $tempа$temp

  4. Vasily says:

    Антон, мне доставляют огромное удовольствие такие сравнения PS с другими языками. C#-овцы заявляют что он мало отличается от c#, перловцы заявляют что это перл, а пхпшники орут про содранный пхп :)Авторы PS знали все эти языки (сюрприз). Они постарались взять всё лучшее от них (судя по отзывам неплохо получилось ;)), и сделали из этого шелл+скриптовый язык.>не было студии.Ну вы ведь знаете что компилятор c# в комплект фреймворка входит? Так что если вам c# всегда-всегда удобнее PS, то отсутствие студии за отмазу не канает ;)>Плюс SCSM, там всё на PSПроще сказать какие продукты пока _не_ на PS :)

  5. Ruslan says:

    > Антон, мне доставляют огромное удовольствие такие сравнения PS с другими языками. C#-овцы заявляют> что он мало отличается от c#, перловцы заявляют что это перл, а пхпшники орут про содранный пхп :)Там есть команда ls, поэтому это линукс. Очевидно же.

  6. Anton says:

    Вась, ты меня не так понял :)Когда я говорил, что ЗДЕСЬ отличия минимальны — имелось ввиду именно в данном скрипте, т.е. мне не пришлось особо что-то изучать. Фактически ведь текст скрипта повторяет текст кода на C#, как если бы я его делал в Студии (конечно с поправкой на синтаксис). Я не сравнивал сам PS и C#.Студия — это ведь не компилятор, а еще и дебагер такой не хилый ;)За замечания — спасибо :)Про продукты, которые не на PS — OpsMgr, SCCM, Sharepoint (десятка пока еще не вышла). Думаю не мало.OpsMgr конечно есть поддержка PS, но в здравом уме его врядли будут сильно использовать.

  7. Vasily says:

    OpsMgr — уже с полноценной PS интеграцией. Да, API там не целикомиполностью PS, но это и не обязательно :). Почему в здравом уме нельзя его сильно использовать — не понял :) SCCM просто отпросился, это вопрос времени. В VNext уже койчего по мелочи видел. Короче будущее зо нами! ;)Руслан, думаешь пошутил? А люди верят http://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:Windows_PowerShell

  8. Anton says:

    Ну, что будет — мы еще увидим. Мы же про сейчас говорим.Про SCOM — один скрипт (пустой) за PS, запущенный на агенте или MS весит 30 метров. Один пустой скрипт на VBScript весит меньше метра. В секунду агент, а тем более MS, может запускать далеко не один скрипт.PS удобен, когда нужно какие-то дейтсивия автоматизировать.И чтоб не писать всё это на C#, можно сделать на PS. Я даже может напишу об этом че-нить.То, что за PS большое будущее я не отрицаю. Слишком много в него вложено.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: