Outlook как сервер микросервисов
Доброго времени суток.
Небольшая вводная. Я для друзей, по их запросам, выгружаю данные из MS SQL Server’а. Друзья дают исходные данные, для которых надо сделать выгрузку в файлах .csv. Исходных данных (ИД) может быть от 1 до … строк. Я загружал данные в sql таблицу с помощью задачи или Task в английской версии в SQL Server Management Studio (SSMS). Исторически сложилось, что все sql файлы со скриптами хранятся на моем ПК. Я файлы открывал в SSMS и запускал на выполнение. Результаты записывал в файл и отправлял сообщение, что обработка выполнена. Друзья забирали файлы с результатами.
Но в один творческий день пришла идея автоматизировать этот процесс, чтобы Друзья все делали сами, с минимальным моим участием.
Была написана программа на C# + VBA для Outlook’а, которые все это делают. Ниже вкратце их опишу. Программа написана как консольное приложение, никакой диалог с пользователем не предполагается.
Ниже — описание основных моментов программы.
Действующие лица
Друзья:
Иванов Иван Иванович, его email iii@corporation.ru
Петров Петр Петрович, его email ppp@corporation.ru
Программа:
SuperPuperProgram spp.exe
Я:
Васильев Василий Васильевич
1. Так как выборку надо делать для нескольких друзей (теоретически, фактически запускает 1), необходимо определить, для кого запущена программа. Почему друг передается как аргумент в командной строке — будет понятно чуть ниже.
switch (args[0])
{
case "Иванов":
strEmail = iii@corporation.ru;
break; case "Петров":
strEmail = ppp@corporation.ru;
break;
}
2. Так как для ввода ИД используется одна таблица, необходимо исключить запуск нескольких экземпляров программы. (Можно для каждой обработки создавать уникальную sql таблицу для ввода ИД, но учитывая, что эта выгрузка нужна несколько раз может в день, может в неделю и занимается этим 1 человек, я решил использовать Mutex (как прежде неизведанное).
Объявляем переменную isNew и по значению, возвращенному из функции Mutex определяем запущен ли уже экземпляр программы. Если экземпляр уже запущен, освобождаем ресурсы Mutex’а, формируем строку ответа и отправляем email для кого была запущена программа, выходим из программы.
bool isNew;
var mutex = new System.Threading.Mutex(true, "SuperPuperProgram", out isNew);
if (isNew)
{
InstanceCheckMutex = mutex;
}
else
{
mutex.Dispose();
strTextOut += "Программа уже запущена !!!
Одновременный запуск нескольких экземпляров программы невозможен !!!
Запуск отменен
";
sendEMailThroughOUTLOOK(ref strTextOut, strEmail);
return;
}
В папке с программой есть 2 папки, назовем их так: папка исходных данных (ПИД) и папка результатов (ПР). В папке ПИД находятся csv файлы с ИД, в папку ПР будут записаны файлы с результатами.
Получаем список csv файлов из ПИД и для каждого файла из списка
foreach (string s1 in csv_In)
{
using (System.IO.FileStream fs = System.IO.File.OpenRead(s1))
{
Определяем кодировку файла. Для определения кодировки файла я использовал Ude.NET.
Encoding e1 = DetectFileEncoding(s1);
Импорт данных нам поможет сделать bcp.exe. Методом научного тыка найден рабочий вариант запуска процесса выполнения bcp.exe.
System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo();
processStartInfo.FileName = @"C:\Windows\system32\cmd.exe";
if (e1.EncodingName.Contains("UTF"))
{
processStartInfo.Arguments = "/c bcp.exe " + "[sqlTable] in \"" + s1 + "\" -S sqlServer -T -c -C " + e1.CodePage + " /t";
}
else
{
processStartInfo.Arguments = "/c bcp.exe " + "[sqlTable] in \"" + s1 + "\" -S sqlServer -T -c -C " + e1.WindowsCodePage + " /t";
}
processStartInfo.CreateNoWindow = true;
processStartInfo.UseShellExecute = false;
processStartInfo.RedirectStandardOutput = true;
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo = processStartInfo;
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
ИД в sqlTable внесены.
Теперь подключаемся к sqlServer’у и выполняем sql скрипт.
strSQLfile — файл со sql скриптом.
const string strSQLfile = "path\\file.sql";
using (System.IO.StreamReader fr = System.IO.File.OpenText(strSQLfile))
{
В переменную queryString, заносим данные из strSQLfile. queryString — это строка, содержащая текст запроса. Все комментарии, начинающиеся с »--» удаляем до конца строки. Если этого не делать, они закомментируют всю остальную часть строки queryString).
Комментарии /*…*/ оставляем, они комментируют часть кода и на остальную часть строки queryString не влияют, на выполнении не сказываются. (Я практически не использую комментарии такого плана, поэтому размер queryString сильно не увеличится.)
string str1 = "";
while ((str1 = fr.ReadLine()) != null)
{
str1 = str1.Replace("\t", " ");
if (str1.Length > 2)
{
//не берем закомментированные строки целиком
if (str1[0] != '-' && str1[1] != '-')
{
if (str1.Contains("--")) //убираем комментарии в строке до конца строки, внутренние комментарии /*...*/ остаются
{
str1 = str1.Substring(0, str1.IndexOf("--"));
}
queryString += str1 + " ";
}
}
else
{
if (str1.Contains("--")) //убираем комментарии в строке до конца строки, внутренние комментарии /*...*/ остаются
{
str1 = str1.Substring(0, str1.IndexOf("--"));
}
queryString += str1 + " ";
}
}
Теперь у нас в queryString полноценный текст запроса, который можно выполнить.
Создаем подключение к sqlServer’у. Подключение у нас Trusted_Connection=Yes, другие типы авторизации сервер отвергает. Вариант подключения к серверу по логину и паролю отпадает. Друзья не прописаны на сервере и вариант с регистрацией их на сервере даже не рассматривается. Если Друзья будут запускать программу от себя, то ничего и не будет. Запуск программы от имени другого пользователя тоже не лучший вариант. Поэтому — единственный вариант — запуск от моего имени на моем ПК.
Ниже, на мой взгляд ничего интересного: создаем подключение, задаем параметры, устанавливаем Connection Timeout=3000, так как неизвестно время выполнения. Как я говорил, на входе может быть и 10 строк, и 99999999.
using (var connection = new QC.SqlConnection("Data Source = sqlServer;" + "Initial Catalog=sqlDB;" + "Trusted_Connection=Yes;" + "Connection Timeout=3000;"))
{
QC.SqlCommand command = new QC.SqlCommand(queryString, connection);
Определяем, куда будем писать результат и кодировку файла.
System.IO.StreamWriter sw = new System.IO.StreamWriter(fileOut.csv, false, Encoding.GetEncoding(1251));
Открываем подключение.
connection.Open();
if (connection.State == DT.ConnectionState.Open)
{
Если подключение открыто успешно, выполняем queryString.
QC.SqlDataReader reader = command.ExecuteReader();
Пишем в вывод шапку.
sw.WriteLine("...");
while (reader.Read())
{
Пишем в вывод данные.
sw.WriteLine("{0};...", reader[0],...);
iRet += 1;
}
Все закрываем.
reader.Close();
}
sw.Close();
connection.Close();
Берем следующий файл в работу (переходим на foreach (string s1 in csv_In)
).
После того, как все файлы обработаны, отправляем письмо с результатами. Сами файлы с результатами в письмо не вкладываю, так как их размер может превысить лимит на вложения.
sendEMailThroughOUTLOOK(ref strTextOut, strEmail);
Программа отлажена, работает, скрипты выполняются, письма отправляются. Осталось сделать так, чтобы программу на моем компе запускали Друзья. Появилась творческая мысль — использовать для этого Outlook. ВАЖНОЕ — у Outlook’а в «Параметрах макросов» должно быть включено «Включить все макросы». Схема виделась такой — коллега шлет мне письмо с кодовым словом в теме письма, Outlook анализирует отправителя, тему письма и при совпадении условий запускает программу. Осталось только реализовать задуманное.
Пусть кодовое слово будет SuperPuperProgram.
В Outlook’е, в ThisOutlookSession вставляем
Private WithEvents myOlItems As Outlook.Items
Private Sub Application_Startup()
Dim olApp As Outlook.Application
Dim objNS As Outlook.NameSpace
Set olApp = Outlook.Application
Set objNS = olApp.GetNamespace("MAPI")
Set myOlItems = objNS.GetDefaultFolder(olFolderInbox).Items
End Sub
Private Sub myOlItems_ItemAdd(ByVal Item As Object)
On Error GoTo ErrorHandler
Dim Msg As Outlook.MailItem
Dim Ret_Val
If TypeName(Item) = "MailItem" Then
Set Msg = Item
Проверяем тему письма
If Msg.Subject = "SuperPuperProgram" Then
Проверяем получателя
If Msg.To = "Васильев Василий Васильевич" Then
Если отправитель прошел проверку, запускаем программу, а Msg.Sender.Name передаем как параметр. По этому параметру будет определяться email, кому отправлять письма и кто запустил программу.
If Msg.Sender.Name = "Иванов Иван Иванович" Then
Ret_Val = Shell("spp.exe " + Msg.Sender.Name)
End If
If Msg.Sender.Name = "Петров Петр Петрович" Then
Ret_Val = Shell("spp.exe " + Msg.Sender.Name)
End If
End If
End If
ProgramExit:
Exit Sub
ErrorHandler:
MsgBox Err.Number & " - " & Err.Description
Resume ProgramExit
End Sub
Получается, что Outlook можно использовать как сервер микро сервисов. Запросы — это письма Друзей, результаты работы — в папке результатов (ПР), взаимодействие (информирование) Друзей с помощью email’ов.
Думаю, что если подойти творчески к идее Outlook’а как сервера микро сервисов, то можно и не такое нагородить!!!
Как вариант, другая программа у меня парсит данные и запускается по письму (запросу).
Спасибо за внимание, буду рад, если мой труд был не напрасен и кому-то окажется полезным.