четверг, 5 ноября 2015 г.

Fractal Provisioner для создания ваших пакетов

Обратите внимание на новый подход к использованию стандартных кастомизаций, таких как настройка иерархии сайтов, настройка списков и веб-частей. Теперь вы можете использовать инструмент для создания пакетов и однажды задав модель, развертывать ее многократно. Например, разработав типовое решение, вы можете развертывать его отдельно для каждого потенциального клиента. Или, разработав структуру в тестовой среде, вы можете легко развернуть ее второй раз в продуктивной среде.

https://fractalprovisioner.codeplex.com

суббота, 2 мая 2015 г.

Упрощение Single Sign On в Office 365

На просторах Интернета можно найти множество инструкций по настройке Single Sign On в Office 365. Вот один из хороших примеров:

https://technet.microsoft.com/en-us/magazine/jj631606.aspx

Но почти все они скрывают одну неприятную правду: при обычном употреблении пользователю все же приходится вводить имя пользователя, или как минимум любое имя пользователя но с корректным доменом, т.к. именно по домену страница перенаправляет автоматически пользователя на соответствующий сервис федерации, развернутый в инфраструктуре компании.

image

Я считаю, это не очень удобно для пользователя, и к счастью, я нашел обходной путь для этого.

Мы можем указать определенный Url в качестве домашней страницы браузера с помощью групповой политики. Например, если мы хотим направить пользователя на узел SharePoint, как https://sapozhkovtest.sharepoint.com, а суффикс upn (домен пользователя) sapozhkov.net, тогда нам нужно указать следующий адрес в качестве страницы по умолчанию с помощью групповой политики:

https://login.microsoftonline.com/?whr=sapozhkov.net&wreply=https:%2f%2fsapozhkovtest%2esharepoint%2ecom

В этом случае пользователь будет автоматически перенаправлен на корпоративный сервис федерации, затем обратно на страницу входа, а затем, наконец, на адрес SharePoint.

Проблема: учетные записи не синхронизируются из AD в SharePoint multitenant User Profile Service Application

Столкнулся с такой проблемой на проекте.

В AD у заказчика вложенные OU для каждой дочерней организации:

Domain
   Customers
      Customer1
         Users
            User1
            User2
      Customer2
         Users
            User3
            User4

SharePoint build: 15.0.4693.1000

Profile service создан с использованием ключа -PartitionMode, т.е. база данных поделена на разделы. Каждая организация(SiteSubscription) имеет свой раздел.

Специальная команда PowerShell для создания раздела запущена для каждой организации:

Add-SPSiteSubscriptionProfileConfig -id <SiteSubscription> -SynchronizationOU ‘Customer1’

Коннектор синхронизации создан и OU “Customers” выбран.

 

Полная синхронизация выполняется без единой ошибки, в центре администрирования все выглядит обещающе:

MOSS
Stage SharePoint Server import
Additions 3311
Updates 46
Unchanged 0
.............................
Successes 3357
Failures 0
Start Time 4/30/2015 11:00:03 PM
End Time 4/30/2015 11:01:58 PM

Несмотря на это ни один профиль не создан. ULS чист, как и журнал приложений и miisclient. Абсолютно.

Я начал подозревать несколько вероятных источников проблемы, и решил провести несколько экспериментов. И хотя это заняло приличное время, я думаю, оно не потрачено зря, т.к. теперь Вам не придется тратить время. Просто можете ознакомиться с описанием экспериментов, сделанных мной.

Тест 1. Грубая попытка

Моими первыми подозрениями были:

  1. SharePoint проверяет только название непосредственно родительского OU, когда распределяет пользователей по разделам.
  2. SharePoint сравнивает OU, указанное для SiteSubscription с OU, отдельно отмеченное при настройке коннектора синхронизации
  3. После того, как учетная запись импортирована в SharePoint но не приписана к определенному разделу, при следующих синхронизациях в любом случае учетная запись будет игнорирована, т.к. на стороне AD не произошло изменений учетной записи, т.е. нечего реплицировать.

