代码改变世界

微软BI 之SSIS 系列 - 使用 Script Task 访问非 Windows 验证下的 SMTP 服务器发送邮件

2014-09-29 10:20  BIWORK  阅读(4223)  评论(9编辑  收藏  举报

开篇介绍

大多数情况下我们的 SSIS 包都会配置在 SQL Agent Job 中周期性的按计划执行,比如每天晚上调用 SSIS 包刷新数据,处理 Cube 等。一旦 SSIS 包中出现任何异常,报错,那么配置在 SQL Agent Job 中的通知,邮件提醒就会把这些错误信息发邮件到指定的用户或者系统维护者,这样就起到了一个错误监控的作用。

但是在有的情况下,有一些自定义的 SSIS 调度框架的计划调度都不是通过 SQL Agent Job 配置来完成的。比如我以前在一个小项目中设计过一个 SSIS 调度框架,只有一个主包配置在 SQL Agent Job 中,所有子包的上线不由 SQL Agent Job 支配。每次新增的子包要上线,只需要将相应的信息以及调度计划注册到相应的表中即可,不需要打开 SQL Agent Job 调整各个子包的执行顺序等等。那么在这种情况下,就只能在 SSIS 内部使用发送邮件的功能来发送错误信息,因为所有的子包和 SQL Agent Job 都是没有直接关系的。

在 SSIS 中我们可以通过 Send Mail Task 或者 Script Task 来发送邮件,当然还有第三种方式就是写存储过程调用发送邮件存储过程.

Send Mail Task 发送邮件

Send Mail Task 的使用其实非常的简单,配置的步骤也并不多,但是在使用它的时候有几个限制:

  1. 只能发送普通的文本格式的邮件,也就是说不支持 HTML 格式的邮件。
  2. 连接 SMTP Server 时不支持用户填写用户名和密码,也就是说用户要么在域环境下 SMTP Server 对用户进行 Windows 方式验证,要么就是访问的 SMTP Server 支持匿名访问,不需要提供用户名和密码。
  3. SMTP Server 的端口号使用默认的,也就是说如果在域中的 SMTP Server 端口号不是默认的话 Send Mail Task 就无法使用了。 

这里是我常用的一个测试服务器地址 http://www.yopmail.com/en/ ,免费的并支持匿名访问,可以通过它来进行邮件发送的测试。关于 Send Mail Task 的使用方式就不在这里演示了,在非域和非 Windows 验证方式下用我提到的可匿名访问的 Yop Mail 一试便知,主要是讲 Script Task 发送邮件的方式。

Script Task 发送邮件

新建一个包并首先创建一个 SMTP 连接 - New Connection。

选择 SMTP 连接管理器。

以我的邮箱为例子 BIWORK@126.com 发送邮箱服务器是 smtp.126.com,这个可以在网易邮箱配置页面查到。但是非常遗憾!在这里仍然是没有地方可以配置用户名和密码的,默认的情况下仍然是只支持 Windows 验证或者匿名访问。

所以说,如果想要在 SMTP Connection 里自己动手配置用户名和密码是不可能的,那么像这种 163,126等等很多很多邮箱直接通过 SMTP Connection 是肯定无法完成发送邮件操作的。

看下图时一定要理解一点:几乎每一个 SSIS 控件的属性都是可配置的,也是可获取的,这是我们在 SSIS ETL 的学习过程中解决很多很多问题的最关键的地方!

新建一些常用的变量,发件人,收件人,抄送,邮件标题,内容,附件地址,还有用户名和密码等。

PS:关于这个密码有一个故事,以前在公司里有一个管机房管电脑的同事,他的网络下载速度最快也没人管,他的电脑里有各种游戏,高清蓝光和 1024。每次大家伙要点东西像挤牙膏似的,大家都在想办法搞他的密码,直到后来大家有意无意记了并试出了密码 - 大概就是 lowformat1MBD!! 类似于这样的!看了密码之后我们都一致认为这个密码确实很符合他的职业,不过貌似苦大仇深啊!

其中 PV_CONTENT 变量描述的是邮件发送的 HTML 格式的内容,注意是字符串类型的因此需要双引号。

"<h1>Hi BIWORK!</h1>
<p>
This is a test email from the test SSIS package of BIWORK!
</p>
<p>Server Name - "+ @[System::MachineName] +"</p>
<p>Package Name - "+ @[System::PackageName] +"</p>
<p>Package Start Time - "+ (DT_WSTR, 12) ( DT_DBDATE) @[System::StartTime] +"</p>
<p></br>Thanks and Regards!</p>
<p>Simon</p>"

系统变量的信息也通过表达式嵌入到邮件正文中。

编辑 Script Task 并引入这些变量,Script Task 中的代码 -

using System.Net.Mail; // BIWORK Added
using System.Text.RegularExpressions; // BIWORK Added

Main 中的代码包括处理优先级,多个收件人的处理,用户名和密码,添加附件的方式等等。

public void Main()
        {
            // Default Priority and SMTP Server Port
            int iPriority = 1;
            int smtpPort = 25;
             
            //User::PV_ATTACHED_FILE,User::PV_CONTENT,User::PV_MAIL_FROM,
            //User::PV_MAIL_TO,User::PV_MAIL_TO_CC,User::PV_TITLE
            String mailFrom = Dts.Variables["User::PV_MAIL_FROM"].Value.ToString();
            String mailTo = Dts.Variables["User::PV_MAIL_TO"].Value.ToString();
            String mailToCC = Dts.Variables["User::PV_MAIL_TO_CC"].Value.ToString();
            String mailTitle = Dts.Variables["User::PV_TITLE"].Value.ToString();
            String mailContent = Dts.Variables["User::PV_CONTENT"].Value.ToString();
            String mailAttachedFilePath = Dts.Variables["User::PV_ATTACHED_FILE"].Value.ToString();
            
            // User Information
            String loginUser = Dts.Variables["User::PV_LOGIN_USER"].Value.ToString();
            String loginPwd = Dts.Variables["User::PV_LOGIN_PWD"].Value.ToString();

            // Get SMTP Server Information from Connection Manager
            String smtpServer = Dts.Connections["CM_SMTP_126"].Properties["SmtpServer"].GetValue(Dts.Connections["CM_SMTP_126"]).ToString();

            try
            {
                SmtpClient smtpClient = new SmtpClient();
                MailMessage message = new MailMessage(); 
                MailAddress fromAddress = new MailAddress(mailFrom, "BIWORKTEST");

                string[] sEmailTo = Regex.Split(mailTo, ";");
                string[] sEmailToCC = Regex.Split(mailToCC, ";");

                smtpClient.Host = smtpServer;
                smtpClient.Port = smtpPort;

                // Login information
                System.Net.NetworkCredential myCredentials =
                   new System.Net.NetworkCredential(loginUser, loginPwd);
                smtpClient.Credentials = myCredentials;

                // Attachment 
                System.Net.Mail.Attachment attachment;
                attachment = new System.Net.Mail.Attachment(mailAttachedFilePath);
                message.Attachments.Add(attachment);
                 
                message.From = fromAddress;  

                // Multiple email to address
                if (sEmailTo != null)
                {
                    for (int i = 0; i < sEmailTo.Length; ++i)
                    {
                        if (sEmailTo[i] != null && sEmailTo[i] != "")
                        {
                            message.To.Add(sEmailTo[i]);
                        }
                    }
                }

                // Multiple cc address
                if (sEmailToCC != null)
                {
                    for (int i = 0; i < sEmailToCC.Length; ++i)
                    {
                        if (sEmailToCC[i] != null && sEmailToCC[i] != "")
                        {
                            message.To.Add(sEmailToCC[i]);
                        }
                    }
                }

                // Email priority
                switch (iPriority)
                {
                    case 1:
                        message.Priority = MailPriority.High;
                        break;
                    case 3:
                        message.Priority = MailPriority.Low;
                        break;
                    default:
                        message.Priority = MailPriority.Normal;
                        break;
                }

                message.Subject = mailTitle;
                message.IsBodyHtml = true;
                message.Body = mailContent;

                smtpClient.Send(message);
            }catch (Exception ex)
            {
                Dts.TaskResult = (int)ScriptResults.Failure;
            }
            // Close Script Task with success 
            Dts.TaskResult = (int)ScriptResults.Success; 
        }

保存并执行 SSIS 包,执行成功!

优先级别,标题,发送人等信息 -

附件信息和内容都可以看的到,包括在抄送栏中抄送给了自己,Package Start Time 的时间是来自虚机的时间,我虚机的时间还是 28日,这个是正确的 -

总结

从中可以看出,虽然在 SSIS 中提供了 SMTP 的连接方式,但是实际上它还是默认只允许通过 Windows 验证或者匿名访问的方式来访问。但是我们通过 Script Task 在 Script 中关联到了 SMTP 服务器,并同时写入用户名和密码,这样就实现了在 SSIS 中访问非域环境下第三方的 SMTP 服务器并通过身份验证且发送邮件的效果。

当然,这种方式在实际项目中可能用的并不是很多,因为项目中的邮件肯定是通过域验证的方式去访问更安全一些,毕竟系统信息的传递只能限制在域中。

在项目中使用邮件通知如是 Windows 验证的方式,有这几个点需要注意:

  1. 在开发与测试过程中,手动执行 SSIS Package 的这个用户要有域中发送邮件的权限,能够通过 Windows 验证。
  2. 当包部署到 SQL Agent Job 中后,执行 SQL Agent Job 的账号要有域发送邮件的权限,也必须能通过 Windows 验证。
  3. 如果不需要部署到 SQL Agent Job,那么就直接使用 Send Mail Task 通过 Windows 验证也可以直接发送错误消息提醒。

疑问

在这个例子中的代码是不是一定要像这样去访问 SMTP Server 的连接信息,感觉是不是多此一举? - 可以不用这样访问,其实很多 SMTP 的配置信息最为方便的是配置成变量,通过变量传递给 Script Task 就可以了。这里通过这样的一种方式来表示在 Script Task 中如何获取连接管理器的属性信息的,因为很多时候我们只知道如何去获取变量信息,这一点体现了 C# Script 的灵活性,我们在 Script 中可以做非常多的事情。包括这些访问 SMTP Server 如何使用 smtpClient 发送邮件的代码可以在网上找到现成的很多很多示例,而我们要做的事情就是大胆的灵活运用它们来解决我们的问题。

在我的这些文章中也提到了邮件处理相关的内容

微软BI 之SSRS 系列 - 报表邮件订阅中 SMTP 服务器匿名访问与 Windows验证, 以及如何成功订阅报表的实例

更多 BI 文章请参看 BI 系列随笔列表 (SSIS, SSRS, SSAS, MDX, SQL Server)  如果觉得这篇文章看了对您有帮助,请帮助推荐,以方便他人在 BIWORK 博客推荐栏中快速看到这些文章。