В компании, где я работаю, имеются рабочие станции, подключенные к двум отдельным сетям. Я активно использую почтовые учетные записи в обеих сетях, но работаю на одном компьютере (и в одной сети) намного больше, чем на другом. Мне очень не хочется для просмотра почты то и дело повторно регистрироваться на другом компьютере, который блокируется через 5 минут бездействия. .

Перехват ответов на почтовые сообщения

Outlook, как все приложения Microsoft, основан на обработке событий. Тот или иной программный фрагмент запускается в ответ на определенные действия пользователя, такие как нажатие на кнопку, переключение между элементами управления или нажатие клавиш. Другие действия являются реакцией на события в работе самого приложения, например запуск или завершение работы. Наконец, имеются такие специфические события, как добавление встречи, назначение уровня важности сообщению или получение нового почтового сообщения. Относительно приложений, управляемых с помощью событий, необходимо помнить, что правильное размещение программного кода для обработки событий — половина успеха. Если вы выберете не то событие, обязательно столкнетесь с неприятными побочными эффектами, включая отсутствие реакции на событие, неоднократную реакцию на событие или реакцию не в тот момент, в который нужно.

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

Наиболее логичным кандидатом представляется событие MailItem_Reply. Что может быть проще? Мы хотим, чтобы программный код запустился при нажатии кнопки «Ответить» (Reply). К сожалению, событие Reply возникает только при создании ответа на открытые сообщения (элементы MailItems). Если вы отвечаете на сообщение, выделенное в панели списка сообщений папки «Входящие», но при этом не открытое, событие Reply не возникает.

Другим логичным местом для размещения ответного действия является кнопка «Ответить». Вы можете перехватывать событие нажатия на кнопку панели инструментов (Click), как показано в следующем примере:

Dim WithEvents objReplyButton
   As Office.CommandBarButton
Set objReplyButton = activeExplorer.
   CommandBars.FindControl (, 354)

Однако это тоже не очень подходящее место, так как здесь код активируется после события Reply, так что вы не сможете получить управление им.

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

Редактор Visual Basic

Все приложения Microsoft Office выпускаются с полнофункциональной интегрированной средой разработки (IDE), называемой «Редактор Visual Basic» (Visual Basic Editor), которая предоставляет интерфейс для доступа из программного кода к моделям объектов в приложениях, так что вы можете вызывать методы объектов, устанавливать свойства объектов и реагировать на события, связанные с объектами. Код, используемый для этого, является специализированным подмножеством языка Visual Basic, называемым Visual Basic for Applications (VBA).

Закладка «Разработчик» (Developer) на ленте Outlook предоставляет доступ к редактору Visual Basic и другим инструментам разработки. Однако по умолчанию эта закладка отключена для защиты от вирусов и другого вредоносного кода. Чтобы вы могли ее использовать, нужно выполнить следующие действия.

  1. На закладке «Файл» выберите пункт «Параметры» и далее «Центр управления безопасностью» (Trust Center).
  2. Нажмите кнопку «Параметры центра управления безопасностью», затем слева выберите пункт «Параметры макросов».
  3. Выберите необходимый уровень безопасности макросов, с учетом того, что эти настройки относятся ко всем макросам, а не только к разработанным вами лично. Если вы не желаете давать карт-бланш всем макросам, можете потребовать от Outlook показывать запрос на подтверждение для каждого запускаемого макроса. В этом случае вы сможете решить, какой макрос можно выполнять. Этот режим называется «Уведомления для всех макросов».
  4. Перезапустите Outlook, чтобы изменения вступили в силу.

С левой стороны закладки появится кнопка Visual Basic, как показано на экране 1.

 

Закладка «Разработчик» (Developer) в Outlook 2010
Экран 1. Закладка «Разработчик» (Developer) в Outlook 2010

Обработка события MailItem_Open

Секрет при доступе к событию какого-либо объекта в Outlook состоит в том, что необходимо добавить в описание объекта ключевое слово WithEvents. Следующий код должен быть помещен в начало модуля ThisOutlookSession:

Public WithEvents mymsg
   As Outlook.mailitem

После добавления описания объекта вы можете осуществлять доступ к нему и его событиям из раскрывающихся списков Object и Procedure. На экране 2 видно, что события Close и Open выделены жирным шрифтом, так как я добавил эти события к моему коду. Чтобы добавить событие, вам надо всего лишь выбрать его из списка, и Outlook сразу добавит к модулю пустую подпрограмму:

Private Sub mymsg_Open (Cancel As
   Boolean)
End Sub

 

Выпадающие списки объектов и процедур
Экран 2. Выпадающие списки объектов и процедур

Привязка объекта myMsg к событию Inspectors_NewInspector

На данном этапе мы описали объект типа MailItem и создали событие для него, но нам необходимо его где-то сформировать. Подходящее место для этого — событие Inspectors_NewInspector. Объект Inspectors — это коллекция, содержащая объекты типа Inspector, представляющие собой все открытые инспекторы. Каждый раз, когда вы открываете окно, в котором отображается элемент Outlook, данный элемент является инспектором. Но здесь мы снова разбрасываемся, так как инспектор может содержать все что угодно, от новой встречи до новой задачи. Однако область действий у нас сужается до списка новых элементов. То есть открытие существующего почтового сообщения не создает событие Inspectors_NewInspector.

Мы можем обрабатывать события объектов Inspectors таким же образом, как и события объектов MailItem. Вначале сделаем описания с использованием ключевого слова WithEvents:

Public WithEvents myOlinspectors As
   Outlook.Inspectors

Теперь мы можем вызвать подпрограмму newInspector ():

Private Sub myOlInspectors_
   NewInspector (ByVal Inspector As
   Inspector)
End Sub

Перед тем как мы сформируем наш объект myMsg типа MailItem, нужно выполнить две проверки, чтобы обрабатывать только те инспекторы, которые нам необходимы. Сначала следует убедиться, что обрабатываемый элемент действительно является почтовым сообщением. Мы совсем не хотим обрабатывать элементы другого типа. Инспектор, передаваемый в подпрограмму, имеет свойство CurrentItem, которое указывает на элемент, который в данный момент просматривает пользователь. Мы можем проверить его свойство Class, чтобы определить, является ли он классом MailItem. В действительности для этой цели может быть использована константа, называемая olMail.

Вторая необходимая проверка — проверка строки с уникальным ID, который назначается элементу при создании поставщиком системы хранения программного интерфейса Messaging API (MAPI). В листинге 1 приведен код, осуществляющий такую проверку. Свойство EntryID не присваивается элементу Outlook до тех пор, пока он не будет сохранен или отправлен. Оно будет идентифицировать наши ответы среди ответов других людей. Формирование объекта MailItem, как показано в листинге 1, активирует событие Open для этого объекта.

Событие myMsg_Open

Событие myMsg_Open — идеальное место для формирования значений свойств сообщения, поскольку оно еще не появилось на экране. А после его появления эти значения можно легко менять. Следующие секции показывают пошаговое выполнение таких действий, как формирование значения полей «Кому», «Тема» и «Тело сообщения» таким образом, чтобы они соответствовали исходному сообщению.

После того как вы сформировали значения объекта myMsg типа MailItem в событии Insepctors_newInpector (), каждое новое сообщение будет инициировать событие Open для этого сообщения, независимо от того, является данное сообщение ответом, переадресацией или новым сообщением.

Идентификация переадресованных сообщений. Для идентификации наших ответов мы можем использовать префикс RE:, который Outlook добавляет к теме сообщения. И более того, в наших переадресованных сообщениях строка темы будет иметь вид: From <имя отправителя>: FW: <исходная тема сообщения>. То есть тема, начинающаяся с RE: From, означает, что сообщение было переадресовано из другой сети.

И наконец, отправителем должны быть вы сами. Данная информация содержится в поле MailItem.To:

Private Sub myMsg_Open (Cancel As
   Boolean)
      If myMsg.subject Like "RE: From*" _
         and myMsg.To Like
         "Gravelle*Robert*") Then
      End If