Я сделал 2 изменения:

  1. Удалил коннектор и создал новый, выделив отдельно OU каждой организации. Т.е. я отметил “Customer1”, “Customer2”, и т.д., вместо того, чтобы выделить один родительский “Customers” целиком, как я делал в прошлый раз, когда синхронизация провалилась.
  2. Выполнил следующую PowerShell команду для привязки прямого родительского OU с пользователями:
    Set-SPSiteSubscriptionProfileConfig -id <SiteSubscription> -SynchronizationOU ‘Users’

Затем я запустил полную синхронизацию.

В результате я получил всех пользователей из всех OU “Users” в одном разделе (к которому я и применил команду).

Чтобы просмотреть количество профилей, привязанных к каждому SiteSubscription, я использовал следующий PowerShell скрипт:

Foreach ( $sub in ( Get-SPSiteSubscription ) )
{
    $sub
    $serviceContext = Get-SPServiceContext -SiteSubscription $sub
    $profileManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager( $serviceContext );
    $profileManager.Count
}

Вывод:

SharePoint сравнивает только название прямого родителя при фильтрации пользователей.

Тест 2. Поиск корректной привязки

Теперь я решил найти способ привязки точно заданного OU “Users” к кажому подписчику. Т.е. каждому SiteSubscription должен быть назначен определенный OU “Users”.

Но для начала мне нужно было удалить профили, созданые на предыдущем этапе:

Remove-SPSiteSubscriptionProfileConfig -id <SiteSubscription>

Далее я начал применять различные формы указания на OU:

'Customer1\Users'
'Customer1/Users'
'Customer1*'
'Users,OU=Customer1’
'DomainName/Customers/Customer1/Users'
'*Customer1'

Например,

Set-SPSiteSubscriptionProfileConfig -id <SiteSubscription> -SynchronizationOU 'Customer1\Users’

Я указал данные значения для разных подписчиков и снова запустил полную синхронизацию.

Ни одного профиля не было создано.

Вывод (предварительный):

Единственный известный способ связывания OU с SiteSubscription это указывать атрибут “name” OU. Например, “Users”.

Тест 3. Очистка

Да, эта правда была не из тех, которую я предпочитал, но меня никто не спросил.

Раз я уже начал эксперименты, я решил получить чуть больше результатов.

Вот очередное подозрение, которое я захотел развеять:

Возможно, пользователи не были созданы в предыдущем тесте (даже с любыми мыслимыми формулировками OU) по простой причине: Они не были изменены и нет изменений, которые требуется реплицировать в профили SharePoint.

Я снова связал одного из подписчиков с OU “Users”:

Set-SPSiteSubscriptionProfileConfig -id <SiteSubscription> -SynchronizationOU ‘Users’

В результате после запуска полной синхронизации все профили были снова созданы в одном разделе.

Вывод:

Нет необходимости заново создавать коннектор при изменении привязки OU. Достаточно запустить полную синхронизацию.

Тест 4. Что отмечать

Единственное, что осталось выяснить: есть ли зависимость от уровня OU, который отмечен в коннекторе синхронизации.

В последних тестах были специально отмечены OU организаций, такие как “Customer1”, “Customer2” и т.д. Я изменил коннектор и отметил только родительский OU, “Customers”.

Так же мне пришлось снова удалить профили с использованием команд Remove-SPSiteSubscriptionProfileConfig и Add-SPSiteSubscriptionProfileConfig.

Полный импорт снова создал все профили в заданном разделе.

Вывод:

Не имеет значения как выбирать OU в коннекторе – один родительский OU или все дочерние. То же самое для multitenat приложения службы профилей.

Further investigation

Теперь, когда я стал более уверенным в том что для фильтрации учетных записей используется только название непосредственного родительского OU, я смог найти похожие истории в Интернете:

Тут Патрик нашел SQL-процедуру в базах данных SharePoint которая выполняет эту “умную” привязку:

https://rompenpatrick.wordpress.com/2011/09/16/error-during-profile-import-completed-export-error-ma-extension-error/

И в этой статье есть ссылка на форум, который сообщяет

The developent team (Microsoft support) canned the fix. This functionality will not be part of the CU2 updates or any future updates. They thought it worked as designed.

По-русски это означает, что разработчики Microsoft законсервировали фикс. Они считают, что все работает так как было задумано.

