среда, 16 октября 2019 г.

Redux: усилители (enhancers).

Введение


В данной статье хотел бы рассказать про усилители в Redux: Что это такое? Какие задачи решают? Как реализованы?

Enhancer можно назвать усилителем, но мне такое название не нравится (Что он там усиливает?!). Больше подходит название «расширитель» или «расширение». 
Собственно, enhancer это функция, позволяющая добавить к хранилищу дополнительный функционал. 
Исходя из назначения, название «расширитель» подходит больше. Но как бы оно там не называлось, наша задача понять, как работает enhancer, а не выбрать название.

А какие задачи у Enhancer? И тут повторюсь – добавить или изменить функциональность хранилища. Функциональность хранилища меняется путем изменения текущих свойств или добавлением новых.
Например, applyMiddleware (единственный enhancer в составе redux) делает обертку dispatch, заменяя оригинальное одноименное свойство. Как делается обертка можно посмотреть здесь.
Также можно добавить дополнительное свойство к хранилищу, например, dispatchAsync (пример в конце статьи).

Теперь перейдем от назначения к реализации.


Реализация.


Как вообще enhancer применяется к хранилищу? А применяется он во время создания хранилища:
createStore(reducer, initialState, enhancer())
или
createStore(reducer, enhancer())
То есть передается либо вторым, либо третьим аргументом в функцию createStore. И заметьте, передается именно результат выполнения, а не сама функция.

Какая сигнатура у enhancer? Функция enhancer должна вернуть функцию, которая принимает функцию createStore в качестве параметра):
function enhancer1(optionalParams){
return function receiveCreateStore(createStoreFunc){
        }

}
Далее функция (у нас это receiveCreateStore), принимающая createStore (через createStoreFunc), должна вернуть функцию (createEnhancedStore). Эта функция и должна вернуть объект-хранилище.
function enhancer1(){
return function receiveCreateStore(createStoreFunc){
return function createEnhancedStore(…args){
return {
                             // возвращаем хранилище
                             // по задумке - расширенное
                        }
               }

       }
}
Обратите внимание на аргументы функции – args. Сюда будут передаваться 2 параметра: reducer и initialState. Первый и второй аргумент функции createStore.
Это была сигнатура. 

Как правило из последней функции (у нас createEnhancedStore) вызывается опять createStore (скажем так, стандартный… далее объясню) и уже ее результат используется для «обертывания» или дополнения.
Поэтому в сигнатуру я еще добавлю вызов функции createStore и возвращение нового – измененного store. Итого получится следующее:
function enhancer1(){
return function receiveCreateStore(createStoreFunc){
return function createEnhancedStore(…args){
const store = createStoreFunc(…args);
return {
…store,
dispatch: _dispatch,  // замена оригинального dispatch
newDispatch: _newDispatch // добавление нового свойства

                        }
               }
       }
}
Это был готовый каркас функции-расширителя. В таком виде можно и использовать.
А вот упрощенная версия с использованием стрелочной нотации:
const enhancer = () => createStoreFunc => (…args) => {
const store = createStoreFunc(…args);
        return {
…store,
dispatch: _dispatch,  // замена оригинального dispatch
newDispatch: _newDispatch // добавление нового свойства/метода
        }
}
Теперь нам нужно обратиться к исходникам redux, чтобы понять: почему именно такая сигнатура, зачем передается createStore, зачем она вызывается повторно и другие детали реализации.

Оперировать будем исходниками createStore, enhancer’ом applyMiddleware и приведенным каркасом. Потом приведу пример для добавления нового метода к хранилищу (взял опять-таки у Freeman’а).

Сначала возьмем функцию createStore.
 
Нас интересует:
А) кусок 1, где описаны параметры, 
Б) кусок 2, где проверяется, был ли передан enhancer
В) и кусок 3, где вызывается enhancer.

Логика такая: если был передан enhancer, то запускается сначала он и функция createStore передает ему сама себя в качестве параметра.
Enhancer вернет функцию, которая принимает аргументы и возвращает объект store:
return function createEnhancedStore(…args){
    return {
        // возвращаем хранилище
    }
}
Далее createStore запустит возвращенную функцию и передаст ей два аргумента: reducer и initialState (через …args):
 
