Send SSIS package failure notification to Outlook email using no credentials - sql-server

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?

Related

Using SSIS to send query results via email

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

Connecting to Azure Service Bus from SSIS

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.

SSIS Send email from package that is off the domain

I have a SSIS package that is deployed to server that is not on a domain but it is within our network perimeter. I would like to be able to send an email on package failure but being off the domain presents a challenge that I have not faced before.
Using a dummy gmail account is not acceptable to the business.
I would like to use the company SMTP server (mail.somecompany.com) but I do not know how to do this while off the domain. Can someone please tell me if this is possible and what steps I need to take.
Useful information:
SQL Server 2016
Project Deployment mode
Visual Studio 2015
Many thanks in advance.
You can use script task for sending mail.
This might can help you:
MailMessage Mymsg = new MailMessage();
SmtpClient smptserver = new SmtpClient();
System.Net.Mail.Attachment attach;
Mymsg.From = new MailAddress("aa#abc.com");
String Tolist = Convert.ToString("a#aa.com");
String CClist = Convert.ToString("a#xse.com");
String BCClist = Convert.ToString("ba#aas.com");
if (!String.IsNullOrEmpty(Tolist))
Mymsg.To.Add(Tolist);
if (!String.IsNullOrEmpty(CClist))
Mymsg.CC.Add(CClist);
if (!String.IsNullOrEmpty(BCClist))
Mymsg.Bcc.Add(BCClist);
Mymsg.Subject = "Subject!";
Mymsg.Body = "Hi, Your message!";
Mymsg.IsBodyHtml = true;
try
{
smptserver.Send(Mymsg);
Console.WriteLine("OK");
}
catch (System.Net.Mail.SmtpException ex)
{
Console.WriteLine(ex.StatusCode.ToString());
}
Dts.TaskResult = (int)ScriptResults.Success;
smptserver = null;
Mymsg = null;

Send all records from SQL Server table though send mail task in body

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);
}

Google User Provisiong using Google Admin SDK c# -- Google.Apis.Admin.Directory.directory_v1.cs not found

I tried authenticating with Google Admin Api-sdk But We get some file missings error which should be created by the Dlls, we are using.
Even after adding all the recommended dlls after going through many article for the same, I din get over to this. Here is the code im using.
protected void Page_Load(object sender, EventArgs e)
{
const string serviceAccountEmail = "<id>#developer.gserviceaccount.com";
const string serviceAccountCertPath = #"E:\Test.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "admin#mydomain.com";
var certificate = new X509Certificate2(serviceAccountCertPath, serviceAccountCertPassword, X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser },
User = userEmail
}.FromCertificate(certificate));
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "User Provisioning",
});
User newuserbody = new User();
UserName newusername = new UserName();
newuserbody.PrimaryEmail = "Harsh#test.com";
newusername.GivenName = "Harsh";
newusername.FamilyName = "Sharma";
newuserbody.Name = newusername;
newuserbody.Password = "test#123";
User results = service.Users.Insert(newuserbody).Execute();
}
}
}
I am using this code for new user provisioning but Google.Apis.Admin.Directory.directory_v1.cs not found while debugging due to this authentication got failed. Please anybody let me know to to get Google.Apis.Admin.Directory.directory_v1.cs file. As much i know i have already added all the dlls added.
The Namespaces i am using are as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Google.Apis.Admin.Directory.directory_v1;
using Google.Apis.Admin.Directory.directory_v1.Data;
using DotNetOpenAuth.GoogleOAuth2;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Auth.OAuth2.Responses;
As per the documentation, you need to download an extra NuGet package for each API you want to use. These packages contain the generated code for that particular API.
thanks all for replying,
I managed to run it successfully, I had all the reference, Code was upto the mark as well.
The Only problem was with the admin setting there in the google admin panel.
I manage to correct them as per my request to google API's and it worked fine.

Resources