Работаем с Lotus Notes через Powershell

Вероятно, многим из вас приходилось работать в крупных организациях, где весь корпоративный документооборот и почта работают на программных продуктах IBM Lotus Domino. Эта система с точки зрения конечного пользователя выглядит некрасиво, пользоваться ей неудобно, и особенно много проблем доставляют задачи, где нужно обрабатывать большое количество документов. Однако, обладая таким мощным инструментом, как Powershell, вы можете существенно облегчить себе жизнь. Так, Powershell умеет работать с Lotus через интерфейс COM-объектов. Этот способ хорошо подходит для задач с небольшой нагрузкой, выполняемых эпизодически.

Вероятно, есть и другие способы взаимодействия с Lotus Notes (например, через ODBC), и если кто-нибудь в комментариях поделится – буду благодарен.

Предположим, что вы – системный администратор, и в вашем ведении находится примерно 300 серверов, данные о которых нужно периодически обновлять, обрабатывать и просматривать в удобном виде. Допустим, вы уже написали скрипт, который проходит по всем этим серверам, и получает все необходимые вам данные о серверах, IP-адресах, о том, где какие службы и компоненты работают, под каким пользователем, по какому расписанию запускаются задачи, когда последний раз делался бэкап, и т.д. Вы сохранили эти данные в xls-файл в удобном для работы виде, и пользуетесь этим документом. Остальные ваши коллеги, скорее всего, не пользуются – либо им некогда разбираться со структурой этого файла, либо они просто не знают о том, где он лежит.

Возникает задача – сделать так, чтобы эти данные были доступны всем, кому это необходимо, и с ними было удобно работать. Учитывая, что в организации уже используется Lotus, который есть у всех сотрудников, напрашивается вывод – сохранить эти данные в базу данных Lotus, и организовать несколько видов (View), чтобы представить эти данные в удобном виде.

Задача «сделать красиво и удобно в Lotus» выходит за рамки этой статьи, потому что требует знаний и опыта программирования в Lotus. Однако, мой опыт показывает, что даже при наличии нулевого опыта и знаний Domino Designer, за несколько дней можно сделать «на скорую руку» несколько представлений, отображающих полученные данные в удобном виде.

Попробуем для начала разобраться, как при помощи Powershell можно работать с документами Lotus.

Открытие сессии и подключение к базе данных.

Для начала, нужно открыть сессию Lotus Notes и подключиться к базе данных (или приложению). Для этого понадобится путь к базе (сервер и имя файла), и пароль пользователя. Чтобы пароль не хранился в открытом виде, мы его будем запрашивать и хранить как SecureString, и расшифровывать перед открытием сессии.
Предполагается, что на компьютере установлен коиент Lotus Notes.