Это мы дошли до последней функции в каркасе, которая собственно и расширяет хранилище.
Как она это делает? Обычно используется функция createStore которая передавалась при первом запуске enhancer (подчеркну, обычно!). createStore запускается с переданными параметрами (reducer и preloadedState): 
const store = createStoreFunc(…args);
Управление передается «оригинальной» функции createStore, но теперь она запускается только с двумя параметрами, то есть без enhancer.
Поскольку enhancer пустой, то отмеченный на картинке вызов пропускается:
 
Во время выполнения «собирается» store и он же, как объект, возвращается enhancer’у:

 Теперь у нас есть созданный store и можем его расширить:
const enhancer = () => createStoreFunc => (…args) => {
const store = createStoreFunc(…args);
        return {
…store,
dispatch: _dispatch,  // замена оригинального dispatch
newDispatch: _newDispatch // добавление нового свойства/метода
        }
}
Давайте теперь на примере applyMiddleware посмотрим как он делает обертку для dispatch.
 

1) Это не особо относится к делу – здесь формируется массив из аргументов функции applyMiddleware.
2) Тут сразу возвращается функция, принимающая createStore.
3) Здесь функция createStore продолжает выполнение и запускает функцию из пункта 2, передает ей createStore, в ответ получает функцию. Здесь она указывается без параметров, а в «нашем» каркасе с параметрами args. Но на самом деле потом все равно используются параметры arguments.
4) И вот enhancer как раз вызывает «оригинальную» функцию createStore, полученную на шаге 2, передавая ей аргументы reducer и initialState. Как мы помним опять запускается createStore, но уже без вызова enhancer. В переменной store храним результат выполнения.
5) В этом куске делается обертка для dispatch.
6) Возвращается хранилище store (созданное на этапе 4) с усиленным dispatch.


Пример.


Как и обещал, пример добавления нового метода к хранилищу:

export const asyncEnhancer = delay => createStoreFunction => (...args) => {
    const store = createStoreFunction(...args);
    return {
        ...store,
        dispatchAsync: (action) => new Promise((resolve, reject) => {
            setTimeout(() => {
                store.dispatch(action);
                resolve();
            }, delay);
        })
    };
}
Здесь возвращается оригинальный store, но с новым методом - dispatchAsync, который запускает "обычный", но с задержкой.

На этом все.

Ссылки:

вторник, 15 октября 2019 г.

Redux: прослойки (middleWares)

В настоящей статье хочу осветить схему с прослойками в Redux: Что это такой? Как реализовано? Как использовать?
Здесь не будет рассказано про «основы» redux: actions, reducers, action creators и т.д. С этим нужно знакомиться отдельно. Возможно, я напишу про это, а возможно уже написал.


Что такое middleWare?

Простым языком, middleWare – это обертка (wrapper) для функции dispatch хранилища Redux. Получилось как-то не очень просто. Если еще проще, то это возможность выполнить какие-то действия до того, как будет выполнен dispatch (либо можно заблокировать вызов последнего). А dispatch, как вы знаете, запускает reducer.
Для такой обертки можно использовать:
1) ручной способ;
2) особый вид функции + функцию applyMiddleware библиотеки Redux.


Ручной способ.

Сначала посмотрим на ручной способ.
Допустим у нас есть некоторый код, который вызывает dispatch для передачи action в наш reducer:
//…
store.dispatch({type: “CREATE”, dataType: “REQUESTS”, payload: this.state.request});
//…
Ни что не мешает добавить в код дополнительные проверки или actions, перед тем как запустить dispatch. Это и будет наша простейшая прослойка.
Например, запустить несколько dispatch, где первый добавляет данные в хранилище, а второй управляет состоянием формы:
store.dispatch({type: “CREATE”, dataType: “REQUESTS”, payload: this.state.request});
store.dispatch({type: “CLOSE_FORM”});
Другой пример прослойки – работа с веб-сервисами:
//…
let result = this.webservice.createRequest(this.state.request);
result.then(data => store.dispatch({type: “CREATE”, dataType: “REQUESTS”, payload: data}))
//…
В примерах я не стал использовать action creators, чтобы упростить код. Хотя, конечно, правильно реализовать отдельный уровень абстракции в виде action creators.
Вот и вся суть middleWare. Хотя, конечно, с такой трактовкой, можно все что угодно считать middleWare.
Приведенный выше способ является наиболее простым, но с точки зрения управления кодом менее предпочтительным: обертка может быть разбросана по всему приложению, динамически подключать и отключать сложно, переписывать и изменять тоже придется в разных местах, дублирование кода и другие «прелести». В общем, способ подходит либо как временное решение, либо в очень маленьких проектах.
А теперь перейдем ко второму способу – использование функции applyMiddleware.


