4

I have a requirement to build a SSIS package that sends HTML formatted emails and then saves the emails as tiff files. I have created a script task that processes the necessary records and then coverts the HTML code to the tiff. I have split the process into separate packages, the email send works fine the converting HTML to tiff is causing the issue.

When running the package manually it will process all files without any issues. my test currently is about 315 files this needs to be able to process at least 1,000 when finished with the ability to send up to 10,000 at one time. The problem is when I set the package to execute using SQL Server Agent it stops at 207 files. The package is deployed to SQL Server 2019 in the SSIS Catalog

Server version and resources

What I have tried so far

I started with the script being placed in a SSIS package and deployed to the server and calling the package from a step (works 99.999999% of the time with all packages) tried both 32 and 64 bit runtime. Never any error messages just Unexpected Termination when looking at the execution reports. When clicking in the catalog and executing package it will process all the files. The SQL Server Agent is using a proxy and I also created another proxy account with my admin credentials to test for any issues with the account.

Created another package to call the package and used the Execute Package Task to call the first package, same result 207 files. Changed the execute Process task to an Execute SQL Task and tried the script that is created to manually start a package in the catalog 207 files. Tried executing the script from the command line both through the other SSIS package and the SQL Server Agent directly same results 207 files. If I try any of those methods directly outside SQL Server Agent the process runs no issues.

I converted the script task to a console application and it works processing all the files. When calling the executable file from any method from the SQL Server Agent it once again stops at the 207 files.

I have consulted with the companies DBA and Systems teams and they have not found anything that could be causing this error. There seems to be some type of limit that no matter the method of execution SQL Server Agent will not allow. I have mentioned looking at third-party applications but have been told no.

I have included the code below that I have been able to piece together. I am a SQL developer so C# is outside my knowledge base. Is there a way to optimize the code so it only uses one thread or does a cleanup between each letter. There may be a need for this to create over ten thousand letters at certain times.

Update

I have replaced the code with the new updated code. The email and image creation are all included as this is what the final product must do. When sending the emails there is a primary and secondary email address and depending on what email address is used it will change what the body of the email contains. When looking at the code there is a section of try catch that sends to primary when indicated to and if that fails it send to secondary instead. I am guessing there is a much cleaner way of doing that section but this is my first program as I work in SQL for everything else.

Thank You for all the suggestions and help.

Updated Code

using System;
using System.Data;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
using System.Drawing;
using System.IO;
using System.Net.Mail;
using System.Configuration;
using System.Diagnostics;

namespace DocCreator
{

