none
PS обработка писем RRS feed

  • Вопрос

  • Доброго дня!

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

    Можно ли с помощью PS открыть п.я., взять оттуда письмо и работать с ним?

    Мне кажется. тут без РОР3 не обойтись, т.к. export-mailbox не поможет, либо потом pst обработать чем-то.
    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    15 июля 2009 г. 8:37

Ответы

  • Я бы предпочел при реализации такой задачи не полагаться на Outlook, а сделать обработку напрямую через POP3 (благо протокол очень легкий) или IMAP.
    В качестве отправной точки можно использовать RFC и эти примеры:
    http://xaegr.wordpress.com/2007/11/29/clearmailboxps1/
    http://xaegr.wordpress.com/2007/11/29/clearmailboximapps1/

    Кроме того можно использовать готовое решение (к сожалению платное) - NetCmdlets. Там присутствуют такие командлеты как Get-Imap, Set-Imap и Get-Pop.

    >Но $msg.Attachments.FileName должно выводить имя файла вложения, но не выводит, странно.
    Вы считаете что на все Attachments всего лишь одно имя файла? :) Это скорее всего массив, попробуйте так:
    $msg.attachments | foreach {$_.filename}


    AKA Xaegr, MCSE: Security, Messaging; MCITP: Server\Enterprise Administrator; Блог: http://xaegr.wordpress.com
    • Предложено в качестве ответа Vasily GusevModerator 15 июля 2009 г. 10:46
    • Помечено в качестве ответа Sergey Babkov 20 июля 2009 г. 9:06
    15 июля 2009 г. 10:46
    Модератор
  • Что удалось сделать:

    По вышеуказанным ссылкам попробовал сделать обработку вывода РОР3 ч/з telnet, получилось, работает.
    Даже удалось сделать декодирование base64 в файл, вообщем убил 1.5 дня и все зря, т.к. пока обрабатывается 2мб файл (как текст) можно спокойно попить чаю.
    Если кому интересно, код:

    Скрипт черновой, т.ч. не все гладно, но работает:определяет поля от кого, тема письма, сохраняет и декодирует вложения.

    server = "192.168.0.1"
    $port=110
    [string]$username  = "test"
    [string]$password  = "789123"
    [int]$FileFlag  = 0
    [string]$FileName = ""
    [string]$ALLFiles = ""
    [string]$FileBody = ""


    function Start-Proc  {
         param (
                 [string]$exe = $(Throw "An executable must be specified"),
                 [string]$arguments
                
                 )   
        
         # Build Startinfo and set options according to parameters
        $startinfo = new-object System.Diagnostics.ProcessStartInfo
        $startinfo.FileName = $exe
        $startinfo.Arguments = $arguments

         if ($hidden){
                     $startinfo.WindowStyle = "Hidden"
                     $startinfo.CreateNoWindow = $TRUE
                     }

         $process = [System.Diagnostics.Process]::Start($startinfo)
         $process.WaitForExit()
        
    }

    function write-stream([System.Net.Sockets.NetworkStream]$n, [String]$s)
    {
        "> $s"
        $arr = "$s`r`n".ToCharArray()
        $n.Write($arr, 0, $arr.Length)
    }

    function FindFrom($msgstr)
    {
     if ($msgstr.contains("@"))
     {
     
     0..$msgstr.length | foreach  {
     
      if($msgstr[$_] -eq "<")
      { $curpos =$_;
       while ($msgstr[$curpos+1] -ne ">") {$script:EmailFrom = $EmailFrom + $msgstr[$curpos+1]; ++$curpos}
       }
      } 
     }
    }


    function FindSubject($msgstr)
    {
     if ($msgstr.contains("Subject: ")) {$script:Subject = $msgstr.Replace("Subject: ","")}
    }

    function FindAttachment($msgstr)

     
     if ($FileFlag -eq 2)#start decoding file
     { 
      $script:ALLFiles = $ALLFiles + $FileName + "#"
      $FileBody >> d:\pop3\pspop3\file.tmp
      
      Start-Proc d:\pop3\pspop3\base64.cmd $FileName

    #Содержание файла base64.cmd
    # type d:\pop3\pspop3\file.tmp >> d:\pop3\pspop3\file_dos.tmp
    # d:\pop3\pspop3\base64.exe -d d:\pop3\pspop3\file_dos.tmp d:\pop3\pspop3\%1
    # # base64.exe - командная утилита декодирования base64
    ## фокус в том, что работает она лишь в dos866 а win1251 НЕ понимает! поэтому type d:\pop3\pspop3\file.tmp >> ## d:\pop3\pspop3\file_dos.tmp 

      remove-item d:\pop3\pspop3\file.tmp
      remove-item d:\pop3\pspop3\file_dos.tmp
      $script:FileFlag = 0
      $script:FileBody = ""
      

     }
     
     if ($msgstr.contains("Content-Disposition: attachment;"))
     {
      $Attachment = $msgstr.split('"')
      $script:FileName = $Attachment[1]
     }

     if ($msgstr.contains("--_"))
     {
      $script:FileFlag = $FileFlag + 1 #End of file
     }

     if($FileFlag -eq 1){

     $script:FileBody = $FileBody + "`n" + $msgstr
      
     }

     if ($msgstr.contains("Content-Transfer-Encoding: base64"))
     {
      $script:FileFlag = $FileFlag + 1 #Begin File Base64 code, 2 - End of file
     }

    }

    $Client = New-Object System.Net.Sockets.TcpClient

    $Client.Connect($server,$port)

    $Stream = $Client.GetStream()

    $Reader = New-Object System.IO.StreamReader($Client.GetStream())

    $r = $Reader.ReadLine()

    if ($r -notlike "+OK*"){throw "Unable to connect: $r"}else{$r}

    write-stream $Stream "user $username"

    $Reader.ReadLine()

    write-stream $Stream "pass $password"

    $r = $Reader.ReadLine()

    if ($r -notlike "+OK*"){throw "Unable to login: $r"}else{$r}

    write-stream $Stream "stat"

    $r = $Reader.ReadLine()

    if ($r -match "^\+OK (\d+) (\d+)")
    {
        $r
      
     $msgs,$bytes = $matches[1],$matches[2]
        "$bytes bytes in $msgs messages"
       
     1..$msgs | foreach  {
     
     write-stream $Stream "RETR $_"
     
     [int]$exitflag = 0
     
     #Searc code: 1 - From, 2 - To, 3 - Subject, 4 - attachment and e.t.c.
     [int]$scode = 0
     [string]$EmailFrom  = ""
     [string]$Subject  = ""
     $ALLFiles   = ""

     while ($exitflag -eq 0)
     {
      $msgstr = $Reader.ReadLine(); #здесь находится считанная строка, в конце сообщения идет "."
      $msgstr = $msgstr.Trim(" ")
      
     #What we are looking for   
     if ($msgstr.contains("From:"))   {$scode = 1} 
     if ($msgstr.contains("To:"))    {$scode = 2} #stop searching "From" e-mail
     if ($msgstr.contains("Subject:"))   {$scode = 3}  
     if ($msgstr.contains("Message-ID:"))  {$scode = 4} #Stop Searching Subject
     if ($msgstr.contains("Content-Disposition: attachment;"))  {$scode = 5}
     
     
     switch ($scode){

     1 {FindFrom($msgstr)}
     3 {FindSubject($msgstr)}
     5 {FindAttachment($msgstr)}

     default {}
     
     }
       if ($msgstr -eq ".") {$exitflag = 1}
     }

    $EmailFrom 
    $Subject  

     }    write-stream $Stream "stat"
        $r = $Reader.ReadLine()
        if ($r -eq "+OK 0 0") {"All messages removed"}
        write-stream $Stream "quit"
     
    }
    else {throw "$r"}


    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    20 июля 2009 г. 8:37
  • Весь этот ужас выше, писал 1.5 дня, в итоге пришел к другому решению:

    Для того, чтобы скрипт работал, нужно зайти пользователям на станцию, где будет крутиться скрипт, и настроить outlook.
    Как я понимаю, берется текущий профиль пользователя, из-под которого запускается скрипт, хотя, я могу и ошибаться (возможно, берется текущий дефолтный профиль outlook)

    $olFolderInbox = 6
    $outlook = new-object -com outlook.application
    $ns = $outlook.GetNameSpace("MAPI")
    $inbox = $ns.GetDefaultFolder($olFolderInbox)

    foreach ($msg in $inbox.items)
    {

    $msg.attachments | foreach {

    $Filename = $_.filename
    $Filename = "d:\pop3\mapi\" + $Filename
    #$_.SaveAsFile($Filename)

    }#Attachments

    }

    Ч/з шедулер скрипт отрабатывает, т.е. терминальная/консольная сессия не нужна. 


    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    20 июля 2009 г. 8:59