applyMiddleware

applyMiddleware – это функция из библиотеки redux. Функция реализует store enhancer (расширитель хранилища… не знаю, как по-русски это обозвать, пусть будет так).
Store Enhancer является отдельной темой для обсуждения, нас сейчас интересует только то, как applyMiddleware делает прослойку, а не то как реализовываются enhancers.
applyMiddleware принимает в качестве параметров одну или более функций, составляет из этих функций цепочку вызовов (в цепочку входит оригинальный dispatch) и заменяет в store оригинальный dispatch на результирующую цепочку.
applyMiddleware, как я уже говорил является enhancer и применяется при создании хранилища.
//…
const middleware = applyMiddleware(func1, func2);
const store = createStore(reducerFunc, middleware);
//…
В упрощенном варианте цепочка вызовов будет выглядеть так: func1 -> func2 -> dispatch(action). То есть цепочка выполняется слева на право. Как правило функции друг о друге ничего не знают, они просто вызывают следующую функцию (или не вызывают, блокируя вызовы).
Функции, передаваемые в applyMiddleware, должны удовлетворять определенной сигнатуре:
({ getState, dispatch }) => next => action
Далее, для лучшего понимания я буду использовать полный синтаксис, вместо стрелочного. Но поскольку синтаксис большинства middleware (по крайней мере на этапе формирования цепочки) будет одинаковым, то лучше использовать стрелочную нотацию (пример также будет далее).
В функцию первым делом передаются функции getState и dispatch. Их использование – опционально, необходимо исходить из задачи. Например, getState можно использовать для получения текущего состояния store и далее, после вызова dispatch, для сравнения изменений или, к примеру, для логирования.
function func1({getState, dispatch}) {             
   console.log(getState());             
   /// …
}
func1 должна вернуть функцию, которая принимает параметр следующей функции вызова.
function func1({getState, dispatch}) {             
   console.log(getState());             
    return function receiveNext(next){
         //…             
     } 
}
applyMiddleware после запуска func1 вызовет результирующую функцию, а в параметр передаст следующую функцию в цепочке (не совсем точно, чуть далее объясню). После выполнения у нас опять должна вернуться функция, но уже принимающая параметр action. Эта функция и реализовывает прослойку, она же является dispatch’ером (она и передается как next).
function func1({getState, dispatch}) {
      return function receiveNext(next){
               return processAction(action){
                       //…                           
                       next(action);                       
                }
      }
}
Здесь я добавил инструкцию next(action), которая запускает следующий dsipatch’ер. Хотя использование необязательное, в этом случае цепочка запуска прервется и в reducer ничего не передастся.
Это и есть полная сигнатура middleWare-функции. В упрощенном виде она выглядит вот так:
const func1 = ({getState, dispatch}) => next => action => {
     //..   
    next(action)
}
С сигнатурой разобрались. Запоминать ее не нужно, она всегда одинаковая, ее всегда можно найти в документации к redux.


Как applyMiddleware строит цепочку из функций?

Как я и сказал выше, первым делом каждая функция запускается по отдельности и ей передается 2 параметра – getState и dispatch (в оригинале реализовано это через Array.map, но для наглядности здесь и далее я буду приводить примеры с ручным перечислением/запуском):
const receiveNextF1 = func1({getState, dispatch});
const receiveNextF2 = func2({getState, dispatch});
На выходе мы имеем две функции, которые принимают функцию в параметре next ( return function receiveNext(next){ } ).
Далее выполнение начинается с правой крайней функции – func2 (а точнее с результатом ее выполнения).
applyMiddleware запускает результирующую функцию (receiveNextF2) и в параметр next передает оригинальный dispatch:
const dispatcherF2 = receiveNextF2(dispatch);
в dispatcherF2 будет вот такой код (next я заменил на dispatch):
processAction(action){ 
    dispatch(action);
}
Далее тоже самое делается для следующей функции, но в качестве параметра next передается результат предыдущего запуска:
const dispatcherF1 = receiveNextF1(dispatcherF2);
в dispatcherF1 будет вот такой код:
processAction(action){
  dispatcherF2(action);
}
После того, как обработана вся цепочка, dispatherF1 сохраняется в объекте store как свойство dispatch. Теперь при вызове store.dispatch() запускается сначала dispatcherF1, он запускает dispatcherF2 и последний запускает оригинальный dispatch, который в свою очередь запускает reducer.
Вот так строится цепочка middleWare на базе функции applyMiddleware.