https://social.msdn.microsoft.com/Forums/office/en-US/e2ac0ff2-0255-4874-9410-5294228a4ece/multi-tenant-user-profile-sync-issue?forum=sharepointgeneralprevious

Решение

Пока что нет. Я не нашел пока что хорошего решения в сложившейся ситуации. Такое поведение означает что мы должны реструктурировать AD. И честно говоря, это врядли возможно, т.к. заказчик использует внешнюю систему управления структурой AD и объектами. Я надеюсь, что я еще вернусь к этому посту и обновлю его хэппи эндом. В другой раз. Спасибо за чтение!

P.S.

Решил провести еще одну небольшую проверку и не удаляя созданные профили поменять привязку и проверить, что произойдет если поменять привязку на другого подписчика и запустить полную синхронизацию. В результате все профили остались на месте, привязанными к старому подписчику. Т.е. если вы хотите при импорте привязать профили к другому подписчику, необходимо предварительно удалить профили.

среда, 15 января 2014 г.

Служба метаданных недоступна

Внезапно перестала работать служба метаданных.

При открытии страницы администрирования службы выдается ошибка “В настоящий момент подключение или служба управляемых метаданных недоступна. Возможно, не были запущены веб-служба управляемых метаданных или пул приложений. Обратитесь к администратору.”

image

Помогло следующее

  1. Создал второе (временное) приложение-службу;
  2. В свойствах первоначального приложения службы скопировал в буфер обмена имя базы данных:
    image
    image
  3. Удалил первоначальную службу, выдающей ошибку. При этом оставил галку удаления данных
    image
  4. Пересоздал первоначальную службу, указав скопированное название базы данных;
  5. Удалил временное приложение.

понедельник, 16 декабря 2013 г.

Фильтр по первой букве

Если список упорядочен по алфавиту в каком-то столбце и количество элементов в списке более 100, целесообразно применить фильтр по первой букве для ускорения навигации.

Для этого необходимо

  1. Добавить в список вычисляемый столбец (с названием, например, firstletter) и с формулой =ЛЕВСИМВ([Имя];1).
  2. Добавить веб-часть редактора содержимого со следующим кодом:

<style>
.lettersfilter
{
    margin-bottom: -10px;
}
.lettersfilter .letter
{
    background-color: #f0f0f0;
    display: inline-block;
    width: 40px;
    padding-bottom: 5px;
    text-align: center;
    vertical-align: middle;
    font-size: 2em;
    margin-right: 6px;
    margin-bottom: 10px;
}
.lettersfilter .letter.all
{
    width: 90px;
}
</style>
<div class="lettersfilter">
    <a class="letter all" href="Справочник%20сотрудников.aspx">Все</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B0">А</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B1">Б</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B2">В</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B3">Г</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B4">Д</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B5">Е</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%91">Ё</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B6">Ж</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B7">З</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B8">И</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%B9">Й</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%BA">К</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%BB">Л</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%BC">М</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%BD">Н</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%BE">О</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D0%BF">П</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%80">Р</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%81">С</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%82">Т</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%83">У</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%84">Ф</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%85">Х</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%86">Ц</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%87">Ч</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%88">Ш</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%89">Щ</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%8D">Э</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%8E">Ю</a>
    <a class="letter" href="Справочник%20сотрудников.aspx?FilterField1=firstletter&FilterValue1=%D1%8F">Я</a>
</div>

В коде заменить “Справочник%20сотрудников” на URL-страницы, где настроено представление.

Заменить “firstletter” на внутреннее название столбца, созданного на шаге 1.

Получается так:

image

вторник, 3 декабря 2013 г.

Столбцы адреса элемента в рабочем процессе SharePoint Designer

При разработке процесса в SharePoint Designer разработчику предоставляется множество различных столбцов, связанных с адресом элемента.

Какой из них выбрать в нужный момент? Вот что возвращает рабочий процесс при запросе различных столбцов на документе, расположенном в подсайте testsite, в библиотеке test, в папке “Папка” (/testsite/test/Папка/Инструкция%20по%20импорту%20в%20базу%20данных%203д-моделей.docx).

Выбирайте.

 

image

http://server/testsite/test/Папка/Инструкция%20по%20импорту%20в%20базу%20данных%203д-моделей.docx

 

image