    class Program
    {
        static void Main(string[] args)
        {
            var connSSIS = ConfigurationManager.ConnectionStrings["ssis_ssrs"].ConnectionString;
            int logid = 0;
            int count = 0;
            string previous = "";
            try
            {
                var connDataExtract = ConfigurationManager.ConnectionStrings["dataExtract"].ConnectionString;
                string archiveFolder = @"Folder Path";
                string project = "Mobile Pay";
                string logProc = "[dbo].[usp_EmailSentandError]";
                int rowCount = 0;
                DataTable dt = new DataTable();
                using (SqlConnection connLog = new SqlConnection(connDataExtract))
                {
                    connLog.Open();
                    SqlCommand command = new SqlCommand("etl.usp_GetEmailListID", connLog)
                    {
                        CommandType = CommandType.StoredProcedure
                    };
                    command.Parameters.Add("@Project", SqlDbType.NText).Value = project;
                    command.Parameters.Add("@NullValue", SqlDbType.Int).Value = 0;
                    using (SqlDataReader dr = command.ExecuteReader())
                    {
                        dt.Load(dr);
                    }
                    connLog.Close();
                }
               foreach (DataRow dr in dt.Rows)
                //Parallel.ForEach(dt.AsEnumerable(), dr =>
                {

                    try
                    {
                        var emailID = dr["Email_ID"];
                        var usePrimary = dr["UsePrimaryEmail"];
                        try
                        {
                            if ((bool)usePrimary)
                            {
                                try
                                {
                                    var dp = GetDataPoints(connDataExtract, (int)emailID, (bool)usePrimary);
                                    string indexfqdn = Path.Combine(archiveFolder, dp.IndexFile);
                                    string filefqdn = Path.Combine(archiveFolder, dp.ArchiveFileName);
                                    string mailBody = GetEmailBody(connDataExtract, dp.SqlProc, (int)emailID, dp.EmailBody_id);
                                    SendEmail(dp.EmailFrom, dp.EmailSubject, dp.Email, mailBody);
                                    Archive(mailBody, filefqdn, dp.FileWidth, dp.FileHeight, dp.IndexFileInsert, indexfqdn, dp.ArchiveFile);
                                    LogEmail(connDataExtract, logProc, (int)emailID, dp.EmailBody_id, 1, 1, "", 0);
                                    rowCount++;
                                }
                                catch (Exception e)
                                {
                                    try
                                    {
                                        var dp = GetDataPoints(connDataExtract, (int)emailID, false);
                                        string indexfqdn = Path.Combine(archiveFolder, dp.IndexFile);
                                        string filefqdn = Path.Combine(archiveFolder, dp.ArchiveFileName);
                                        string mailBody = GetEmailBody(connDataExtract, dp.SqlProc, (int)emailID, dp.EmailBody_id);
                                        SendEmail(dp.EmailFrom, dp.EmailSubject, dp.Email, mailBody);
                                        Archive(mailBody, filefqdn, dp.FileWidth, dp.FileHeight, dp.IndexFileInsert, indexfqdn, dp.ArchiveFile);
                                        LogEmail(connDataExtract, logProc, (int)emailID, dp.EmailBody_id, 0, 1, e.Message.ToString(), 1);
                                        rowCount++;
                                    }
                                    catch (Exception e2)
                                    {
                                        LogEmail(connDataExtract, logProc, (int)emailID, 0, 0, 0, e2.Message.ToString(), 1);
                                        Console.Clear();
                                        Console.WriteLine(e2.Message);
                                        Console.ReadLine();
                                    }
                                }
                            }
                            else
                            {
                                try
                                {
                                    var dp = GetDataPoints(connDataExtract, (int)emailID, (bool)usePrimary);
                                    string indexfqdn = Path.Combine(archiveFolder, dp.IndexFile);
                                    string filefqdn = Path.Combine(archiveFolder, dp.ArchiveFileName);
                                    string mailBody = GetEmailBody(connDataExtract, dp.SqlProc, (int)emailID, dp.EmailBody_id);
                                    SendEmail(dp.EmailFrom, dp.EmailSubject, dp.Email, mailBody);
                                    Archive(mailBody, filefqdn, dp.FileWidth, dp.FileHeight, dp.IndexFileInsert, indexfqdn, dp.ArchiveFile);
                                    LogEmail(connDataExtract, logProc, (int)emailID, dp.EmailBody_id, 0, 1, "", 0);
                                    rowCount++;
                                }
                                catch (Exception e)
                                {
                                    LogEmail(connDataExtract, logProc, (int)emailID, 0, 0, 0, e.Message.ToString(), 1);
                                    Console.Clear();
                                    Console.WriteLine(e.Message);
                                    Console.ReadLine();
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            LogEmail(connDataExtract, logProc, (int)emailID, 0, 0, 0, e.Message.ToString(), 1);
                            Console.Clear();
                            Console.WriteLine(e.Message);
                            Console.ReadLine();
                        }
                    }


                    catch (Exception e2)
                    {
                        //Console.WriteLine(e2.Message);
                        using (SqlConnection connLog2 = new SqlConnection(connSSIS))
                        {
                            connLog2.Open();
                            SqlCommand command2 = new SqlCommand("dbo.usp_InsertssisScriptTaskLog", connLog2)
                            {
                                CommandType = CommandType.StoredProcedure
                            };
                            command2.Parameters.Add("@PackageLogID", SqlDbType.Int).Value = ((ulong)logid);
                            command2.Parameters.Add("@ErrorMessage", SqlDbType.NText).Value = e2.Message.ToString();
                            command2.ExecuteNonQuery();
                            connLog2.Close();
                        }
                    }

                    count++;
                    int directThreadsCount = Process.GetCurrentProcess().Threads.Count;
                    Console.Clear();
                    Console.WriteLine(previous + Environment.NewLine+ count + "  Memory usage: " + GC.GetTotalMemory(false) + "     "  + GC.GetTotalMemory(true)  + "  Threads: " + directThreadsCount);
                    previous = count + "  Memory usage: " + GC.GetTotalMemory(false) + "     "  + GC.GetTotalMemory(true) + "  Threads: " + directThreadsCount;
                    GC.Collect(1);
                }
                Console.WriteLine("Files have been created");

            }
            catch (Exception e)
            {
                //Console.WriteLine(e.Message);
                using (SqlConnection connLog = new SqlConnection(connSSIS))
                {
                    connLog.Open();
                    SqlCommand command = new SqlCommand("dbo.usp_InsertssisScriptTaskLog", connLog)
                    {
                        CommandType = CommandType.StoredProcedure
                    };
                    command.Parameters.Add("@PackageLogID", SqlDbType.Int).Value = ((ulong)logid);
                    command.Parameters.Add("@ErrorMessage", SqlDbType.NText).Value = e.Message.ToString();
                    command.ExecuteNonQuery();
                    connLog.Close();
                }
            }
        }

        public static
        (int EmailBody_id, bool ArchiveFile, int FileHeight, int FileWidth, string IndexFile, string IndexFileInsert, string Email, string ArchiveFileName, string EmailFrom, string EmailSubject, string SqlProc)
            GetDataPoints(string connDataExtract, int Email_ID, bool UsePrimary)
        {
            string dataExtract = connDataExtract;
            int emailID = Email_ID;
            bool usePri = UsePrimary;
            using (SqlConnection connLog = new SqlConnection(dataExtract))
            {
                connLog.Open();
                SqlCommand command = new SqlCommand("etl.usp_EmailGetDataPoints", connLog)
                {
                    CommandType = CommandType.StoredProcedure
                };
                command.Parameters.Add("@Email_ID", SqlDbType.Int).Value = emailID;
                command.Parameters.Add("@UsePrimary", SqlDbType.Bit).Value = usePri;
                SqlDataReader sqlDataReader;
                int EmailBody_id = 0;
                bool ArchiveFile = true;
                int FileHeight = 0;
                int FileWidth = 0;
                string IndexFile = "";
                string IndexFileInsert = "";
                string Email = "";
                string ArchiveFileName = "";
                string EmailFrom = "";
                string EmailSubject = "";
                string SqlProc = "";
                sqlDataReader = command.ExecuteReader();
                while (sqlDataReader.Read())
                {
                    EmailBody_id = (int)sqlDataReader.GetValue(sqlDataReader.GetOrdinal("EmailBody_ID"));
                    ArchiveFile = (bool)sqlDataReader.GetValue(sqlDataReader.GetOrdinal("ArchiveFile"));
                    FileHeight = (int)sqlDataReader.GetValue(sqlDataReader.GetOrdinal("FileHeight"));
                    FileWidth = (int)sqlDataReader.GetValue(sqlDataReader.GetOrdinal("FileWidth"));
                    IndexFile = sqlDataReader.GetValue(sqlDataReader.GetOrdinal("IndexFile")).ToString();
                    IndexFileInsert = sqlDataReader.GetValue(sqlDataReader.GetOrdinal("IndexFileInsert")).ToString();
                    Email = sqlDataReader.GetValue(sqlDataReader.GetOrdinal("Email")).ToString();
                    ArchiveFileName = sqlDataReader.GetValue(sqlDataReader.GetOrdinal("ArchiveFileName")).ToString();
                    EmailFrom = sqlDataReader.GetValue(sqlDataReader.GetOrdinal("EmailFrom")).ToString();
                    EmailSubject = sqlDataReader.GetValue(sqlDataReader.GetOrdinal("EmailSubject")).ToString();
                    SqlProc = sqlDataReader.GetValue(sqlDataReader.GetOrdinal("SqlProc")).ToString();
                }
                connLog.Close();
                return (
                    EmailBody_id,
                    ArchiveFile,
                    FileHeight,
                    FileWidth,
                    IndexFile,
                    IndexFileInsert,
                    Email,
                    ArchiveFileName,
                    EmailFrom,
                    EmailSubject,
                    SqlProc);
            }
        }
        public static string GetEmailBody(string connDataExtract, string SQLProc, int Email_ID, int EmailBodyID)
        {
            string dataExtract = connDataExtract;
            string proc = SQLProc;
            int emailID = Email_ID;
            int bodyID = EmailBodyID;
            string MailBody;
            using (SqlConnection connLog = new SqlConnection(dataExtract))
            {
                connLog.Open();
                SqlCommand command = new SqlCommand(proc, connLog)
                {
                    CommandType = CommandType.StoredProcedure
                };
                command.Parameters.Add("@Email_ID", SqlDbType.Int).Value = ((ulong)emailID);
                command.Parameters.Add("@EmailBody_ID", SqlDbType.Int).Value = ((ulong)bodyID);
                SqlDataReader dataReader;
                string Output = "";
                dataReader = command.ExecuteReader();
                while (dataReader.Read())
                {
                    Output = dataReader.GetValue(0).ToString();
                }
                connLog.Close();
                MailBody = Output;
                return MailBody;
            }
        }
        public static void SendEmail(string emailFrom, string emailSubject, string emailTo, string mailBody)
        {
            string from = emailFrom;
            string subject = emailSubject;
            string to = emailTo;
            string source = mailBody;
            using (MailMessage myHtmlFormattedMail = new MailMessage())
            {
                MailAddress fromMail = new MailAddress(from);
                myHtmlFormattedMail.From = fromMail;
                myHtmlFormattedMail.Subject = subject;
                myHtmlFormattedMail.Body = source;
                foreach (var address in to.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries))
                {
                    myHtmlFormattedMail.To.Add(address);
                }
                myHtmlFormattedMail.IsBodyHtml = true;
                SmtpClient mySmtpClient = new SmtpClient();
                mySmtpClient.Send(myHtmlFormattedMail);
            }
        }
        public static void IndexFile(string indexFileInsert, string indexfqdn)
        {
            string insert = indexFileInsert;
            string fqdn = indexfqdn;
            try
            {
                if (!File.Exists(fqdn))
                {
                    using (StreamWriter sw = File.CreateText(fqdn))
                    {
                        sw.WriteLine(insert);
                    }
                }
                using (StreamWriter sw = File.AppendText(fqdn))
                {
                    sw.WriteLine(insert);
                }
            }
            catch { }
        }
        public static void LogEmail(string databaseServer, string logProc, int email_ID, int emailBodyID, int primaryEmailUsed, int emailSent, string errorMessage, int errorExists)
        {
            string dataExtract = databaseServer;
            string proc = logProc;
            int emailID = email_ID;
            int bodyID = emailBodyID;
            int usePri = primaryEmailUsed;
            int sent = emailSent;
            string error = errorMessage;
            int exists = errorExists;
            using (SqlConnection connLog = new SqlConnection(dataExtract))
            {
                connLog.Open();
                SqlCommand command = new SqlCommand(proc, connLog)
                {
                    CommandType = CommandType.StoredProcedure
                };
                command.Parameters.Add("@Email_ID", SqlDbType.Int).Value = ((ulong)emailID);
                command.Parameters.Add("@EmailBody_ID", SqlDbType.Int).Value = ((ulong)bodyID);
                command.Parameters.Add("@PrimaryEmailUsed", SqlDbType.Int).Value = ((ulong)usePri);
                command.Parameters.Add("@EmailSent", SqlDbType.Int).Value = ((ulong)sent);
                command.Parameters.Add("@ErrorMessage", SqlDbType.NText).Value = error;
                command.Parameters.Add("@ErrorExists", SqlDbType.Int).Value = ((ulong)exists);
                command.ExecuteNonQuery();
                connLog.Close();
            }
        }
        public static void StartBrowser(string mailBody, string file, int width, int height, string fileInsert, string indexFile)
        {
            try
            {
                string source = mailBody;
                string fqdn = file;
                int w = width;
                int h = height;
                string insert = fileInsert;
                string indexFQDN = indexFile;
                IndexFile(insert, indexFQDN);
                using (WebBrowser wb = new WebBrowser())
                {
                    wb.ScrollBarsEnabled = false;
                    wb.Width = w;
                    wb.Height = h;
                    wb.Visible = false;
                    wb.DocumentCompleted +=
                         (sender, e) => WebBrowser_DocumentCompleted(sender, e, fqdn);
                    wb.DocumentText = source;
                    Application.Run();
                }
            }
            finally 
            {
                Application.Exit();
            }
        }
        public static void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e, string file)
        {
            string fqdn = file;
            var webBrowser = (WebBrowser)sender;
            using (Bitmap bitmap = new Bitmap(webBrowser.Width, webBrowser.Height))
            {
                webBrowser
                    .DrawToBitmap(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height));
                bitmap.Save(fqdn, System.Drawing.Imaging.ImageFormat.Jpeg);
            }
        }
        static void Wait(int milliseconds)
        {
            using (System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer())
            {
                if (milliseconds == 0 || milliseconds < 0) return;

                // Console.WriteLine("start wait timer");
                timer1.Interval = milliseconds;
                timer1.Enabled = true;
                timer1.Start();

                timer1.Tick += (s, e) =>
                {
                    timer1.Enabled = false;
                    timer1.Stop();
                // Console.WriteLine("stop wait timer");
            };

                while (timer1.Enabled)
                {
                    System.Windows.Forms.Application.DoEvents();
                }
            }
        }
        public static void Archive(string emailBody, string file, int width, int height, string fileInsert, string indexFile, bool archiveFile)
        {
            string source = emailBody;
            string fqdn = file;
            int w = width;
            int h = height;
            string insert = fileInsert;
            string indexFQDN = indexFile;
            bool archive = archiveFile;
            if (archive)
            {
                Thread tr = new Thread(() => StartBrowser(source, fqdn, w, h, insert, indexFQDN))
                    {
                        Name = "Fred",
                        IsBackground = true
                    };
                    tr.SetApartmentState(ApartmentState.STA);
                    tr.Start();

                int wc = 800;
                while (!File.Exists(file) || wc <= 0)
                {
                    Wait(50);
                    wc--;
                };
                tr.Abort();
            }
        }

    }
}