Пример

В завершение приведу пример прослойки для вызова сразу нескольких actions (взял у Freeman’а). Запускаются они, конечно, не одновременно, а последовательно. Является хорошей альтернативой запуска нескольких dispatch.
const multiActions = ({dispatch, getState}) => next => action => {
    if (Array.isArray(action)) {
        action.forEach(a => next(a));
    }
    else { next(action); }
}
export default createStore(reducer, applyMiddleware(multiActions));
Теперь мы можем в dispatch передать несколько actions:
dispatch([{type: “STORE”, payload: this.state.request }, {type: “CLOSE_FORM”} ])
Всю эту жуть вида [{},{}] конечно же нужно заменить на actionCreators, тогда получится более лаконичный код:
dispatch([createRequest(request), closeForm()])
На этом все.

Ссылки:

четверг, 4 октября 2018 г.

RRAS custom IKEv2 policy

RRAS custom IKEv2 policy:
https://msdn.microsoft.com/en-us/library/ee791026.aspx

суббота, 6 апреля 2013 г.

Автоматизация публикации виртуальных машин в UAG 2010.

Microsoft Forefront Unified Access Gateway (UAG) 2010 - очень интересное средство для публикации внутренних ресурсов для внешних (да и внутренних тоже) пользователей. Но вот есть в нем очень серьезный недостаток - нет никаких средств автоматизации процесса управления.

Точнее так, средства автоматизации есть, в виде COM-интерфесов, но они недокументированы и понять, как они работают и какие параметры принимают, мне так и не удалось. Информации на просторах Интернет об этом я также не нашел.
Дополнительные неудобства вызывает и консоль управления UAG, явно выполненная не в майкрософтовском стиле (по известным причинам).

Ну и что с этого, скажете вы? Тогда немного об истоках проблемы.
Есть вычислительная среда, в виде энного количества серверов, включенных в группу управления VMM. В VMM нарезаны облака, на облака делегировано управление для конечных пользователей (разработчиков). В этих облаках разработчики создают виртуальные машины для своих нужд. Виртуальные машины необходимо как-то публиковать для доступа как снаружи так изнутри. На помощь нам приходит UAG - мы публикуем RDP виртуальных машин. Казалось бы, что еще нужно?
Но тут начинаются проблемы. Поскольку среда динамичная и относительно большая, то поддерживать такое решение (с ручной публикацией) становится практически невозможным:
- виртуальные машины постоянно создаются и удаляются (а это нужно актуализировать информацию).
- необходимо постоянно добавлять/удалять пользователей для опубликованных ярлыков UAG.

Вот здесь то мы и встречаемся с неудобной консолью управления UAG и отсутствием автоматизации.

Поэтому была поставлена задача как-то облегчить страдания группы поддержки и дать возможность конечным пользователям самим публиковать свои виртуальные машины, добавлять/удалять пользователей для ярлыков и держать это все в актуальном состоянии.

Дать доступ к консоли UAG конечным пользователям - не вариант.

После недолгих изучений, было обнаружено, что UAG хранит свою конфигурацию в файле UAG.egf, что находится в common\conf\ в папке установки. Файл текстовый, имеет некоторую иерархическую структуру. В качестве узлов иерархии выступают объекты, такие как, trunk, приложения, политики и т.д.

