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

* Два способа переместить почтовый элемент в пользовательскую папку после отправки.

* Решение на клиентской стороне, не зависящее от поставщика почтового сервера.

* Лучшие способы распространения кода VBA среди пользователей.

По умолчанию Microsoft Outlook сохраняет копии отправленных элементов в папке Sent Items («Отправленные»). Но типовой подход не всегда приемлем в компании, сотрудники которой ежедневно отправляют сотни почтовых сообщений. Часто сообщения электронной почты упорядочивают по темам или направлениям бизнеса. Этот метод отличается от сортировки по беседам, так как каждое направление бизнеса может содержать несколько потоков бесед. Распределять по папкам полученные сообщения достаточно просто; другое дело — отправленные сообщения. Основная трудность заключается в том, что несмотря на возможность переместить копию почтового элемента после отправки, в Outlook нет правила или параметра для перемещения самого отправленного элемента.

.

Ограничения подхода на основе правил

Как правило, если поведение программы не соответствует моим ожиданиям, я стараюсь найти программное решение. Но прежде чем вплотную заняться кодом VBA, рассмотрим параметры и правила электронной почты Outlook. Только исчерпав их возможности, следует начинать искать более сложные решения. Посмотрим, что можно сделать с помощью мастера Outlook Rules Wizard. Начиная с пустого правила, можно указать, следует ли применить правило к входящим или исходящим сообщениям, как показано на экране 1. После того, как выбраны критерии отбора сообщений, можно указать папку для копирования этих сообщений (экран 2).

 

Использование мастера Rules Wizard для применения правила к отправленным элементам
Экран 1. Использование мастера Rules Wizard для применения правила к отправленным элементам

 

Диалоговое окно выбора папки для мастера правил
Экран 2. Диалоговое окно выбора папки для мастера правил