End Sub

Извлечение из темы сообщения информации об исходном отправителе. Тема сообщения будет содержать либо отображаемое имя (display name) отправителя, либо его почтовый адрес, в зависимости от того, в какой сети находился отправитель. В любом случае нам надо проанализировать текст темы между RE: From и двоеточием. Следующий фрагмент кода выполняет это действие:

Dim sender As String, pos As Integer
pos = InStr (9, myMsg.Subject, ":") — 9
sender = Trim (Mid (myMsg.Subject, 9, pos))

Присвоение полю «Кому» значения адреса исходного отправителя. Замена вашего адреса адресом исходного отправителя не гарантирует, что почтовый сервер распознает отправителя. Здесь может помочь вызов функции Recipient.Resolve (). Сбой в разрешении адреса чаще всего обусловлен использованием отображаемого имени вместо полного почтового адреса. Это нетрудно исправить, так как мы знаем сетевое имя узла-отправителя. В моем случае преобразование отображаемого имени (имеющего вид Lastname, Firstname) в правильный почтовый адрес (вида Firstname.Lastname@hostname.com) потребует просто перестановки частей имени, вставки точки между ними и добавления почтового адреса. Повторный вызов Resolve () подтвердит правильность этого действия. В случае неудачи я просто оставлю поле «Кому» пустым. Однако на практике мне такие случаи не встречались. Листинг 2 содержит код для присвоения полю «Кому» значения адреса исходного отправителя.

Задание темы сообщения. Как и во всех переадресованных сообщениях, текст исходной темы начинается после префикса FW:. Для нахождения позиции оригинальной темы используется функция InStr (). Текст из следующего примера был добавлен к идентификатору ответа RE:; так, исходная строка REMINDER: Network Maintenance извлекается из From Smith, Bob: FW: REMINDER: Network Maintenance следующим образом:

pos = InStr (9, subject,":") — 9
   'start search after the "RE: From"
pos = InStr (pos + 1, myMsg.Subject,
   "FW:")
myMsg.Subject = Left (myMsg.Subject,
   4) & _
      Trim (Mid (myMsg.Subject, pos + 3))

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

Лучше всего начать именно с тела сообщения, а не с манипуляций со строкой темы. Как вы увидите, код, который находит исходное сообщение, называемое родительским, использует свойство ConversationTopic. Изменение темы сообщения меняет и это свойство.

Поиск родительского сообщения состоит из двух шагов. Вначале программа проверяет текущее выбранное сообщение в активном окне списка сообщений. Текущий выбранный элемент в списке — наиболее вероятный родитель. Мы можем подтвердить это, сравнив со свойством ConversationIndex нашего ответа на данное сообщение. Когда вы отвечаете на сообщение, Outlook удаляет 10 символов (5 байт) из ConversationIndex. Таким образом, ConversationIndex родительского сообщения без последних 10 символов будет соответствовать свойству ConversationIndex нашего ответа.

Для формирования тела сообщения нам нужно проверить его формат, так как он может быть HTML, RTF или простым текстом. Для присвоения соответствующего свойства тела сообщения используется конструкция Select Case, приведенная в листинге 3.

Как я уже отметил, текущее выбранное сообщение в активном окне списка сообщений — наиболее вероятный родитель ответного сообщения. Однако вполне возможно, что это не так. Например, если для ответа вы использовали кнопку на объекте MailItem Inspector, то после открытия переадресованного почтового сообщения вы можете выбрать сколько угодно других сообщений (при этом вы даже можете быть совсем в другой папке). Предполагая, что вы находитесь в той же папке, что и родительское сообщение, для поиска родителя вы можете использовать функцию MAPIFolder.Items.Restrict (). Эта функция получает специально сформированную строку, содержащую свойство и его значение, которое необходимо найти. Функция возвращает коллекцию элементов. Затем для поиска родителя у всех этих элементов проверяется свойство ConversationIndex. В листинге 4 приведен программный код для вызова функции MAPIFolder.Items.Restrict ().

Избавление от диалоговой панели Outlook