Теперь нужно было как-то разобрать эту информацию.
Полностью описывать структуры файла не буду, да и таких знаний у меня нет. Меня больше интересовали именно приложения RDP.
Структура файла:
{
     настройки самого UAG (версия схемы, алгоритмы шифрования и т.д.)
     {
      Репозитории аутентификации
       {
       }
      ...
     }
     {
        trunk http (80) и его настройки
      }
      {
         trunk https и его настройки
         {
                опубликованные приложения  <<< то что нам нужно
         }
      }
...........
}

Приложение формируется из набора атрибутов со значениями.
Формат атрибута следующий:

m_<тип><наименование атрибута>_<ID-приложения>[_<податрибуты и коды>]=[<значение>]

В "[]" заключены необязательные данные.
К типам относятся b (boolean), n (numeric), cs (скорее всего case-sensitive string) и т.д. Хотя конечно это не нужная для нас информация, мы ее никак использовать не будем.
Наименование атрибута - думаю понятно что это. Примеры: AppName, AppID, AppAuthList.
У приложения есть целочисленный уникальный идентификатор (ID-приложения). Уникальный скорее всего в пределах транка (Trunk).
Могут быть некоторые податрибуты. Например, для AuthList - это Repository, UserName и т.д.
Если например мы публикуем ярлык для нескольких пользователей, то податрибуты для разных пользователей еще будут различаться по целочисленному уникальному идентификатору.
После знака "равно" идет значение, хотя оно может отсутствовать.

Список атрибутов для ярлыка RDP можно посмотреть здесь - http://pastebin.com/5sz0EV9z
Список атрибутов для конкретного приложения можно получить с помощью вот этого скрипта (Powershell) - http://pastebin.com/WqHP0fx9. На входе путь к файлу UAG и имя приложения, которое отображается в консоли. На выходе список атрибутов со значениями.

В принципе достаточно повторить все эти атрибуты, добавить в секцию приложений конкретного транка, сохранить и активировать конфигурацию. В итоге нужный ярлык с приложением должен появиться на UAG-портале.
Но не все так просто. У приложения есть идентификатор вот в таком виде:
m_csAppId_24=7F81AB0527A443BEBA2018216CBCD110.
Когда изначально разбирался в структуре файла, я думал что это некий хэш MD5. Тем более очень похож, 128 бит. Пробовал его прикладывать к имени приложения, но результат отрицательный. К чему бы еще приложить - не нашел. Сейчас пишу эту статью и в голову пришла мысль, что это обычный GUID, без знаков "-" и фигурных скобок. Возможно в ближайшее время эта информация подтвердится.

Дополнительно нужно было убедиться, что в файле не меняются другие атрибуты или если меняются то какие и где.
Для этого написал небольшой скрипт для получения различий между двумя файлами (сначала поискал готовые утилиты, но они меня не удовлетворили, а тратить время на дальнейший поиск и изучение еще и этого вопроса вовсе не хотелось). Скрипт можно взять здесь - http://pastebin.com/3w5hp4Wz. На входе пути файлов Source и Dest.
Как выяснилось изменяются еще ряд атрибутов, такие как количество приложений в транке.

Все это я учел и начал вручную публиковать RDP-ярлык.
То есть, я беру и повторяю список атрибутов для приложения "Personal RDP", изменяю некоторые атрибуты (имена, ID и т.д.), изменяю дополнительные атрибуты и активирую конфигурацию. Мое приложение на портале не появляется и конфигурация откатывается на изначальную. Ошибок почему-то в мониторе активации нет.

Тогда я иду другим способом. Создаю некоторое приложение через консоль UAG. Далее изменяю нужные мне атрибуты уже через текстовый файл: имя, отображаемое имя, внутренний IP-адрес, список авторизации и т.д.. Применяю конфигурацию. Работает!
Ну собственно таким способом и решил действовать.