Все ответы

  • Как вариант, можно попробовать так:

    $olFolderInbox = 6
    $outlook = new-object -com outlook.application
    $ns = $outlook.GetNameSpace("MAPI")
    $inbox = $ns.GetDefaultFolder($olFolderInbox)


    foreach ($msg in $inbox.items)
    {

    $msg.Attachments.FileName

    }

    Но $msg.Attachments.FileName должно выводить имя файла вложения, но не выводит, странно.


    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    15 июля 2009 г. 9:52
  • Я бы предпочел при реализации такой задачи не полагаться на Outlook, а сделать обработку напрямую через POP3 (благо протокол очень легкий) или IMAP.
    В качестве отправной точки можно использовать RFC и эти примеры:
    http://xaegr.wordpress.com/2007/11/29/clearmailboxps1/
    http://xaegr.wordpress.com/2007/11/29/clearmailboximapps1/

    Кроме того можно использовать готовое решение (к сожалению платное) - NetCmdlets. Там присутствуют такие командлеты как Get-Imap, Set-Imap и Get-Pop.

    >Но $msg.Attachments.FileName должно выводить имя файла вложения, но не выводит, странно.
    Вы считаете что на все Attachments всего лишь одно имя файла? :) Это скорее всего массив, попробуйте так:
    $msg.attachments | foreach {$_.filename}


    AKA Xaegr, MCSE: Security, Messaging; MCITP: Server\Enterprise Administrator; Блог: http://xaegr.wordpress.com
    • Предложено в качестве ответа Vasily GusevModerator 15 июля 2009 г. 10:46
    • Помечено в качестве ответа Sergey Babkov 20 июля 2009 г. 9:06
    15 июля 2009 г. 10:46
    Модератор
  • Спасибо за ответ.

    Похоже мой вариант в любом случае не подойдет, т.к. идет работа с текущим п.я., а если это будет скрипт, то берут сомнения, что заработает.
    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    16 июля 2009 г. 6:09
  • Что удалось сделать:

    По вышеуказанным ссылкам попробовал сделать обработку вывода РОР3 ч/з telnet, получилось, работает.
    Даже удалось сделать декодирование base64 в файл, вообщем убил 1.5 дня и все зря, т.к. пока обрабатывается 2мб файл (как текст) можно спокойно попить чаю.
    Если кому интересно, код:

    Скрипт черновой, т.ч. не все гладно, но работает:определяет поля от кого, тема письма, сохраняет и декодирует вложения.

    server = "192.168.0.1"
    $port=110
    [string]$username  = "test"
    [string]$password  = "789123"
    [int]$FileFlag  = 0
    [string]$FileName = ""
    [string]$ALLFiles = ""
    [string]$FileBody = ""


    function Start-Proc  {
         param (
                 [string]$exe = $(Throw "An executable must be specified"),
                 [string]$arguments
                
                 )   
        
         # Build Startinfo and set options according to parameters
        $startinfo = new-object System.Diagnostics.ProcessStartInfo
        $startinfo.FileName = $exe
        $startinfo.Arguments = $arguments

         if ($hidden){
                     $startinfo.WindowStyle = "Hidden"
                     $startinfo.CreateNoWindow = $TRUE
                     }

         $process = [System.Diagnostics.Process]::Start($startinfo)
         $process.WaitForExit()
        
    }

    function write-stream([System.Net.Sockets.NetworkStream]$n, [String]$s)
    {
        "> $s"
        $arr = "$s`r`n".ToCharArray()
        $n.Write($arr, 0, $arr.Length)
    }

    function FindFrom($msgstr)
    {
     if ($msgstr.contains("@"))
     {
     
     0..$msgstr.length | foreach  {
     
      if($msgstr[$_] -eq "<")
      { $curpos =$_;
       while ($msgstr[$curpos+1] -ne ">") {$script:EmailFrom = $EmailFrom + $msgstr[$curpos+1]; ++$curpos}
       }
      } 
     }
    }


    function FindSubject($msgstr)
    {
     if ($msgstr.contains("Subject: ")) {$script:Subject = $msgstr.Replace("Subject: ","")}
    }

    function FindAttachment($msgstr)

     
     if ($FileFlag -eq 2)#start decoding file
     { 
      $script:ALLFiles = $ALLFiles + $FileName + "#"
      $FileBody >> d:\pop3\pspop3\file.tmp
      
      Start-Proc d:\pop3\pspop3\base64.cmd $FileName

    #Содержание файла base64.cmd
    # type d:\pop3\pspop3\file.tmp >> d:\pop3\pspop3\file_dos.tmp
    # d:\pop3\pspop3\base64.exe -d d:\pop3\pspop3\file_dos.tmp d:\pop3\pspop3\%1
    # # base64.exe - командная утилита декодирования base64
    ## фокус в том, что работает она лишь в dos866 а win1251 НЕ понимает! поэтому type d:\pop3\pspop3\file.tmp >> ## d:\pop3\pspop3\file_dos.tmp 

      remove-item d:\pop3\pspop3\file.tmp
      remove-item d:\pop3\pspop3\file_dos.tmp
      $script:FileFlag = 0
      $script:FileBody = ""
      

     }
     
     if ($msgstr.contains("Content-Disposition: attachment;"))
     {
      $Attachment = $msgstr.split('"')
      $script:FileName = $Attachment[1]
     }

     if ($msgstr.contains("--_"))
     {
      $script:FileFlag = $FileFlag + 1 #End of file
     }

     if($FileFlag -eq 1){

     $script:FileBody = $FileBody + "`n" + $msgstr
      
     }

     if ($msgstr.contains("Content-Transfer-Encoding: base64"))
     {
      $script:FileFlag = $FileFlag + 1 #Begin File Base64 code, 2 - End of file
     }

    }

    $Client = New-Object System.Net.Sockets.TcpClient

    $Client.Connect($server,$port)

    $Stream = $Client.GetStream()

    $Reader = New-Object System.IO.StreamReader($Client.GetStream())

    $r = $Reader.ReadLine()

    if ($r -notlike "+OK*"){throw "Unable to connect: $r"}else{$r}

    write-stream $Stream "user $username"

    $Reader.ReadLine()

    write-stream $Stream "pass $password"

    $r = $Reader.ReadLine()

    if ($r -notlike "+OK*"){throw "Unable to login: $r"}else{$r}

    write-stream $Stream "stat"

    $r = $Reader.ReadLine()

    if ($r -match "^\+OK (\d+) (\d+)")
    {
        $r
      
     $msgs,$bytes = $matches[1],$matches[2]
        "$bytes bytes in $msgs messages"
       
     1..$msgs | foreach  {
     
     write-stream $Stream "RETR $_"
     
     [int]$exitflag = 0
     
     #Searc code: 1 - From, 2 - To, 3 - Subject, 4 - attachment and e.t.c.
     [int]$scode = 0
     [string]$EmailFrom  = ""
     [string]$Subject  = ""
     $ALLFiles   = ""

     while ($exitflag -eq 0)
     {
      $msgstr = $Reader.ReadLine(); #здесь находится считанная строка, в конце сообщения идет "."
      $msgstr = $msgstr.Trim(" ")
      
     #What we are looking for   
     if ($msgstr.contains("From:"))   {$scode = 1} 
     if ($msgstr.contains("To:"))    {$scode = 2} #stop searching "From" e-mail
     if ($msgstr.contains("Subject:"))   {$scode = 3}  
     if ($msgstr.contains("Message-ID:"))  {$scode = 4} #Stop Searching Subject
     if ($msgstr.contains("Content-Disposition: attachment;"))  {$scode = 5}
     
     
     switch ($scode){

     1 {FindFrom($msgstr)}
     3 {FindSubject($msgstr)}
     5 {FindAttachment($msgstr)}

     default {}
     
     }
       if ($msgstr -eq ".") {$exitflag = 1}
     }

    $EmailFrom 
    $Subject  

     }    write-stream $Stream "stat"
        $r = $Reader.ReadLine()
        if ($r -eq "+OK 0 0") {"All messages removed"}
        write-stream $Stream "quit"
     
    }
    else {throw "$r"}


    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    20 июля 2009 г. 8:37
  • Весь этот ужас выше, писал 1.5 дня, в итоге пришел к другому решению:

    Для того, чтобы скрипт работал, нужно зайти пользователям на станцию, где будет крутиться скрипт, и настроить outlook.
    Как я понимаю, берется текущий профиль пользователя, из-под которого запускается скрипт, хотя, я могу и ошибаться (возможно, берется текущий дефолтный профиль outlook)

    $olFolderInbox = 6
    $outlook = new-object -com outlook.application
    $ns = $outlook.GetNameSpace("MAPI")
    $inbox = $ns.GetDefaultFolder($olFolderInbox)

    foreach ($msg in $inbox.items)
    {

    $msg.attachments | foreach {

    $Filename = $_.filename
    $Filename = "d:\pop3\mapi\" + $Filename
    #$_.SaveAsFile($Filename)

    }#Attachments

    }

    Ч/з шедулер скрипт отрабатывает, т.е. терминальная/консольная сессия не нужна. 


    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    20 июля 2009 г. 8:59
  • >По вышеуказанным ссылкам попробовал сделать обработку вывода РОР3 ч/з telnet
    Ну непричем тут телнет :) Это совсем другой протокол :)


    >Даже удалось сделать декодирование base64 в файл
    Лучше для этого использовать не внешнюю утилиту а функцию .Net:
    [System.Convert]::FromBase64String($string)


    $Attachment = $msgstr.split('"')
    $script:FileName = $Attachment[1]
    Если таким кодом обрабатывать двух мегабайтную строку, то у вас памяти будут 3 переменные, минимум по 2 мб каждая. Скрипт просмотрел невнимательно, но думаю что там есть еще несколько подобных мест для улучшения. По возможности надо в первую очередь отрезать вложение от сообщения, обработать его, скинуть на диск, и обнулить переменные. Регулярные выражения на больших строках тоже наверняка вызывают немало тормозов. Вобщем смотреть разные методы и проверять с Measure-Expression ;)

    Удачи :)


    AKA Xaegr, MCSE: Security, Messaging; MCITP: Server\Enterprise Administrator; Блог: http://xaegr.wordpress.com
    20 июля 2009 г. 9:02
    Модератор
  • Отлично, чем проще тем лучше. Я опасался что так могут возникнуть проблемы из за нелюбви оутлука к автоматизаторам и скриптам ;) Кстати какая версия офиса? Кроме того буду благодарен если подробно опишите настройки задачи в шедулере которая запускает сценарий. Вполне может самому пригодиться :)


    AKA Xaegr, MCSE: Security, Messaging; MCITP: Server\Enterprise Administrator; Блог: http://xaegr.wordpress.com
    20 июля 2009 г. 9:06
    Модератор
  • Отлично, чем проще тем лучше. Я опасался что так могут возникнуть проблемы из за нелюбви оутлука к автоматизаторам и скриптам ;) Кстати какая версия офиса? Кроме того буду благодарен если подробно опишите настройки задачи в шедулере которая запускает сценарий. Вполне может самому пригодиться :)


    AKA Xaegr, MCSE: Security, Messaging; MCITP: Server\Enterprise Administrator; Блог: http://xaegr.wordpress.com

    Офис 2007, на 2003 не тестировал, но можно попробовать (наверняка будет работать, т.к. пример брал с 2003).
    Настройки самые обычные, только сперва зашел пользователем на сервер и настроил outlook под ним.
    Пробовал запускать скрипт без профиля outlook - ругается.

    Конфигурация:
    Что запускать:C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -command  D:\pop3\MAPI\mapitest.ps1
    Рабочая папка:D:\pop3\MAPI\

    Больше настроек не было.



    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    20 июля 2009 г. 11:07
  • Пользуясь случаем проверил на 2003 офисе, все работает, но outlook выдает предупреждение, что кто-то пытается к нему подключиться. Обойти это невозможно, есть правда сторонние утилиты, т.ч. проще 2007 офис использовать.


    MCSE, MCSA:Messaging (2000/2003) MCTS: Exchange 2007
    24 июля 2009 г. 6:49
  • Вот, вот как раз с этой фигнёй я и сталкивался в своё время :) Впрочем для меня достаточно того что в 2007 это исправлено.


    AKA Xaegr, MCSE: Security, Messaging; MCITP: Server\Enterprise Administrator; Блог: http://xaegr.wordpress.com
    24 июля 2009 г. 6:51
    Модератор