В силу своей широкой популярности Outlook длительное время являлся мишенью для хакеров. Для противодействия попыткам злоумышленников Microsoft реализовала в продукте многочисленные средства защиты. Я целиком и полностью за безопасность, но не хочу, чтобы служба безопасности Outlook перехватывала мой собственный код. Я совсем не хочу вывести из строя мой компьютер, во всяком случае умышленно!

Все версии Microsoft Office 2010, 2007, 2003, 2000 и 98 включают обновление системы безопасности. Когда макрос пытается прочитать любое свойство сообщения, отображается диалоговая панель с предупреждением, показанная на экране 3. Вы мало что можете сделать с этой надоедливой диалоговой панелью; даже установка низкого уровня защиты (что я никому не рекомендую) не избавляет от нее.

 

Предупреждение безопасности в Outlook 2010
Экран 3. Предупреждение безопасности в Outlook 2010

Однако существуют обходные пути. Мой любимый способ — использование компонента Redemption. Redemption — это обычная COM-библиотека; после регистрации в системе она доступна в любом языке программирования (например, VB, VBA, VC++, Delphi). Redemption использует расширенный интерфейс MAPI (на который не действует то самое обновление безопасности, так как он недоступен из языков сценариев) для реализации функциональности, заблоки­ро­ванной обновлением безопасности. Все объекты Safe*Item в Redemption имеют свойство Item, которое должно ссылаться на какой-либо элемент Outlook. Через свойство Item можно получить доступ к любым свойствам и методам объекта типа MailItem, в том числе и заблокированным. Для заблокированных свойств и функций объекты Redemption обходят объектную модель Outlook и действуют так, как Outlook без установленного обновления безопасности.

Использование Redemption в событии myMsg_Open ()

Чтобы код для события MailItem.Open () работал с Redemption, необходимо заменить все ссылки на отправителя и получателей объекта MailItem соответствующими значениями SafeMailItem из Redemption. Единственный недостаток использования SafeMailItem состоит в том, что вы не сможете получить доступ к информации о получателе, пока не сохраните сообщение. Таким образом, вы не сможете извлечь информацию о списке получателей для нового сообщения. Однако эта проблема легко решается: надо вызвать метод Save () для исходного объекта MailItem до присвоения его объекту SafeMailItem из Redemption. Эта операция добавит элемент SafeMailItem в папку «Черновики». После того как вы присвоите свойству Item объекта SafeMailItem ссылку на исходное почтовое сообщение, вы получите доступ как к свойствам MailItem, так и к дополнительным свойствам Redemption.

За исключением добавления объекта Redemption.SafeRecipient для управления разрешением почтовых адресов, программный код практически идентичен коду для исходного события Open. В листинге 5 приведен код с использованием Redemption для определения отправителя и строки темы. В нем отсутствует необязательная часть для формирования тела сообщения.

Можно приступать

Хотя ответ на переадресованные почтовые сообщения не так прост, как создание правил, Outlook предоставляет возможности для этого, если, конечно, вы желаете погрузиться в мир событий Outlook и VBA-кода. Многие стараются избегать этого из-за боязни внести ошибку в свое любимое почтовое приложение. Однако все, что вам нужно, — это немного времени для выбора наиболее подходящих событий, в которые следует поместить ваш программный код.

Листинг 1. Формирование объекта myMsg класса MailItem

Private Sub myOlInspectors_NewInspector(ByVal Inspector As Inspector)
    If Inspector.CurrentItem.Class = olMail Then
        If Len(Inspector.CurrentItem.EntryID) = 0 Then
            Set myMsg = Inspector.CurrentItem
        End If
    End If
End Sub)

Листинг 2. Замена вашего почтового адреса адресом исходного отправителя

With myMsg.recipients
  .Remove 1
  .Add sender
  .Item(1).Resolve
  If Not .Item(1).Resolved Then
    'could be using "lastname, firstname" display format
    'used for known users on originating network
    If InStr(1, sender, ", ") Then
      Dim senderNames() As String
      .Item(1).Delete
      senderNames = Split(sender, ", ", 2)
      'reverse name order and convert to
      'firstname.lastname@networkaddress format
      sender = senderNames(1) & "." & senderNames(0) & "@microsoft.com"
      .Add sender
      .Item(1).Resolve
    End If
    'didn't work. Leave it empty.
    If Not .Item(1).Resolved Then myMsg.To = ""
  End If