В результате родился следующий скрипт (Powershell): http://pastebin.com/jW9S0qaC
Список функций:

  • Set-UAGConfig - устанавливает глобальные переменные с расположением файла-egf, расположением каталога для бэкапов файлов-egf и путь к активатору (об этом немного позже). По умолчанию, в качестве $SourceFile берется UAG.egf из каталога установки UAG. $BackupDir - c:\EGFBackups (если не существует, то происходит попытка создания каталога). $UagActivator - по умолчанию C:\Scripts\UAGActivate.exe.
  • Backup-UAGConfig - копирует egf-файл из $SourceFile в $BackupDir с добавлением временной метки (для уникальности)
  • Disable-UAGApp - отключает опубликованное приложением, переименовывает в Unused<ID> и очищает некоторые атрибуты. Например, если вы хотите удалить приложение, то вызываете эту функцию.
  • Enable-UAGApp - включает приложение. Эта функция находит доступное отключенное приложением (с именем Unused<ID>), включает его и присваивает ему атрибуты. Какие? Имя приложения, имя приложения на портале, в ShortDescription попадает ID облака (это конкретно моя реализация), в LongDescription попадает GUID виртуальной машины, IP-адрес внутреннего сервера (ВМ). Большинство этих атрибутов устанавливается конкретно под мои задачи. Вам они скорее всего не подойдут. В этом случае необходимо изменить скрипт.
  • Set-UAGApp - обновляет атрибуты для уже опубликованного приложения. В моем случае это имя приложения, имя приложения на портале, ProjectID -> ShortDescription, VMID -> LongDescription и IP-адрес.
  • Get-UAGAuth - возвращает список авторизации для опубликованного приложения (выходной формат описан далее).
  • Set-UAGAuth - устанавливает список авторизации для опубликованного приложения.
  • Get-UAGAppList - возвращает список приложений, опубликованный для конкретного облака по ProjectID (это конкретно под мою задачу).
  • Get-UAGApp - возвращает приложение по VMID или по имени на портале (VMID берется из LongDescription).
  • Activate-UAGConfig - активирует конфигурацию с помощью файла из переменной $UAGActivator.

Как этим пользоваться?

Предварительный этап:
1) Необходимо через UAG консоль создать пул приложений RDP, с произвольными именами (отображение на портале нужно включить, список авторизации пустой), сохраняете конфигурацию (опционально можно ее применить).
2) Далее вы импортируете функции в консоль Powershell:
Import-Module "c:\Scripts\UAGFunctions.ps1"
3) делаете резервную копию файла конфигурации
Backup-UAGConfig
4) Отключаете созданные приложения
Disable-UAGApp -AppName "Имя приложения на портале"
Внимание!!! Обратите внимание, что в моей реализации я работаю с приложениями по имени, отображаемому на портале или по VMID, который я сохраняю в атрибуте LongDescription.
5) Активируете конфигурацию командой
Activate-UAGConfig