Обратите внимание, что правило создает и перемещает копию сообщения; оригинал остается в папке Sent Items. Единственный способ решить проблему — снять флажок Save copies of messages in Sent Items folder («Сохранять копии сообщений в папке «Отправленные"») в разделе Message handling («Обработка сообщений») в окне E-mail Options («Параметры почты»), как показано на экране 3. Недостаток данного подхода в том, что Outlook не может сохранять копии любых отправленных сообщений, и пользователь лишается доступа ко всем отправленным сообщениям, на которые не распространяется это правило.

 

Диалоговое окно «Параметры?почты»
Экран 3. Диалоговое окно «Параметры ?почты»

Решение VBA: используем события Send почтового элемента

Разочаровавшись в правилах и параметрах, я засучил рукава, приготовил кофе и взялся за работу.

Как и мои предшественники, пытавшиеся решить эту путаную задачу, я начал с события MailItem_Send(). Такой подход кажется естественным: вы отправляете почтовое сообщение, а затем перемещаете его. Единственная проблема: сообщения не перемещаются в папку Sent Items до тех пор, пока не завершится событие Send. Поэтому любая попытка обнаружить сообщение в папке Sent Items оказывается бесплодной.

Куда поместить код?

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

Я вспомнил о событии ItemAdd() папки назначения. Оно возникает всегда, когда в коллекцию Items добавляется один или несколько элементов. Но по зрелом размышлении я понял, что этому событию свойственны те же проблемы временного согласования, что и событию MailItem_Send(). Кроме того, необходимо дублировать программный код для всех папок назначения. Дублировать исходный текст не годится, забудьте об этой идее.

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

Фильтрация сообщений по критериям

В идеальном мире можно вызвать макрокоманду VBA из правила. В результате фильтрация почты выполняется правилом, а макрокоманда перемещает сообщение. В Outlook 2002 появилась возможность запустить сценарий по прибытии входящих сообщений, но, к сожалению, она не распространяется на исходящие сообщения. Не расстраивайтесь: это не самый эффективный способ запуска сценария. Я испробовал его несколько раз и выяснил, что он очень ненадежен. Время от времени возникают ошибки и правило деактивируется. Лучше организовать фильтрацию непосредственно в событии oMsg_Send().

Предположим, вам требуется переместить все сообщения, адресованные нескольким корреспондентам в компании RobGravelleAndCo.com, в папку Outlook с именем FOSS Export (CR-035), как показано на экране 4. Объект Recipients содержит коллекцию элементов Recipient, каждый из которых включает свойства и методы, связанные с одним получателем. Одно из свойств Recipient — объект AddressEntry, в котором содержатся сведения об адресе получателя, в том числе адрес электронной почты. У Recipient есть свойство, именуемое Address для адреса электронной почты. Это свойство задействовано в исходном тексте oMsg_Send с меткой B листинга 1.

 

Папки Outlook
Экран 4. Папки Outlook

Вместо того чтобы пытаться вручную удалить сообщение после отправки, можно присвоить флагу DeleteAfterSubmit почтового элемента значение true, чтобы это сделала программа Outlook. Просто помните, что установка флага DeleteAfterSubmit из диалогового окна MailItem Properties приведет к удалению всех отправленных сообщений! Это радикальное решение, хотя требуется всего лишь переместить определенные сообщения.

Нельзя перемещать сообщение из события MailItem_Send(), так как оно еще не обработано программой Outlook. Попытка сделать это приведет к ошибке времени выполнения. Как утверждают в Microsoft, предпочтительный способ управлять этой тонкой операцией — сначала клонировать сообщение с помощью функции Copy(), затем переместить клон. Это действие нельзя назвать истинным перемещением сообщения, но результат тот же: после перемещения клона исходное сообщение удаляется благодаря флагу DeleteAfterSubmit.

После этого нужно получить ссылку на соответствующую папку. Для работы с особыми папками требуется немного больше усилий, чем при использовании стандартных папок Outlook. Нельзя просто ввести имя папки для вызова функции GetFolder (такой нет). Вместо этого нужно перейти к специальной папке из одной из стандартных папок. В нашем случае папка FOSS Export (CR-035) параллельна папке Inbox («Входящие») в корневом почтовом ящике. Чтобы получить ссылку на стандартную папку Outlook, просто вызовите функцию Application.Session GetDefaultFolder() с одним из значений перечисления библиотеки olDefaultFolders. Например, следующий код извлекает Inbox:

Set olInbox = Application.Session.GetDefaultFolder(olFolderInbox)

Можно добраться до нужной папки с помощью кода:

Set oBusinessFolder = Application.Session.GetDefaultFolder(olFolderInbox).Parent.Folders(BUSINESS_FOLDER)

BUSINESS_FOLDER — константа для имени папки. oBusinessFolder можно напрямую передать в подпрограмму MailItem.Move(), как требуется для объекта MAPIFolder. Аналогичным образом можно получить вложенную папку с помощью свойства коллекции Folders:

Set ObjFolder = Application.Session.GetDefaultFolder(olFolderInbox).Folders("<имя_вложенной_папки>»)

Visual Basic Editor

Все приложения Microsoft Office поставляются с полнофункциональной средой разработки, именуемой Visual Basic Editor. Она обеспечивает интерфейс для доступа к объектным моделям приложения через программный код. В результате пользователь может вызывать методы объектов, задавать методы объектов и отвечать на события объектов. Для этих целей используется код VBA, особое подмножество языка Visual Basic.

Для доступа к Visual Basic Editor и другим инструментам разработки на ленте Office имеется вкладка Developer («Разработчик»). Но по умолчанию эта вкладка отключена, чтобы уменьшить уязвимость для вирусов и других вредоносных программ. Прежде чем воспользоваться этой вкладкой, необходимо выполнить следующие шаги.

1. В Outlook выберите Outlook Options («Параметры Outlook») на вкладке File («Файл»), чтобы открыть диалоговое окно Outlook Options.

2. В диалоговом окне Outlook Options нажмите кнопку Trust Center («Центр управления безопасностью»).

3. Щелкните Trust Center Settings («Параметры центра управления безопасностью»), а затем выберите параметр Macro Settings («Параметры макросов») слева.

4. Выберите удобный уровень безопасности Macro, с учетом того, что этот параметр влияет как на ваши, так и чужие макрокоманды. Если разрешить все макрокоманды, то Outlook будет отображать приглашение каждый раз при запуске макрокоманды. Таким образом, пользователь может решить, нужно ли запускать макрокоманду. Этот режим называется Notifications for all macros («Уведомления для всех макросов»).

5. Перезапустите Outlook, чтобы изменения вступили в силу.

Кнопка Visual Basic, которую мы видим на экране 5, находится на дальнем левом краю вкладки Developer. На экране 6 показан Visual Basic Editor.

 

Вкладка «Разработчик» с кнопкой Visual Basic
Экран 5. Вкладка «Разработчик» с кнопкой Visual Basic

 

Visual Basic Editor с видимой областью проверки
Экран 6. Visual Basic Editor с видимой областью проверки

Событие MailItem Send()

Чтобы события объекта были доступны в раскрывающемся списке Declarations («Объявления») в Visual Basic Editor (экран 7), нужно воспользоваться ключевым словом WithEvents для объявления объекта.

 

Раскрывающийся список «Объявления» в?Visual?Basic Editor
Экран 7. Раскрывающийся список «Объявления» в?Visual?Basic Editor

Следующие объявления объектов позволяют обратиться к событию MailItem Send():

Public WithEvents oInspectors As Outlook.Inspectors
Public WithEvents oMsg As Outlook.MailItem

Коллекция Inspectors содержит объекты Inspector для всех открытых инспекторов (то есть окно, в котором отображается информация об элементе Outlook). Ссылка на коллекцию Inspectors задается в событии Application_StartUp():

Private Sub Application_Startup()
Set oInspectors = Application.Inspectors

End Sub

Привязка oMsg к текущему инспектору

Устанавливая ссылки MailItem в событии Inspectors_NewInspector, мы указываем, что обрабатываются только новые сообщения. Открытие ранее принятого почтового сообщения не приведет к возникновению события Inspectors_NewInspector.

Inspector, переданный подпрограмме, имеет свойство CurrentItem, которое относится к элементу, просматриваемому пользователем. Проверив свойство Class этого элемента, можно определить, действительно ли это почтовый элемент. Для такой цели можно воспользоваться константой с именем olMail. Также необходимо проверить уникальную строку идентификатора, которую поставщик хранилища Messaging API (MAPI) назначает при создании элемента в хранилище. Поэтому свойство EntryID назначается элементу Outlook только после того, как элемент сохранен или отправлен. Эта проверка, выполняемая фрагментом кода с меткой A в листинге 1, отличает новые элементы электронной почты от существующих. Назначение почтового элемента таким способом приводит к возникновению его событий, в том числе события Send.

Событие oMsg_Send в действии

Я направил часть вывода в область проверки Immediate (она показана в нижней панели Visual Basic Editor на экране 6), чтобы протестировать процесс. Щелкните View на панели меню, а затем Immediate Window («Область проверки»), если она невидима. На экране 8 показаны типичные результаты в случае, если сообщение адресовано только проверяемому узлу. Это сообщение адресовано трем получателям: одному в поле To («Кому»), одному в поле CC («Копия») и одному в поле BCC («Скрытая»). Все три получателя содержались в коллекции получателей почтового элемента. Адрес BCC — RobGravelleAndCo.com, как показано на экране 9.

 

Пример вывода события oMsg_Send Event
Экран 8. Пример вывода события oMsg_Send Event

 

Коллекция получателей MailItem
Экран 9. Коллекция получателей MailItem

Последним тестом был ответ на это сообщение с удаленным получателем RobGravelleAndCo.com. Как и ожидалось, правило не переместило отправленный элемент, как показано на экране 10.

 

Проверка правила отправки
Экран 10. Проверка правила отправки

Альтернативное решение: использование события Items_ItemAdd папки Sent Items

Решение oMsg_Send — удачный выбор, если новые сообщения уже обрабатываются и необходимо ссылаться на Inspector нового элемента. Альтернативное решение помещает код в событие Items_ItemAdd() папки Sent Items. Исходный текст ThisOutlookSession для данного решения показан в листинге 2.

Размещение основной логики в событии Items_ItemAdd() дает два преимущества. Во-первых, уменьшается размер исходного текста. Во-вторых, это очень эффективный способ. Все отправленные элементы оказываются в папке Sent Items, если только не созданы обходные правила или не снят флажок Save copies of messages in Sent Items («Сохранять копии сообщений в папке «Отправленные"») в окне E-mail Options («Параметры почты»). Обратите внимание, что оба решения применяются к одной учетной записи почтового ящика. Поэтому, если нужно сходным образом обработать несколько почтовых ящиков, следует присоединить обрабатывающий код к каждому событию папки SentItems, как показано в листинге 3.

Получение доступа к событию Items_ItemAdd() папки Sent Items

Событие ItemAdd() является членом объекта коллекции Items, поэтому необходимо использовать ключевое слово WithEvents в верхней части модуля ThisOutlookSession для объявления объекта типа Items:

Public WithEvents olSentItems As Items

Здесь же находится информация бизнес-папки. Если ожидается большой объем электронной почты, связанный с определенным направлением деятельности, то полезно создать глобальную ссылку на эту папку, как показано во фрагменте с меткой A в листинге 2. Как и ранее, ссылки на объект назначаются в событии Application_StartUp(). Как показано во фрагменте с меткой B листинга 2, ссылка на бизнес-папку дана в связи с папкой Sent Items (то есть на одном уровне с папкой «Входящие».)

Измененный код правила

Устанавливать флаг DeleteAfterSubmit для создания копии почтового элемента более не требуется. Однако необходимо проверить тип Class элемента, так как параметр Item представляет собой универсальный объект. Объекты, отличные от сообщений электронной почты, такие как Meeting Item (элемент собрания), можно размещать в папке Sent Items. Я также предпринял дополнительный шаг для сохранения элемента в соответствующем объекте MailItem, чтобы активизировать функцию автозавершения среды разработки. Этот шаг можно пропустить, если точно известны свойства, к которым требуется получить доступ.

Событие oSentItems_ItemAdd в действии

И вновь я направил вывод в область проверки для тестирования решения Items_AddItem; оно функционировало исправно. Например, на экране 11 показано сообщение, адресованное только узлу, для которого проводится проверка. Сообщение на экране 12 адресовано лицу, которое не является членом домена RobGravelleAndCo.com. Как ожидалось, вывод поступал только от почтовых элементов.

 

Пример события SentItems_ItemAdd
Экран 11. Пример события SentItems_ItemAdd

 

Проверка правила отправки для MailItems
Экран 12. Проверка правила отправки для MailItems

Добавление других типов элементов не составляет труда; просто измените инструкцию If в Select Case и укажите целевые типы в списке с разделителями запятыми, как показано в листинге 4.

Запуск макрокоманды Move Sent MailItems по требованию

Установленную макрокоманду Move Sent MailItems можно применить к ранее отправленным сообщениям. Для этого используйте диалоговое окно Macros (макросы), открываемое нажатием кнопки Macros на ленте. Единственная проблема в том, что открывается доступ только к общедоступным макрокомандам, к которым наша макрокоманда не относится. Даже если можно увидеть событие ItemAdd папки SentItems, обрабатывается только последнее отправленное сообщение. Поэтому необходимо добавить общедоступную подпрограмму для обработки каждого элемента в папке SentItems, как показано во фрагменте исходного текста с меткой C листинга 2. Затем можно открыть диалоговое окно Macros, выбрать новую общедоступную подпрограмму (если она еще не выбрана) и нажать кнопку Run («Выполнить»), чтобы запустить ее.

Простота и безопасность

Итак, в этой статье я рассказал о том, как использовать программный код VBA для расширения возможностей встроенных правил и параметров Outlook 2010. В частности, здесь показано два способа перемещения почтовых элементов в папку пользователя после отправки. Описанный метод гораздо безопаснее и проще многих решений, в которых применяются сложные процессы с временной синхронизацией, подверженные ошибкам вызовы Windows API или сторонние DLL-библиотеки. На клиентской стороне решение не зависит от поставщика почтового сервера и не привязано к Exchange Server. Более того, оно применимо для компании с любым количеством пользователей — от 50 до 5000.

Остается лишь найти оптимальный способ распространения кода VBA. Сделать это можно по-разному, причем в одних случаях требуется содействие пользователей, а в других можно действовать дистанционно:

* использовать команду File | Export (Файл | Экспорт) в VBA-среде Outlook для экспорта модулей как файлов. bas,. cls или. frm;

* копировать файл VbaProject.otm с компьютера, на котором были подготовлены макрокоманды, на компьютеры других пользователей, заменив существующий файл VbaProject.otm;

* использовать мастер Office Profile Wizard (Proflwiz.exe) для распространения проекта VBA.

Дополнительные сведения об этих методах можно найти в статье "To Distribute Microsoft Outlook VBA Code to Other Users» (http://www.outlookcode.com/article.aspx?id=28).

Дополнительные материалы

«Microsoft Outlook Programming: Jumpstart for Administrators, Developers, and Power Users» Сью Мошер

Inspectors.NewInspector Event

MailItem.Send Event

Листинг 1. Код ThisOutlookSession для решения oMsg_Send

Option Explicit
Public WithEvents oInspectors As Outlook.Inspectors
Public WithEvents oMsg As Outlook.MailItem
Private Const BUSINESS_FOLDER = «FOSS Export (CR-035)»
Private Sub Application_Startup()
Set oInspectors = Application.Inspectors
End Sub
# Начало фрагмента A
Private Sub oInspectors_NewInspector(ByVal Inspector As Inspector)
If Inspector.CurrentItem.Class = olMail Then
If Len(Inspector.CurrentItem.EntryID) = 0 Then
Set oMsg = Inspector.CurrentItem
End If
End If
End Sub
# Конец фрагмента A
Private Sub oMsg_Send(Cancel As Boolean)
Dim oRecipient As Recipient, oBusinessFolder As MAPIFolder, oEmailCopy As MailItem
For Each oRecipient In oMsg.Recipients
# Начало фрагмента B
If InStr(1, oRecipient.Address, «RobGravelleAndCo.com») Then
# Конец фрагмента B
oMsg.DeleteAfterSubmit = True
Set oBusinessFolder = Application.Session.GetDefaultFolder(olFolderInbox).Parent.Folders(BUSINESS_FOLDER)
Set oEmailCopy = oMsg.Copy
oEmailCopy.Move oBusinessFolder
Exit For
End If
Next
End Sub

Листинг 2. Код ThisOutlookSession для решения Items_AddItem

Option Explicit
Public WithEvents oSentItems As Items
Private oBusinessFolder As MAPIFolder
# Начало фрагмента A
Private Const BUSINESS_FOLDER = «FOSS Export (CR-035)»
# Конец фрагмента A
Private Const PARTNER_EMAIL_ADDRESS = «RobGravelleAndCo.com»
# Начало фрагмента B
Private Sub Application_Startup()
Dim oSentItemsFolder As MAPIFolder
Set oSentItemsFolder = Application.Session.GetDefaultFolder(olFolderSentMail)
Set oSentItems = oSentItemsFolder.Items
Set oBusinessFolder = oSentItemsFolder.Parent.Folders(BUSINESS_FOLDER)
End Sub
# Конец фрагмента B
Private Sub oSentItems_ItemAdd(ByVal Item As Object)
Dim oRecipient As Recipient, oMailItem As MailItem
If Item.Class = olMail Then
Set oMailItem = Item 'this will enable auto-complete for mailitems.
For Each oRecipient In oMailItem.Recipients
If InStr(1, oRecipient.Address, PARTNER_EMAIL_ADDRESS) Then
oMailItem.Move oBusinessFolder
Exit For
End If
Next
End If
End Sub
# Начало фрагмента C
Public Sub runMoveSentItemsMacro()
Dim item As Object
For Each item In Application.Session.GetDefaultFolder(olFolderSentMail).Items
Call oSentItems_ItemAdd(item)
Next
End Sub
# Конец фрагмента C

Листинг 3. Программный код для обработки нескольких почтовых ящиков

Public WithEvents oAFSSentItems As Items
Private oAFSBusinessFolder As MAPIFolder
Public WithEvents oSTSSentItems As Items
Private oSTSBusinessFolder As MAPIFolder
Private Sub oAFSSentItems_ItemAdd(ByVal Item As Object)
Private Sub oSTSSentItems_ItemAdd(ByVal Item As Object)

Листинг 4: Программный код для добавления типов элементов

Private Sub oSentItems_ItemAdd(ByVal item As Object)
Dim oRecipient As Recipient
Select Case item.Class
Case olMail, olMeetingRequest
For Each oRecipient In item.Recipients
If InStr(1, oRecipient.Address, PARTNER_EMAIL_ADDRESS) Then
item.Move oBusinessFolder
Exit For
End If
Next
End Select
End Sub