I have a task to send query result in an email using SSIS 2017.
I have referred to previous posts related to that but i am not able to get my required answer.
Here's what i have done so far:
1. Create an executive sql task with my query and resultset which refers to a variable of object type
2. create a foreach container
3. placed a script task under foreach loop(i think my issue is with this task) and have placed the variable as readonlyvariable
4. send email task with bodytext as variable
My result set is just one column from the table.
I have referred to this url: How to send the records from a table in an e-mail body using SSIS package?
and please find attached the script task code.
Would be great if you guys could help me out
/*Microsoft SQL Server Integration Services Script Task
Write scripts using Microsoft Visual C# 2008.
The ScriptMain is the entry point class of the script.
*/
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
namespace ST_8015f41e93944f0e944089c73b520312
{
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
#region VSTA generated code
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
public void Main()
{
Variables varCollection = null;
string header = string.Empty;
string message = string.Empty;
Dts.VariableDispenser.LockForWrite("User::EmailMessage");
Dts.VariableDispenser.LockForWrite("User::Result");
Dts.VariableDispenser.GetVariables(ref varCollection);
//Set the header message for the query result
if (varCollection["User::EmailMessage"].Value == string.Empty)
{
header = "Execute SQL task output sent using Send Email Task in SSIS:\n\n";
header += string.Format("{0}\n", "Result");
//varCollection["User::EmailMessage"].Value = header;
}
//Format the query result with tab delimiters
message = string.Format("{0}",
varCollection["User::Result"].Value);
varCollection["User::EmailMessage"].Value = varCollection["User::EmailMessage"].Value + message;
Dts.TaskResult = (int)ScriptResults.Success;
}
}
}
I was able to accomplish this without using a script task or db mail. I needed to email myself notices of any reports that failed to refresh across multiple reporting systems. But I didn't want to save the results to a file and attach the file. I just wanted the results in the email body.
I wrote my query to output one concatenated column and added the query results to a recordset Object and used a foreach loop container to append each row to a string variable with "/n" to start a new line.
This created a string that I could use as the body of the email.
Data Flow
Control Flow
Related
I'm new to SSIS and would like to send an email notification when a package fails. I'm using script task with the following code:
#region Namespaces
using System;
using System.Net;
using System.Net.Mail;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
#endregion
public void Main()
{
// TODO: Add your code here
String SendMailFrom = Dts.Variables["EmailFrom"].Value.ToString();
String SendMailTo = Dts.Variables["EmailTo"].Value.ToString();
String SendMailSubject = Dts.Variables["EmailSubject"].Value.ToString();
String SendMailBody = Dts.Variables["EmailBody"].Value.ToString();
try
{
MailMessage email = new MailMessage();
SmtpClient SmtpServer = new SmtpClient("smtp.office365.com");
// START
email.From = new MailAddress(SendMailFrom);
email.To.Add(SendMailTo);
email.Subject = SendMailSubject;
email.Body = SendMailBody;
//END
SmtpServer.Port = 587;
SmtpServer.Credentials = new System.Net.NetworkCredential(SendMailFrom, "Password");
SmtpServer.EnableSsl = true;
SmtpServer.Send(email);
MessageBox.Show("Email was Successfully Sent ");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
Dts.TaskResult = (int)ScriptResults.Success;
Dts.TaskResult = (int)ScriptResults.Success;
}
My first issue is that I can not get this task to work with my own credentials, I get error "System.IOException: Unable to read data from the transport connection: net_io_connection closed."
But even beyond that, I know its unwise to hardcode my own credentials into this script task which I want run by a SQL Agent Job. Is there a way to send this email without any credentials? I don't care where the email is from, only where it is sent to.
SSIS Send Email task has lots of limitations.
It was created long time ago to work with Microsoft Exchange. It even doesn't support emails in HTML format.
Instead of the following line:
SmtpServer.Credentials = new System.Net.NetworkCredential(SendMailFrom, "Password");
You can try the following:
SmtpServer.UseDefaultCredentials = true;
SmtpServer.Credentials = CredentialCache.DefaultNetworkCredentials;
I found the solution to my initial problem here
I was able to add the following line of code to run the script using my own credentials:
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12
However still need to figure out a solution to running this script using sql agent job. Would there be credential issues if another user were to run the job?
I have following C# code in a console application.
Whenever I debug the application and run the query1 (which inserts a new value into the database) and then run query2 (which displays all the entries in the database), I can see the new entry I inserted clearly. However, when I close the application and check the table in the database (in Visual Studio), it is gone. I have no idea why it is not saving.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlServerCe;
using System.Data;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
string fileName = "FlowerShop.sdf";
string fileLocation = "|DataDirectory|\\";
DatabaseAccess dbAccess = new DatabaseAccess();
dbAccess.Connect(fileName, fileLocation);
Console.WriteLine("Connected to the following database:\n"+fileLocation + fileName+"\n");
string query = "Insert into Products(Name, UnitPrice, UnitsInStock) values('NewItem', 500, 90)";
string res = dbAccess.ExecuteQuery(query);
Console.WriteLine(res);
string query2 = "Select * from Products";
string res2 = dbAccess.QueryData(query2);
Console.WriteLine(res2);
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e);
Console.ReadLine();
}
}
}
class DatabaseAccess
{
private SqlCeConnection _connection;
public void Connect(string fileName, string fileLocation)
{
Connect(#"Data Source=" + fileLocation + fileName);
}
public void Connect(string connectionString)
{
_connection = new SqlCeConnection(connectionString);
}
public string QueryData(string query)
{
_connection.Open();
using (SqlCeDataAdapter da = new SqlCeDataAdapter(query, _connection))
using (DataSet ds = new DataSet("Data Set"))
{
da.Fill(ds);
_connection.Close();
return ds.Tables[0].ToReadableString(); // a extension method I created
}
}
public string ExecuteQuery(string query)
{
_connection.Open();
using (SqlCeCommand c = new SqlCeCommand(query, _connection))
{
int r = c.ExecuteNonQuery();
_connection.Close();
return r.ToString();
}
}
}
EDIT: Forgot to mention that I am using SQL Server Compact Edition 4 and VS2012 Express.
It is a quite common problem. You use the |DataDirectory| substitution string. This means that, while debugging your app in the Visual Studio environment, the database used by your application is located in the subfolder BIN\DEBUG folder (or x86 variant) of your project. And this works well as you don't have any kind of error connecting to the database and making update operations.
But then, you exit the debug session and you look at your database through the Visual Studio Server Explorer (or any other suitable tool). This window has a different connection string (probably pointing to the copy of your database in the project folder). You search your tables and you don't see the changes.
Then the problem get worse. You restart VS to go hunting for the bug in your app, but you have your database file listed between your project files and the property Copy to Output directory is set to Copy Always. At this point Visual Studio obliges and copies the original database file from the project folder to the output folder (BIN\DEBUG) and thus your previous changes are lost.
Now, your application inserts/updates again the target table, you again can't find any error in your code and restart the loop again until you decide to post or search on StackOverflow.
You could stop this problem by clicking on the database file listed in your Solution Explorer and changing the property Copy To Output Directory to Copy If Newer or Never Copy. Also you could update your connectionstring in the Server Explorer to look at the working copy of your database or create a second connection. The first one still points to the database in the project folder while the second one points to the database in the BIN\DEBUG folder. In this way you could keep the original database ready for deployment purposes and schema changes, while, with the second connection you could look at the effective results of your coding efforts.
EDIT Special warning for MS-Access database users. The simple act of looking at your table changes the modified date of your database ALSO if you don't write or change anything. So the flag Copy if Newer kicks in and the database file is copied to the output directory. With Access better use Copy Never.
Committing changes / saving changes across debug sessions is a familiar topic in SQL CE forums. It is something that trips up quite a few people. I'll post links to source articles below, but I wanted to paste the answer that seems to get the best results to the most people:
You have several options to change this behavior. If your sdf file is part of the content of your project, this will affect how data is persisted. Remember that when you debug, all output of your project (including the sdf) if in the bin/debug folder.
You can decide not to include the sdf file as part of your project and manage the file location runtime.
If you are using "copy if newer", and project changes you make to the database will overwrite any runtime/debug changes.
If you are using "Do not copy", you will have to specify the location in code (as two levels above where your program is running).
If you have "Copy always", any changes made during runtime will always be overwritten
Answer Source
Here is a link to some further discussion and how to documentation.
I need to put a message into an Azure ServiceBus queue from an SSIS package running under SQL Server 2014. As suggested in this post: connecting to azure service bus queue from ssis,
I wrote a Script Task that references the "Azure SDK 2.9". This approach has worked for me with Azure Storage Accounts to work with blobs (referencing the Microsoft.WindowsAzure.Storage assembly), but it is NOT working for the Azure Storage Bus (referencing the Microsoft.ServiceBus assembly). Any calls I make into that assembly trigger a Run-time exception: "exception has been thrown by the target of an invocation: at System.RuntimeMethodHandle.InvokeMethod(...)" When I comment out all calls to the Microsoft.ServiceBus assembly it runs fine, so it is obviously something about the assembly reference (version 2.4). I tried updating to the latest version with NuGet (version 3.0) and that made no difference.
So my question is: has anybody been able to place a message in an Azure Service Bus queue from SSIS, and if so, how did you do it?
Since somebody will ask for my Script Task code, I'm posting it:
#region Namespaces
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
#endregion
#region CustomNamespaces
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Messaging;
#endregion
namespace ST_dba6519c1eda4e0c968485a6eb7a6c29
{
[Microsoft.SqlServer.Dts.Tasks.ScriptTask.SSISScriptTaskEntryPointAttribute]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
public void Main()
{
try
{
// Create the message for the Queue
string ClientShortName = Dts.Variables["$Package::ClientShortName"].Value.ToString();
bool bExtendedForecast = (bool)Dts.Variables["$Package::ExtendedForecast"].Value;
var msg = new BrokeredMessage(ClientShortName + ": ExtendedForecast=" + bExtendedForecast.ToString()); // this statement throws the exception
// get Service Bus Connection Information from the Package Parameters
string SBAccessKey = Dts.Variables["$Package::ServiceBusAccessKey"].Value.ToString();
string SBNamespace = Dts.Variables["$Package::ServiceBusNamespace"].Value.ToString();
string SBQueue = Dts.Variables["$Package::ServiceBusQueueName"].Value.ToString();
String connStr = "Endpoint=sb://" + SBNamespace +
".servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=" + SBAccessKey;
// First Method tried
Uri SBUri = ServiceBusEnvironment.CreateServiceUri(String.Empty, SBNamespace, String.Empty); // this statement throws the exception
TokenProvider SBToken = TokenProvider.CreateSharedAccessSignatureTokenProvider("RootManageSharedAccessKey", SBAccessKey);
NamespaceManager nsMgr = new NamespaceManager(SBUri, SBToken);
MessagingFactory msgFactory = MessagingFactory.Create(nsMgr.Address, nsMgr.Settings.TokenProvider);
QueueClient queueClient2 = msgFactory.CreateQueueClient(SBQueue);
queueClient2.Send(msg);
// Second Method tried
MessagingFactory factory = MessagingFactory.CreateFromConnectionString(connStr); // this statement throws the exception
MessageSender queueSender = factory.CreateMessageSender(SBQueue);
queueSender.Send(msg);
// Third Method tried
QueueClient queueClient = QueueClient.CreateFromConnectionString(connStr, SBQueue); // this statement throws the exception
queueClient.Send(msg);
Dts.TaskResult = (int)ScriptResults.Success;
}
catch
{
Dts.TaskResult = (int)ScriptResults.Failure;
}
}
#region ScriptResults declaration
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
}
}
So, of course 10 minutes after I post the question, I hit upon the answer. I had to run GACUTIL -i Microsoft.ServiceBus. Once that was done, I chose to use the Third Method in the code (the simplest) to Send the message, and it worked fine.
I am trying to send all the table records in email body though send mail task
My flow:
I uses SQL execute task to fetch the rows from table and stored in an object
Uses for each loop container and in that I use a script task to store the rows in an EmailMessage body
I used Send mail task to send the email
I am only getting last records of the table in the message body.
Please guide me how to send all the table data at once in a message body
Actaul flow
error
I think I would take a slightly different approach and recurse the recordset directly in the script task but this looks like it would work too. I would guess that your problem is that you overwrite User::EmailMessage at every iteration. You say you get last few records but looking at your code I would think you will get 1 unless you uncomment the IF (varcollection == string.empty) in which case you might get more.
Anyway, the main offending problem is
varCollection["User::EmailMessage"].Value = header;
That resets your EmailMessage body to the header row any time it is called.
Edit: Adding as per your comment to reset message at every new shipment number. Add another package variable PrevShippingNum which will hold the previous looped number to test if it is the same or has changed. Make sure that this variable is listed as ReadWriteVariable to the script task. then modify your script to include something like this:
Dts.VariableDispenser.GetVariables(ref varCollection);
bool newMessage = (varCollection["User::PrevShippingNum"].value != varCollection["User::ShppingNum"].value) ? true : false;
if (string.IsNullOrWhiteSpace(varCollection["User::EmailMessage"].Value.ToString()) || newMessage)
{
varCollection["User::EmailMessage"].Value = string.Format("{0}........");
}
varCollection["User::EmailMessage"].Value += string.Format("{0}......");
The positive about this is you can also use your new variable as a constraint to determine when to send email task.
A different Approach:
Note pretty big edit to add new sub to take care of sending emails per ShippingNum:
Way I would proceed pass the recordset variable you are using to a script task and let it do the email message building. Just to be clear this is to replace your foreach loop! here is some code adapted from one of my solutions:
Add Reference to System.Data.DataSetExtensions
Add following namespaces:
using System.Data.OleDb;
using System.Net.Mail;
using System.Linq;
using System.Collections.Generic;
private void Main()
{
//using System.Data.OleDb;
OleDbDataAdapter oleAdapter = new OleDbDataAdapter();
DataTable dt = new DataTable();
oleAdapter.Fill(dt, Dts.Variables["User::OleDbRecordSetVar"].Value);
//build header row
string headerRow = string.Format("{0}........", "ShippingNum ....");
//get distinct shippingNums
var shippingNums = (from DataRow dr in dt.Rows
select (int)dr["ShppingNum"]).Distinct();
//Now Build the Differnt Emails
foreach (var num in shippingNums)
{
string emailBody = headerRow;
List<DataRow> emailLines = (from DataRow dr in dt.Rows
where (int)dr["ShippingNum"] == num
select dr).ToList<DataRow>();
foreach (DataRow line in emailLines)
{
emailBody += string.Format("{0}....", line["ColumnName1"].ToString(), line["ColumnName2"].ToString());
}
SendEmail(emailBody);
}
Dts.TaskResult = (int)ScriptResults.Success;
}
private void SendEmail(string messageBody)
{
//get the smtp server address from the SSIS connection manger
ConnectionManager smtpConnectionManager = Dts.Connections["Name Of SMTP Connection Manager"];
//note this is for trusted authentication if you want to use a username and password you will have to do some discovery
SmtpClient emailClient = new SmtpClient(smtpConnectionManager.Properties["SmtpServer"].GetValue(smtpConnectionManager).ToString());
MailMessage email = new MailMessage();
email.Priority = MailPriority.Normal;
email.IsBodyHtml = false; //change to true if you send html
//can hard code addresses if you desire I use variables to make it more flexible
email.From = new MailAddress(Dts.Variables["User::FromAddress"].Value.ToString());
email.To.Add(Dts.Variables["User::ToAddress"].Value.ToString());
email.Body = messageBody;
emailClient.Send(email);
}
I'd quite like to use ADO.NET to generate a CREATE TABLE script to create an exact copy of a given table.
The reason for this is persistence testing. I would like to know whether my application will persist to a particular database. I would like to be able to point the app to the database and table in question, and then the app will generate a new database with an exact copy of the specified table. Thus, persistence testing can take place against the cloned table without touching the original database, and when I'm done the new database can simply be dropped.
Before I embark on this ambitious project, I would like to know if anything already exists. I've tried Google, but all I can find are ways to get schema generation SQL through the SSMS UI, not through code.
You can use SQL Management Objects (SMO) for this.
Example (C#)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Management.Smo;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Server srv = new Server(#".\SQLEXPRESS");
Database db = srv.Databases["MyDB"];
Scripter scrp = new Scripter(srv);
scrp.Options.ScriptDrops = false;
scrp.Options.WithDependencies = true;
//Iterate through the tables in database and script each one. Display the script.
//Note that the StringCollection type needs the System.Collections.Specialized namespace to be included.
Microsoft.SqlServer.Management.Sdk.Sfc.Urn[] smoObjects = new Microsoft.SqlServer.Management.Sdk.Sfc.Urn[1];
foreach (Table tb in db.Tables)
{
smoObjects[0] = tb.Urn;
if (tb.IsSystemObject == false)
{
System.Collections.Specialized.StringCollection sc;
sc = scrp.Script(smoObjects);
foreach (string st in sc)
Console.WriteLine(st);
}
}
Console.ReadKey();
}
}
}