Далее 1 шаг - включаем свободное приложение (Unused):

  1. $ErrorActionPreference = "Stop"
  2. Get-PSSession | Remove-PSSession
  3. #Пароль для учетной записи, которая подключается удаленно к UAG
  4. $SecureString = ConvertTo-SecureString -String "\`d.T.~Vb/{41BAA34C-B0E6-41D0-8F2B-CB3901AAF6A8}\`d.T.~Vb/" -AsPlainText-Force
  5. $Creds = New-Object system.Management.Automation.PSCredential("domain\os2uag_connector", $SecureString)
  6. $SessionOpt = New-PSSessionOption -OperationTimeout (30*60*1000)
  7. #Подключаемся к UAG через PSRemoting
  8. $Session = New-PSSession -ComputerName "server-uag01" -Credential $Creds -SessionOption $SessionOpt
  9. #Импортируем модуль с функциями
  10. Invoke-Command -Session $Session -ScriptBlock {Import-Module "c:\Scripts\UAGFunctions.ps1"}
  11. #Устанавливаем глобальные переменные $BackupDir и UAGActivator
  12. Invoke-Command -Session $Session -ScriptBlock {Set-UAGConfig -BackupDir "c:\EGFBackups" -UAGActivatorPath"c:\Scripts\UAGActivate.exe" }
  13. #Бэкапим конфиг
  14. Invoke-Command -Session $Session -ScriptBlock {Backup-UAGConfig}
  15. $ScriptBlock = {
  16.         #Заполняем входную информацию для публикации
  17.         $VMID = "\`d.T.~Ed/{6F6D86D8-3492-4328-B606-7B983F64826F}.VM ID\`d.T.~Ed/"
  18.         $AppName = "\`d.T.~Ed/{6F6D86D8-3492-4328-B606-7B983F64826F}.VM Name\`d.T.~Ed/"
  19.         $ProjectID = [int]("\`d.T.~Ed/{6F6D86D8-3492-4328-B606-7B983F64826F}.Cloud\`d.T.~Ed/".Split("_")[0].Substring(1))
  20.         $IPAddress = "\`d.T.~Ed/{90F45712-817A-4429-A5F0-668F26467CFD}.{08BCA15A-22A5-4770-81E7-1D41E21D1CDA}\`d.T.~Ed/"
  21.        
  22.         #Смотрим опубликовано ли уже приложение по имени
  23.         $App = Get-UAGApp -AppName $AppName
  24.         if ($App){
  25.                 if ($App.VMID -ne ($VMID.ToString().ToUpper().Replace("-",""))){
  26.                         #Если приложение (ВМ) с таким именем опубликовано и VMID различаются, то скорее всего старую ВМ удалили и создали с тем же именем
  27.                         #в этом случае отключаем исходное
  28.                         Disable-UAGApp -AppName $AppName
  29.                         #включаем ярлык, заполняем атрибутами
  30.                         Enable-UAGApp -AppName $AppName -AppGUID $VMID -ProjectID $ProjectID -IpAddr $IPAddress
  31.                 }
  32.                 else{
  33.                         #обновляем информацию о приложении
  34.                         Set-UAGApp -AppName $AppName -IpAddr $IPAddress -ProjectID $ProjectID
  35.                 }
  36.         }
  37.         else{
  38.                 $App = Get-UAGApp -VMID $VMID
  39.                 if (-not $App){
  40.                         #Включаем Unused приложение
  41.                         Enable-UAGApp -AppName $AppName -AppGUID $VMID -ProjectID $ProjectID -IpAddr $IPAddress
  42.                 }
  43.                 else {
  44.                         #обновляем информацию о приложении
  45.                         Set-UAGApp -AppName ($App.AppPortalName) -AppGUID $VMID -IpAddr $IPAddress -NewAppName $AppName-ProjectID $ProjectID
  46.                 }
  47.         }
  48. }
  49. #выполняем скрипт-блок, сформированный ранее
  50. Invoke-Command -Session $Session -ScriptBlock $ScriptBlock
  51. $Session | Remove-PSSession