peston
  • 61
  • 5
  • I must admit, none of this really looks like it's a task for SQL Server and SSIS, and more should be some kind of application doing this work. – Thom A Mar 04 '22 at 15:46
  • It really should be, the code above is actually in a console app and the ssis package just calls the executable. I am very limited as to what I am allowed to create and they are trying to force this into SSIS. Most of what I do is beyond the normal limits of SSIS. – peston Mar 04 '22 at 16:05
  • I don't like the way you have implemented sleep/wait. A release version might optimize the cod in an unwanted way. Maybe you are sending too fast or running out of memory (try to increase it in the project settings) while processing images. – wp78de Mar 04 '22 at 20:24
  • The sleep/wait was added for troubleshooting purposes. My original thought was that it was stepping on itself and the previous file was not being created. I had it set to 5 seconds between checks and it still did not make a difference. For memory and CPU usage, the DBA and System admin have both watched the metrics in the controller on the VM and host machine and have not seen any spikes in either. – peston Mar 04 '22 at 20:29
  • After a first pass... `SqlConnection`, `SqlCommand` and `SqlDataReader` are all `IDisposable` classes, meaning that they should be used in `with (...) {...}` blocks so that they are closed and disposed of as soon as possible. The `Timer` and `WebBrowser` classes also descend from the `Component` class, meaning that they are also `IDisposable` and should be handled similarly. If I were to guess the `WebBrowser` instances are the major source of memory problems, since they pull in a lot of GDI resources for rendering, plus the HTML Document Object Model will be full of `IDisposable`s, etc.. – AlwaysLearning Mar 05 '22 at 04:32

1 Answers1

1

I have resolved the issue so it meets the needs of my project. There is probably a better solution but this does work. Using the code above I created an executable file and limited the result set to top 100. Created a ssis package with a For Loop that does a record count from the staging table and kicks off the executable file. I performed several tests and was able to exceed the 10,000 limit that was a requirement to the project.

peston
  • 61
  • 5
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 07 '22 at 18:45