Роб Грейвилл – автор сайта GravelleConsulting.com, создает системы для коммерческих и правительственных организаций Канады
* Два способа переместить почтовый элемент в пользовательскую папку после отправки.
* Решение на клиентской стороне, не зависящее от поставщика почтового сервера.
* Лучшие способы распространения кода VBA среди пользователей.
По умолчанию Microsoft Outlook сохраняет копии отправленных элементов в папке Sent Items («Отправленные»). Но типовой подход не всегда приемлем в компании, сотрудники которой ежедневно отправляют сотни почтовых сообщений. Часто сообщения электронной почты упорядочивают по темам или направлениям бизнеса. Этот метод отличается от сортировки по беседам, так как каждое направление бизнеса может содержать несколько потоков бесед. Распределять по папкам полученные сообщения достаточно просто; другое дело — отправленные сообщения. Основная трудность заключается в том, что несмотря на возможность переместить копию почтового элемента после отправки, в Outlook нет правила или параметра для перемещения самого отправленного элемента.
.
Ограничения подхода на основе правил
Как правило, если поведение программы не соответствует моим ожиданиям, я стараюсь найти программное решение. Но прежде чем вплотную заняться кодом VBA, рассмотрим параметры и правила электронной почты Outlook. Только исчерпав их возможности, следует начинать искать более сложные решения. Посмотрим, что можно сделать с помощью мастера Outlook Rules Wizard. Начиная с пустого правила, можно указать, следует ли применить правило к входящим или исходящим сообщениям, как показано на экране 1. После того, как выбраны критерии отбора сообщений, можно указать папку для копирования этих сообщений (экран 2).
Экран 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.
Экран 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.
Экран 5. Вкладка «Разработчик» с кнопкой Visual Basic |
Экран 6. Visual Basic Editor с видимой областью проверки |
Событие MailItem Send()
Чтобы события объекта были доступны в раскрывающемся списке Declarations («Объявления») в Visual Basic Editor (экран 7), нужно воспользоваться ключевым словом WithEvents для объявления объекта.
Экран 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.
Экран 8. Пример вывода события oMsg_Send Event |
Экран 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. Как ожидалось, вывод поступал только от почтовых элементов.
Экран 11. Пример события SentItems_ItemAdd |
Экран 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
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
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
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)
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