End With

Листинг 3. Заполнение тела сообщения текстом исходного сообщения

If safemsg.subject Like "RE: From*" _
And safemsg.To Like "Gravelle*Robert" Then
  'set the body to the original email
  Set myOlSel = Application.ActiveExplorer.Selection
  If myOlSel.Count = 1 Then
    If myOlSel.Item(1).Class = OlObjectClass.olMail Then
      Set oOriginalEmail = myOlSel.Item(1)
      Dim strParentConversationIndex As String
      strParentConversationIndex = Left(oOriginalEmail.ConversationIndex, _
  Len(oOriginalEmail.ConversationIndex) - 10)

      If strParentConversationIndex <> myMsg.ConversationIndex Then _
        Set oOriginalEmail = FindParentMessage(myMsg)

      If Not oOriginalEmail Is Nothing Then
        Select Case oOriginalEmail.BodyFormat
          Case olFormatHTML
            myMsg.HTMLBody = oOriginalEmail.HTMLBody
          Case olFormatPlain, olFormatRichText
            myMsg.Body = oOriginalEmail.Body
        End Select
      End If
    End If
  End If
...

Листинг 4. Использование свойств ConversationIndex и ConversationTopic для поиска исходного сообщения

Function FindParentMessage(msg As Outlook.MailItem) As Outlook.MailItem
  Dim strFind As String
  Dim strIndex As String
  Dim fld As Outlook.MAPIFolder
  Dim itms As Outlook.Items
  Dim itm As Outlook.MailItem

  On Error Resume Next

  strIndex = Left(msg.ConversationIndex, _
                  Len(msg.ConversationIndex) - 10)
  Set fld = Application.Session.GetDefaultFolder(olFolderInbox)
  strFind = "[ConversationTopic] = " & _
            Chr(34) & msg.ConversationTopic & Chr(34)
  Set itms = fld.Items.Restrict(strFind)
  For Each itm In itms
    If itm.ConversationIndex = strIndex Then
      Set FindParentMessage = itm
      Exit For
    End If
  Next
End Function

Листинг 5. Полный текст подпрограммы MailItem_Open()

Private Sub myMsg_Open(Cancel As Boolean)
  Dim safemsg As New SafeMailItem

  myMsg.Save
  safemsg.Item = myMsg

  If safemsg.subject Like "RE: From*" _
  and safemsg.To Like "Gravelle*Robert*" Then
    Dim sender As String, subject As String, _
        pos As Integer, sendTo As Redemption.SafeRecipient
    subject = safemsg.subject
    pos = InStr(9, subject, ":")  9  'start search after the "RE: From"
    sender = Trim(Replace(Mid(subject, 9, pos), vbTab, ""))
    safemsg.recipients.Remove 1
    safemsg.recipients.Add sender
    Set sendTo = safemsg.recipients(1)
    sendTo.Resolve
    If Not sendTo.Resolved Then
      'could be using "lastname, firstname" display format
      'used for known users on originating network
      If InStr(1, sender, ", ") Then
        Dim senderNames() As String
        sendTo.Delete
        senderNames = Split(sender, ", ", 2)
        sender = senderNames(1) & "." & senderNames(0) & _
                 "@cbsa-asfc.gc.ca"
        safemsg.recipients.Add sender
        Set sendTo = safemsg.recipients(1)
        sendTo.Resolve
      End If
    End If
    myMsg.To = IIf(sendTo.Resolved, sendTo.Address, "")

    'set the subject
    pos = InStr(pos + 1, subject, "FW:")
    myMsg.subject = Left(subject, 4) & Trim(Mid(subject, pos + 3))
  End If
End Sub

Роб Грейвилл — основатель сайта GravelleConsulting.com, создает системы для коммерческих и правительственных организаций Канады