Инструкция по импорту в базу данных 3д-моделей

 

image

Инструкция по импорту в базу данных 3д-моделей.docx

 

image

/testsite/test/Папка/Инструкция по импорту в базу данных 3д-моделей.docx

 

image

/testsite/test/Папка

 

image

/testsite/test/Папка/Инструкция по импорту в базу данных 3д-моделей.docx

 

image

http://xn--80acmlhv0b.xn--80agflthakqd0d1e/testsite

 

image

http://server/testsite/test/Папка/Инструкция по импорту в базу данных 3д-моделей.docx

среда, 30 октября 2013 г.

Оценка “Мне нравится” на форме просмотра элемента

В SharePoint 2013 есть стандартный функционал оценок:

image

Он работает отлично на обычных табличных представлениях.

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

image

На странице DispForm нет кнопки “Нравится”, есть только в табличном представлении.

Но можно добавить эту кнопку следующим кодом:

<div class="LikeSection"><span class="likecount"></span><a href="#" onclick="LikePage()" class="LikeButton"></a></div>
<SharePoint:ScriptLink language="javascript" name="reputation.js" OnDemand="true" runat="server" Localizable="false"/>
<script type="text/javascript">
    function getQueryStringParameter(urlParameterKey) {
        var params = document.URL.split('?')[1].split('&');
        var strParams = '';
        for (var i = 0; i < params.length; i = i + 1) {
            var singleParam = params[i].split('=');
            if (singleParam[0] == urlParameterKey)
                return decodeURIComponent(singleParam[1]);
        }
    }

    function LikePage() {
        var like = false;
        var likeButtonText = $("a.LikeButton").text();
        if (likeButtonText != "") {
            if (likeButtonText == "Нравится")
                like = true;
   
            var aContextObject = new SP.ClientContext();
            EnsureScriptFunc('reputation.js', 'Microsoft.Office.Server.ReputationModel.Reputation', function () {
                Microsoft.Office.Server.ReputationModel.
                Reputation.setLike(aContextObject,
                    _spPageContextInfo.pageListId.substring(1, 37),
                    getQueryStringParameter('ID'), like);
   
                aContextObject.executeQueryAsync(
                    function () {
                        //alert(String(like));
                        GetLikeCount();
                    }, function (sender, args) {
                        //alert('F0');
                    });
            });
        }
   
    }
   
    function GetLikeCount() {
   
        var context = new SP.ClientContext(_spPageContextInfo.webServerRelativeUrl);
        var list = context.get_web().get_lists().getById(_spPageContextInfo.pageListId);
        var item = list.getItemById(getQueryStringParameter('ID'));
   
        context.load(item, "LikedBy", "ID", "LikesCount");
        context.executeQueryAsync(Function.createDelegate(this, function (success) {
            // Check if the user id of the current users is in the collection LikedBy.
            var likeDisplay = true;
            var $v_0 = item.get_item('LikedBy');
            var itemc = item.get_item('LikesCount');
            if (!SP.ScriptHelpers.isNullOrUndefined($v_0)) {
                for (var $v_1 = 0, $v_2 = $v_0.length; $v_1 < $v_2; $v_1++) {
                    var $v_3 = $v_0[$v_1];
                    if ($v_3.$1E_1 === _spPageContextInfo.userId) {
                        //cb(true, item.get_item('LikesCount'));
                        //alert("Liked by me");
                        likeDisplay = false;
                    }
                }
            }
            ChangeLikeText(likeDisplay, itemc);
   
        }), Function.createDelegate(this, function (sender, args) {
            //alert('F1');
        }));
   
    }
   
    function ChangeLikeText(like, count) {
        if (like) {
            $("a.LikeButton").text('Нравится');
        }
        else {
            $("a.LikeButton").text('Разонравилось');
        }
        var htmlstring = '<img alt="" src="/_layouts/15/images/LikeFull.11x11x32.png" />' + ' ' + String(count);
        if (count > 0)
            $(".likecount").html(htmlstring)
        else
            $(".likecount").html("");
    }
   
    $(document).ready(function () {
        GetLikeCount();
        $("a.LikeButton").click(function () {
            LikePage();
        });
    });
</script>

Получается вполне симпатично:

image