Шаг 2 - настраиваем авторизацию:

  1. $ErrorActionPreference = "Stop"
  2. $SecureString = ConvertTo-SecureString -String "\`d.T.~Vb/{41BAA34C-B0E6-41D0-8F2B-CB3901AAF6A8}\`d.T.~Vb/" -AsPlainText-Force
  3. $Creds = New-Object system.Management.Automation.PSCredential("domain\os2uag_connector", $SecureString)
  4. $SessionOpt = New-PSSessionOption -OperationTimeout (30*60*1000)
  5. $Session = New-PSSession -ComputerName "server-uag01" -Credential $Creds -SessionOption $SessionOpt
  6. Invoke-Command -Session $Session -ScriptBlock {Import-Module "c:\Scripts\UAGFunctions.ps1"}
  7. Invoke-Command -Session $Session -ScriptBlock {Set-UAGConfig -BackupDir "c:\EGFBackups" -UAGActivatorPath"c:\Scripts\UAGActivate.exe" }
  8. $ScriptBlock = {
  9.         #Имя приложения, отображаемое на портале
  10.         $AppName = "\`d.T.~Ed/{6F6D86D8-3492-4328-B606-7B983F64826F}.VM Name\`d.T.~Ed/"
  11.         #DN пользователя в AD (я использую Active Directory Repository, для других репозиториев эти атрибуты могут отличаться)
  12.         $UserDN = "\`d.T.~Ed/{D6BCE08A-3CA4-4480-9482-4635B5083573}.Distinguished Name\`d.T.~Ed/"
  13.         $UserCanArr = @($UserDN.split(",") | where {$_ -notlike "dc=*"} | foreach {$_.Substring(3)})
  14.         $UserCanArr = @(for($i=($UserCanArr.Count-1);$i -ge 0$i--) { $UserCanArr[$i] })
  15.         #User Canonical String без домена (это тоже особенность Active Directory Repository в UAG)
  16.         $UserCanStr = [string]::Join('\', $UserCanArr)
  17.         #Получаю список авторизации для приложения
  18.         $authlist = Get-UAGAuth -AppName $AppName
  19.         #Создаю новый объект
  20.         $userObj = New-Object System.Object
  21.         #DN
  22.         $userObj | Add-Member -Name ID -MemberType NoteProperty -Value $UserDN
  23.         #Тип - 1 (пользователь)
  24.         $userObj | Add-Member -Name IsUser -MemberType NoteProperty -Value "1"
  25.         #Это имя репозитория (не имя домена, а именно репозитория аутентификации)
  26.         $userObj | Add-Member -Name Repository -MemberType NoteProperty -Value "domain.com"
  27.         #User Canonical name
  28.         $userObj | Add-Member -Name UserName -MemberType NoteProperty -Value $UserCanStr
  29.         #Разрешить - 1 (для блокирования видимо 0)
  30.         $userObj | Add-Member -Name Allow -MemberType NoteProperty -Value "1"
  31.         #добавляю к исходному списку (hash-таблица, в которой в качестве ключа используется DN) нового пользователя
  32.         $authlist[$userObj.ID] = $userObj
  33.         #Устанавливаю список авторизации для приложения
  34.         Set-UAGAuth -AppName $AppName -UserArray $authlist
  35. }
  36. #Выполняю скрипт-блок, подготовленный ранее
  37. Invoke-Command -Session $Session -ScriptBlock $ScriptBlock
  38. #Здесь можно активировать, но в моем случае активация конфигурации происходит чуть позже
  39. #Invoke-Command -Session $Session -ScriptBlock { Activate-UAGConfig }
  40. $Session | Remove-PSSession


Теперь завершающий этап - активация:

  1. $ErrorActionPreference = "Stop"
  2. $SecureString = ConvertTo-SecureString -String "\`d.T.~Vb/{41BAA34C-B0E6-41D0-8F2B-CB3901AAF6A8}\`d.T.~Vb/" -AsPlainText-Force
  3. $Creds = New-Object system.Management.Automation.PSCredential("domain\os2uag_connector", $SecureString)
  4. $SessionOpt = New-PSSessionOption -OperationTimeout (30*60*1000)
  5. $Session = New-PSSession -ComputerName "server-uag01" -Credential $Creds -SessionOption $SessionOpt
  6. Invoke-Command -Session $Session -ScriptBlock {Import-Module "c:\Scripts\UAGFunctions.ps1"}
  7. Invoke-Command -Session $Session -ScriptBlock {Set-UAGConfig -BackupDir "c:\EGFBackups" -UAGActivatorPath"c:\Scripts\UAGActivate.exe" }
  8. Invoke-Command -Session $Session -ScriptBlock { Activate-UAGConfig }
  9. $Session | Remove-PSSession


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

Что за UAGActivate.exe?
Это скрипт Autoit, скомпилированный в .exe файл. Он запускает ConfigMgrUtil.exe для активации конфигурации UAG и посылает ему нажатия клавиш. Почему-то ConfigMgrUtil.exe отказался, в моем случае, принимать команды через поток StdIn, пришлось извращаться и делать через autoit.

Скрипт autoit:

Run("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe -a")
WinWait("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe")
ControlSend("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe", "", "", "{ENTER}")
ControlSend("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe", "", "", "{ENTER}")
ControlSend("C:\Program Files\Microsoft Forefront Unified Access Gateway\utils\ConfigMgr\ConfigMgrUtil.exe", "", "", "{ENTER}")

Сам autoit здесь - http://www.autoitscript.com/site/autoit/
Скомпилированный выше скрипт здесь - http://www.filehosting.org/file/details/428697/UAGActivate.exe
SHA-1: b97f3151c3016f5eba54b460c43545d1810e5d21
Этот файл нужно сохранить под оригинальным именем (UAGActivate.exe) в папке c:\Scripts. Либо при выполнении скрипта/модуля с UAG-функциями установить путь к активатору.

У меня реализована схема публикации через Service Manager Portal (здесь пользователи создают заявки), далее отрабатывает System Center 2012 Orchestrator (бывший Opalis).
Runbook в Orchestrator выглядит вот так:
Скрипт с функциями был написан для схемы UAG версии 2010 SP1, но без изменений работает на SP3. Тестировался на протяжении месяца, без каких-либо проблем.