$Credentials = Get-Credential
$notes=new-object -comobject Lotus.NotesSession
$securePassword = $Credentials.Password
$BSTR =[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
$notes.initialize([System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR))
$LotusDB = $notes.GetDatabase("Server", "LotusDB.nsf")

В результате мы получаем объект сессии Lotus Notes и объект базы данных Lotus.
Дальше мы будем работать с объектами документов внутри этой базы данных.

Создание документа.
Документ создаётся по шаблону (форме документа – Form), данные записываются в указанные поля документа.

$Document = $LotusDB.CreateDocument()
$result = $Document.AppendItemValue("Form", "Имя формы документа")

Запись атрибутов в новый документ.

$result = $Document.AppendItemValue("Status", "1")
$result = $Document.AppendItemValue("DueDateStatus", "0")
$result = $Document.AppendItemValue("Field_1", "Содержимое 1")
$result = $Document.AppendItemValue("Field_2", "Содержимое 2")
$result = $Document.ComputeWithForm($false, $false)
if ($result -eq "True")
    {
    $result = $Document.Save($false, $false)
    }
else
    {
    Write-Host "Failed: Document did not successfully computed with form"
    }

При каждом изменении полей документа, метод возвращает какие-то данные, которые нас не интересуют, и чтобы они не выводились на экран, мы присваиваем вывод переменной, и не используем её.
Перед сохранением, «хорошим тоном» будет проверить, соответствует ли содержимое документа форме.

Запись даты изменения в документ.
У любого документа есть атрибут – поле Modified, в котором прописана дата последнего изменения документа. Однако, нам может оказаться полезным хранить отдельно информацию о том, когда документ был изменён при помощи скрипта.

Для этого в форму документа нужно добавить поле ModifiedByScript, и изменить его тип на Date/Time. Но после этого, в это поле можно будет записать только данные типа NotesDate. Чтобы получить объект NotesDate, содержащий дату и время, понадобится получить текущую дату, и преобразовать её в нужный тип при помощи метода объекта NotesSession:

$date = Get-Date -Format "yyyy-MM-dd HH:mm"
$notesDate = $notes.CreateDateTime($date)
$result = $Document.AppendItemValue("ModifiedByScript", $notesDate)

Объект NotesDate понадобится нам дальше при поиске документа.

Дополнение: этот способ получения NotesDate работал нестабильно, и пришлось его немного изменить.

Поиск документа.
Попробуем найти документ заданного типа (переменная $Form), у которого значение поля, имя которого прописано в переменной $Field, имеет значение $Value.

$notesDate = $notesSession.CreateDateTime("1990-01-01")
$query = "SELECT form = '"+ $Form + "' & Status != '0' & " + $Field + " = '" +$Value + "'"
$docCollection = $LotusDBObject.Search($query, $notesDate, 0)

При поиске можно задать ограничение на количество найденных документов (0 – без ограничений).
В результате выполнения запроса мы получаем объект типа NotesCollection, содержащий несколько документов, найденных в базе данных.

Важно: при большом количестве документов этот способ работает очень медленно, более правильный способ – поиск документа в отсортированном виде.

Дальше, если мы точно знаем, что в результате может быть получен один документ, можно получить этот объект документа:

if ($docCollection)
{
$Document = $docCollection.GetFirstDocument()
}

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

Получение содержимого документа.
У документа Lotus есть свойство Items, которое содержит поля и их значения. Имя поля – Name, его содержимое – Text.
Чтобы получить значение одного поля документа (имя поля – переменная $Field), воспользуемся такой конструкцией:

$Value = ($Document.Items | where {$_.Name -eq $Field}).Text

либо

$Value = $Document.GetFirstItem($Field).Text

Также можно выгрузить все поля документа в переменную-массив типа «хэш-таблица».

$DocFields = @{}
foreach ($item in $Document.Items)
{
$DocFields[$item.Name] = $item.Text
}

С полученным массивом удобно работать в скрипте, так как он включает в себя содержимое документа в виде «поле – значение», не содержит ничего лишнего, и не требует обращения к различным «странным» методам COM-объектов Lotus.

Запись атрибутов в существующий документ.
При редактировании существующего документа, вместо AppendItemValue мы будем использовать метод ReplaceItemValue, иначе в одном поле документа окажется несколько значений, а это не совсем то, что нам нужно (однако, может понадобиться для каких-то задач).
Предположим, что мы уже записали все значения необходимых полей в хэш-таблицу, аналогичную полученной на предыдущем шаге. Можно изменить какие-то из этих значений, или добавить новые. Например:

$DocFields.Hostname = "srv12"
$DocFields.IPaddress = "192.168.0.12"
$DocFields.Description = "Сервер БД для большого корпоративного приложения"

Запишем эти данные в документ, добавим дату изменения скриптом, и сохраним его, предварительно проверив корректность по форме:

foreach ($field in $DocFields.Keys)
    {
    $result = $Document.ReplaceItemValue($field, $DocFields[$field])
    }
$date = Get-Date -Format "yyyy-MM-dd HH:mm"
$notesDate = $notes.CreateDateTime($date)
$result = $Document.ReplaceItemValue("ModifiedByScript", $notesDate)
# проверка и запись документа
$result = $Document.ComputeWithForm($false, $false)
if ($result -eq "True")
    {
    $result = $Document.Save($false, $false)
    }
else
    {
    Write-Host "Failed: Document did not successfully computed with form"
    }

Работа с Rich Text.
Намного сложнее работать с полями типа Rich Text, в которые может быть записано любое содержимое – текст с форматированием, вложенные файлы, таблицы, и т.д. Например, чтобы просто записать обычный текст в это поле, нужно отдельно добавлять в поле строки, и записывать текст в каждую строку. Пример кода, который записывает Rich Text в поле Body (содержимое – в переменной $docBody):

$bodyObject = $Document.CreateRichTextItem("Body")
$docBodyLines = $docBody -split "`r`n"
foreach ($line in $docBodyLines)
    {
    $bodyObject.AddNewLine()
    $bodyObject.AppendText($line)
    }

Для углублённого понимания работы с полями Rich Text и вообще с COM-объектами Lotus, нужно изучить соответствующую документацию – например, Lotus Domino Designer Programming Guide, Volume 2:LotusScript/COM/OLE Classes.

Командлеты для работы с Lotus

Чтобы упростить код основного скрипта, и сделать его больше похожим на Powershell, я решил вынести основные элементарные операции (такие, как открытие сессии, подключение к БД, создание и редактирование документа, поиск документа) в отдельную библиотеку командлетов Powershell для работы с Lotus.

# Командлеты для работы с БД Lotus, созданной по собственному шаблону
# (c) 2013 Миловидов А.Н.

function Open-LotusSession
# открывает лотусовую сессию
# аргументы:
#   пароль в Secure-String
# на выходе: объект сессии
{
[CmdletBinding()]
param
    (
    [Parameter(Mandatory=$True, HelpMessage='Credentials for connect to server')]
    [System.Management.Automation.PSCredential] $Credentials
    )

process
    {
#   инициализация сессии Lotus
    $securePassword = $Credentials.Password
    $notesSession=new-object -comobject Lotus.NotesSession
    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
    $notesSession.initialize([System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR))
    Write-Output $notesSession
    }
}

function Open-LotusDB
# открывает лотусовую сессию (если не задана в параметрах) и подключается к БД
# аргументы:
#   сервер
#   путь к базе
#   логин и пароль (Credentials)
# на выходе: объект БД
{
[CmdletBinding()]
param
    ([Parameter(Mandatory=$False, HelpMessage='Lotus server name')]
    [Alias('Server')] [string[]]$ServerName = "",
   
    [Parameter(Mandatory=$True, HelpMessage='Path to Lotus database')]
    [Alias('DBPath')] [string] $LotusDbPath,
   
    [Parameter(Mandatory=$False, HelpMessage='Credentials for connect to server')]
    [System.Management.Automation.PSCredential] $Credentials,

    [Parameter(Mandatory=$False, HelpMessage='Lotus Lotes session')] $LotusNotesSession = $null
    )

process
    {
    if (! $LotusNotesSession)
        {
        #   инициализация сессии Lotus, если она не была передана в качестве аргумента
        if (! $Credentials)
        # получение пароля Lotus, если он не был передан в качестве Credentials
            {
            # $UserName = [Environment]::UserName
            # $Credentials = Get-Credential -Credential $UserName
            #$color = $Host.UI.RawUI.ForegroundColor
            #$Host.UI.RawUI.ForegroundColor = "Red"
            $securePassword = Read-Host -AsSecureString -Prompt "Пароль к id-файлу Lotus Notes"
            #$Host.UI.RawUI.ForegroundColor = $color
            }
        else
            {
            $securePassword = $Credentials.Password
            }
        $LotusNotesSession = new-object -comobject Lotus.NotesSession
        $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
        $LotusNotesSession.initialize([System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR))
        }
    #   получение объекта БД
    $LotusDB = $LotusNotesSession.GetDatabase($ServerName, $LotusDbPath)
    Write-Output $LotusDB
    }
}

function Find-LotusDoc
# ищет документы заданного типа (Form) с заданным атрибутом и его значением
# аргументы:
#   объект БД
#   тип (Form) документа
#   название атрибута
#   значение атрибута
# на выходе: набор документов Lotus, подходящих по параметрам
{
[CmdletBinding()]
param
    (
    [Parameter(Mandatory=$True, HelpMessage='Lotus DB object')] [Alias('LotusDB')] $LotusDBObject,
    [Parameter(Mandatory=$True, HelpMessage='Document form')] [string]$Form,
    [Parameter(Mandatory=$False, HelpMessage='Attribute name')] [string]$Attribute,
    [Parameter(Mandatory=$False, HelpMessage='Attribute value')] [string]$Value,
    [Parameter(Mandatory=$False, HelpMessage='Not-equal match')] [switch]$NotEqual
    )
process
    {
    $notesSession = $LotusDBObject.Parent
    $notesDate = $notesSession.CreateDateTime("1990-01-01")
    #$query = "SELECT form = '"+ $Form + "' & " + '$' + "Ref=''"
    $query = "SELECT form = '"+ $Form + "'"
    if ($Attribute)
        {
        if ($notEqual)
            {
            $query += " & " + $Attribute + " != '" + $Value + "' & " + '$' + "Ref=''"
            }
        else
            {
            $query += " & " + $Attribute + " = '" + $Value + "' & " + '$' + "Ref=''"
            }
        }
    $docCollection = $LotusDBObject.Search($query, $notesDate, 0)
    Write-Output $docCollection
    }
}

function Find-LotusServerDoc
# ищет документ типа (form) ServerDocument с заданными "ключевыми" атрибутами Hostname, Profile, Cell, Node, Server
# атрибуты заданы в переменной - хэш-таблице ($переменная.атрибут=значение)
# на выходе - объект документа Lotus, либо пустое значение
{
[CmdletBinding()]
param
    (
    [Parameter(Mandatory=$True, HelpMessage='Lotus DB object')] [Alias('LotusDBObject')] $LotusDB,
    [Parameter(Mandatory=$True, HelpMessage='Document contents')] [System.Collections.Hashtable] $Content
    )
process
    {
    $key = $Content.HostName + " " + $Content.ProfileName + " " + $Content.Cell + " " + $Content.Node + " " + $Content.AppServer
    $NotesView = $LotusDB.GetView("(indexWASDocument)")
    $doc = $NotesView.GetDocumentByKey($key)
    Write-Output $doc
    }
}

function New-LotusDoc
# Аргументы:
# Объект лотусовой базы, в которой нужно создать документ
# Тип документа (форма, по которой создавать документ)
# Содержимое документа в виде массива хэш-таблицы (атрибут-значение)
# На выходе: объект документа Lotus
{
[CmdletBinding()]
param
    (
    [Parameter(Mandatory=$True, HelpMessage='Lotus DB object')] [Alias('LotusDB')] $LotusDBObject,
    [Parameter(Mandatory=$True, HelpMessage='Document form')] [string] $Form,
    [Parameter(Mandatory=$False, HelpMessage='Document contents')] [System.Collections.Hashtable] $Content
    )

process
    {
    # Получаем текущую дату в виде объекта NotesDateTime
    $date = Get-Date -Format "yyyy-MM-dd HH:mm"
    $notesSession = $LotusDBObject.Parent
    $notesDate = $notesSession.CreateDateTime($date)
    # Создаём документ
    $newdoc = $LotusDBObject.CreateDocument()
    # Записываем значения в документ
    $result = $newdoc.AppendItemValue("Form", $Form)
    $result = $newdoc.AppendItemValue("Status", "1")
    $result = $newdoc.AppendItemValue("DueDateStatus", "0")
    if ($Content)
        {
        foreach ($field in $Content.Keys)
            {
            $result = $newdoc.AppendItemValue($field, $Content[$field])
            }
        }
    # Записываем дату изменения скриптом в отдельный атрибут (он должен быть в форме документа)
    $result = $newdoc.AppendItemValue("ModifiedByScript", $notesDate)
    # Проверяем соответствие документа его форме
    $success = $newdoc.ComputeWithForm($false, $false)
    if ($success -eq "True")
        {
        $result = $newdoc.Save($false, $false)
        Write-Output $newdoc
        }
    else
        {
        Write-Verbose "Failed: Document did not successfully computed with form"
        Write-Output $null
        }
    }
}

function Add-LotusDocAttributes
# Добавляет/изменяет значения полей документа
# Аргументы:
# Объект документа
# Значения полей документа в виде массива хэш-таблицы (атрибут-значение)
# На выходе: объект документа, если документ изменён, и Null, если не удалось изменить документ
{
[CmdletBinding()]
param
    (
    [Parameter(Mandatory=$True, ValueFromPipeline=$True,
    ValueFromPipelineByPropertyName=$True,
    HelpMessage='Document object')]
    $Document,

    [Parameter(Mandatory=$True, HelpMessage='Document contents')]
    [System.Collections.Hashtable] $Content
    )
process
    {
    # Получаем текущую дату в виде объекта NotesDateTime
    $date = Get-Date -Format "yyyy-dd-MM HH:mm"
    $LotusDBObject = $Document.ParentDatabase
    $notesSession = $LotusDBObject.Parent
    $notesDate = $notesSession.CreateDateTime($date)
    # Записываем значения полей в документ
    foreach ($field in $Content.Keys)
        {
        $result = $Document.ReplaceItemValue($field, $Content[$field])
        }
    # Записываем дату изменения скриптом, проверяем соответствие докумена форме
    $result = $Document.ReplaceItemValue("ModifiedByScript", $notesDate)
    $result = $Document.ComputeWithForm($false, $false)
    if ($result -eq "True")
        {
        $result = $Document.Save($false, $false)
        Write-Output $Document
        }
    else
        {
        Write-Verbose "Failed: Document did not successfully computed with form"
        Write-Output $null
        }
    }
}

Обладая даже таким небольшим инструментарием, можно «приручить» Lotus, упростить работу с большим количеством документов, и сделать его помощником в выполнении ряда административных задач. Удачи!

(c) 2013 Миловидов А.Н. http://milovidoff.ru
При копировании просьба ссылаться на источник.

Работаем с Lotus Notes через Powershell: 4 комментария

  1. Кирилл

    Прошу прощения, но я совершенно не могу понять откуда определяются все три переменных $Form, $Field, $Value?

    1. Админ Сайта

      $Form – это переменная, которая здесь означает форму документа Lotus Notes. Например, я создал в Lotus Notes документ с формой AppServer, в этой форме прописано, что у документа есть поля Hostname, IPAddress, WASProductName, WASVersion, Profile, Cell, Node, Server, и т.д.
      $Field – это название поля документа Lotus Notes.
      $Value – это значение поля, по которому в моём примере производится поиск документа.

  2. Перси

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

    1. Админ Сайта

      Наверное можно автоматизировать и создание пользователя, только не уверен, нужно ли для этого использовать COM-объекты, или воспользоваться какими-то стандартными средствами администрирования Lotus Notes.

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *