I am currently working on an application for the company I work for. Part of this application deals with discrepancy reporting and sending emails out when a new discrepancy number has been created. First, it is important to realize that I am redeveloping this application from VB.NET to C#. In the old application the developer chose to read an XML file for a few email addresses. I've read to use alternate options unless the XML file is full of information. This is not intended to be an opinionated question and sorry if this sounds like one. However, I am looking for the correct way of doing this. Should these email addresses be kept in a database table for easy adding/deleting or is there a more standardized way to do this? Please find the current code below.
public void PrepareEmail(string subject, string message)
{
if (MessageBox.Show(#"Are you sure you want to save and send Discrepancy Report: " + tbxDRNumber.Text + #"?\n Click YES to save\n Click NO to cancel", #"Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
SendEmail(subject, message);
}
}
public Array AddEmail()
{
string[] dRemail = { "", "", "" };
if (File.Exists(#"\\fs01\Applications\EMS-Manager\DREmailAddresses.xml"))
{
XmlReader emailDocument = new XmlTextReader(#"\\fs01\Applications\EMS-Manager\DREmailAddresses.xml");
while (emailDocument.Read())
{
var type = emailDocument.NodeType;
switch (type)
{
case XmlNodeType.Element:
if (emailDocument.Name == "DRCreatedAddEmail")
{
dRemail[0] = emailDocument.ReadInnerXml();
}
if (emailDocument.Name == "DRActionNeededAddEmail")
{
dRemail[1] = emailDocument.ReadInnerXml();
}
if (emailDocument.Name == "DRPendingAddEmail")
{
dRemail[2] = emailDocument.ReadInnerXml();
}
else
{
MessageBox.Show(#"The file: 'DREmailAddresses.xml' was not found at: \\fs01\Applications\EMS-Manager");
}
break;
}
}
}
return dRemail;
}
public void SendEmail(string subjectText, string bodyText)
{
string[] email = (string[])AddEmail();
//object oOutlook = default(Microsoft.Office.Interop.Outlook.Application);
var oMessage = default(MailItem);
Activator.CreateInstance(Type.GetTypeFromProgID("Outlook.Application"));
if (subjectText == "New Discrepancy Created. DR" + tbxDRNumber.Text + " ")
{
oMessage.To = email[0];
oMessage.Subject = subjectText;
oMessage.HTMLBody = bodyText;
try
{
oMessage.Send();
}
catch (System.Exception e)
{
MessageBox.Show(#"Send Failed with error: " + e);
throw;
}
}
else if (subjectText == tbxDRNumber.Text + " - Action Needed")
{
oMessage.To = email[1];
oMessage.Subject = subjectText;
oMessage.HTMLBody = bodyText;
try
{
oMessage.Send();
}
catch (System.Exception e)
{
MessageBox.Show(#"Send Failed with error: " + e);
throw;
}
}
else if (subjectText == tbxDRNumber.Text + "DR Pending Approval")
{
oMessage.To = email[2];
oMessage.Subject = subjectText;
oMessage.HTMLBody = bodyText;
try
{
oMessage.Send();
}
catch (System.Exception e)
{
MessageBox.Show(#"Send Failed with error: " + e);
throw;
}
}
}
There isn't necessarily anything wrong with flat file configuration files. It really depends on your use case. How often do the emails in the file change? How many entries are there? Do users of the application edit the list? Are there any complex rules where different addresses will get sent different emails?
If this just a simple mailing list that doesn't change often it's probably fine to use the xml file. You could also just create an email distribution list to send to instead, like ApplicationDiscrepancyReport. That way you can just manage the recipients in active directory. If you stick with the XML email addresses, at the very least I would try to get rid of the hardcoded path to the xml file located on your file share.
If the email addresses change a lot, and are changed by users of the application, I would recommend moving this to a SQL database. If the recipients list is changing frequently you may want to add tracking of when these addresses get edited as well.
Related
I have to create a program which saves Excel attachments from the mail inbox.
At the moment I am saving all attachments from incoming mails via an event handler, but it seems like that the event is not always triggered but rather 3 from 4 mails only. I don't know the reason though.
So I was thinking about looping through the inbox mails, look for mails with specific subject title and save the attached Excel files.
But how can I do that? Other solutions shows only via add in, but I want to use a Windows service for that.
So far my code (this doesn't work every time though, maybe someone knows a reason for that?)
public partial class MyService : ServiceBase
{
public string AttachPath = #"[mypath to save attachments]";
public MyService()
{
InitializeComponent();
}
public void RunAsConsole(string[] args)
{
Console.WriteLine("This service is executed as a console application.");
Console.WriteLine("Application active.");
OnStart(args);
Console.WriteLine("Press q to exit.");
string userInput = Console.ReadLine();
while (userInput != "q")
{
userInput = Console.ReadLine();
}
Console.WriteLine("Finished! \nPress any key to exit...");
Console.ReadLine();
OnStop();
}
protected override void OnStart(string[] args)
{
Outlook.NameSpace outlookNameSpace;
Outlook.MAPIFolder inbox;
Outlook.Items items;
Outlook.Application oApp = new Outlook.Application();
outlookNameSpace = oApp.GetNamespace("MAPI");
inbox = outlookNameSpace.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox);
items = inbox.Items;
items.ItemAdd +=
new Outlook.ItemsEvents_ItemAddEventHandler(items_ItemAdd);
}
void items_ItemAdd(object Item)
{
string filter = "[myFilter]";
Outlook.MailItem mail = (Outlook.MailItem)Item;
if (Item != null)
{
if (mail.Subject.ToUpper().Contains(filter.ToUpper()))
{
Console.WriteLine(DateTime.Now.ToShortTimeString() + " Mail found!: " + mail.Subject);
if (mail.Attachments.Count > 0)
{
for (int i = 1; i - 1 < mail.Attachments.Count; ++i)
{
Console.WriteLine($#"Saving {mail.Attachments[i].FileName}");
//Console.WriteLine(Path.Combine(AttachPath, mail.Attachments[i].FileName));
string filepath = Path.Combine(AttachPath, mail.Attachments[i].FileName);
mail.Attachments[i].SaveAsFile(filepath);
//if (File.Exists(filepath))
//{
// mail.Delete(); //after saving the file delete the mail
//}
}
}
else
{
Console.WriteLine("No attachments found: execute auto reply...");
Outlook.MailItem replyMail = mail.Reply();
replyMail.HTMLBody = $#"Some answer for reply";
replyMail.Send();
}
Console.WriteLine("Delete mail: " + mail.Subject.ToString());
mail.UnRead = false; //mark as read
mail.Delete();
}
}
}
protected override void OnStop()
{
//nothing
}
}
At the moment, the service can be executed as a console application and a Windows service, so please don't pay too much attention at that point, it's for debugging reasons.
Other Solutions shows only via add in, but I want to use a windows service for that.
Microsoft does not currently recommend, and does not support, Automation of Microsoft Office applications from any unattended, non-interactive client application or component (including ASP, ASP.NET, DCOM, and NT Services), because Office may exhibit unstable behavior and/or deadlock when Office is run in this environment.
If you are building a solution that runs in a server-side context, you should try to use components that have been made safe for unattended execution. Or, you should try to find alternatives that allow at least part of the code to run client-side. If you use an Office application from a server-side solution, the application will lack many of the necessary capabilities to run successfully. Additionally, you will be taking risks with the stability of your overall solution. Read more about that in the Considerations for server-side Automation of Office article.
As a workaround, you may consider using a low-level API on which Outlook is based on - Extended MAPI or just any wrappers around that API such as Redemption.
If you deal with Exchange only, you may consider using Graph API or EWS, see Start using web services in Exchange for more information.
Please refer to Eugene's answer on the architecture side of things. But from the perspective of extracting the emails with a filter, you can try this code.
Instead of getting items = inbox.Items, try finding the emails with a filter query which returns an Outlook.Table
Then you can iterate on this table to get the emails.
const string PropTag = "http://schemas.microsoft.com/mapi/proptag/";
var filter = "#SQL=" + "\"" + PropTag
+ "0x0037001E" + "\"" + " ci_phrasematch " + "\'" + strFilter + "\'";
Outlook.Table table = inbox.GetTable(filter, Outlook.OlTableContents.olUserItems);
while (!table.EndOfTable)
{
Outlook.Row nextRow = table.GetNextRow();
try
{
Outlook.MailItem mi;
try
{
string entryId = nextRow["EntryID"];
var item = outlookNameSpace.GetItemFromID(entryId);
mi = (Outlook.MailItem)item;
}
catch (InvalidCastException ex)
{
Console.WriteLine("Cannot cast mail item, so skipping. Error: {0}", e);
continue;
}
//Extract the attachments here and archive or reply - put your logic here
}
catch (Exception e)
{
Console.WriteLine("An error occurred: '{0}'", e);
}
}
I'm attempting to read a text file and delete a user entered string. I cannot get it to report a message if the string does not exist.
I cannot explain everything I've tried to this point, it's been many things. I know there is nothing in it's current form that would give me the results I expect, but I've tried many many things and this is currently where it's at. For the code that is there, it's doing everything I'm telling it to do.
if (rButtonDelete.Checked)
{
bool isValid = txtID.Text.Length < 5;
if (txtID.Text == "")
{
lbOne.Items.Add("You must enter a fixture to delete.");
}
else
if(!isValid==false)
{
lbOne.Items.Add("Enter full fixture ID to delete.");
}
else
{
var oldLines = System.IO.File.ReadAllLines(#"F:\09 Quality\CMM Fixtures\fixtures.txt");
var newLines = oldLines.Where(lines => !lines.Contains(txtID.Text));
System.IO.File.WriteAllLines(#"F:\09 Quality\CMM Fixtures\fixtures.txt", newLines);
lbOne.Items.Add(txtID.Text + " was deleted.");
}
}
As stated above, as it exists now, it does everything I am telling it to do. I just need to report that a string being searched for does not exist if in doesn't. No matter what I type into the text box, it tells me it's been deleted, even if it doesn't exist.
How about this:
if (oldLines.Count() == newLines.Count())
{
lbOne.Items.Add(txtID.Text + " does not exist.");
}
else
{
lbOne.Items.Add(txtID.Text + " was deleted.");
}
I have looked around a lot of MS Dynamics CRM blogs and SO questions and tried all the solutions but they haven't worked.
The problem I am facing is as follows: I am trying to loop through an excel file with company names and company type. I then find company with matching names in CRM and set values for some custom fields depending on the company type in excel. When I do this though code for a single company it works fine however when I try to run a loop for a large number of companies I am constantly getting:
SecLib::CrmCheckPrivilege failed. Returned hr = -2147220943 on UserId: xxxxxx and PrivilegeType: Read.
The user in question has all privilleges and is an admin user with Read/Write CAL. Also like I pointed out I am able to do the updates for a single record. But when run as a loop that very same record throws an error.
I have been unable to find a solution for this so any help would be much appreciated. I am using MS Dynamics CRM online.
Here's my code:
while (!parser.EndOfData)
{
counter++;
if (counter >= 20)
{
try
{
counter = 0;
context.SaveChanges();
}
catch (Exception exc)
{
while (exc != null)
{
System.Diagnostics.Debug.WriteLine("ERROR: " + exc.Message);
exc = exc.InnerException;
}
continue;
}
}
//Processing row
string[] fields = parser.ReadFields();
foreach (string field in fields)
{
//TODO: Process field
company.Add(fields[0]);
category.Add(fields[1]);
var currAccs = context.CreateQuery<Account>().Where(x => x.Name.Contains(fields[0])).ToList();
if (currAccs != null)
{
foreach (var currAcc in currAccs)
{
System.Diagnostics.Debug.WriteLine("Processing: " + currAcc.Name);
currAcc.cir_MediaOwner = false;
currAcc.cir_Agency = false;
currAcc.cir_AdvertiserBrand = false;
if (fields[1].Contains("MO"))
{
currAcc.cir_MediaOwner = true;
}
if (fields[1].Contains("A"))
{
currAcc.cir_Agency = true;
}
if (fields[1].Contains("B"))
{
currAcc.cir_AdvertiserBrand = true;
}
try
{
context.UpdateObject(currAcc);
}
catch (Exception exc)
{
while (exc != null)
{
System.Diagnostics.Debug.WriteLine("ERROR: " + exc.Message);
exc = exc.InnerException;
}
continue;
}
}
}
break;
}
}
It is possible that you have either a Workflow or Plugin Step related to account and create/update message, impersonated with an User without the proper privileges.
For workflows, these could have the option "execute as: the owner of the workflow" (link)
For plug-in steps, these have an option "Run in User's Context" that can be set to a fix user (link)
having an issue when saving data, I am trying to set it up so if the user tries to save their name data for their character and a file already exists, it will create a second xml file "NameData2.xml" and so forth until a maximum of 3 files is reached, ( so the user has differant characters to choose from ) however at the moment it is simply creating 2 xml files at once all containing the same name ( i would guess this is due to them checking all at once in the same if else statements possibly? all i could find when trying to find an answer was how to check for a files existence and if it cant find it how to create one, i will post my code below if anyone has any suggestions that would be brilliant as im quite stumped!
Thankyou in advance.
// This will save the users generated name in a created file
// NameData.xml and will take the user to the partySelectionScreen.
private void continueButton_Click(object sender, RoutedEventArgs e)
{
try
{
NameSavingInformation nameInfo = new NameSavingInformation();
nameInfo.GeneratedName = generatedNameTexbox.Text;
SaveXml.SaveData(nameInfo, "NameData.xml");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
if(File.Exists("NameData.xml"))
{
NameSavingInformation nameInfo = new NameSavingInformation();
nameInfo.GeneratedName = generatedNameTexbox.Text;
SaveXml.SaveData(nameInfo, "NameData2.xml");
}
else if (File.Exists("NameData2.xml"))
{
NameSavingInformation nameInfo = new NameSavingInformation();
nameInfo.GeneratedName = generatedNameTexbox.Text;
SaveXml.SaveData(nameInfo, "NameData3.xml");
}
else if (File.Exists("NameData3.xml"))
{
MessageBox.Show("You have passed the limit of existing characters" +
"To continue please return to the main menu and delete at least 1 character");
}
You should do like this.
This will perform a a search on the files that doesn't exists and will generate them. This will permit the user to delete any character the wants without breaking the algorithm.
private void continueButton_Click(object sender, RoutedEventArgs e)
{
if(!File.Exists("NameData.xml"))
{
SaveFileInfo("NameData.xml");
}
else if (!File.Exists("NameData2.xml"))
{
SaveFileInfo("NameData2.xml");
}
else if (!File.Exists("NameData3.xml"))
{
SaveFileInfo("NameData3.xml");
}
else
{
MessageBox.Show("You have passed the limit of existing characters" +
"To continue please return to the main menu and delete at least 1 character");
}
}
public SaveFileInfo(string fileName)
{
try
{
NameSavingInformation nameInfo = new NameSavingInformation();
nameInfo.GeneratedName = generatedNameTexbox.Text;
SaveXml.SaveData(nameInfo, fileName);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
This is my first time posting a question although I have been lurking and learning from all of you for a while now. I have been developing a C# Windows Form application that automates several day to day activities. One of the simplest pieces of this application is really giving me a hard time.
I need to insert two jpg files into an Email that I am responding to. I can accomplish this by pulling the files directly from the drive but would prefer them to be stored as a resource in the executable. This way I can pass the EXE to others and they can use it also. Here is an example of the code that works when it is stored locally. what I would prefer to do however is replace #"H:\ISOIR\PhishMeIcon.jpg" with Resource1.PhishMeIconJPG . I have seen several discussion about streams and converting the file to Byte format but this does not seem to interact well with Outlook.
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("Make sure you have the Email reporting the incident Open");
string inc_num;
inc_num = incident_number.Text;
Microsoft.Office.Interop.Outlook.Application aPP = new Microsoft.Office.Interop.Outlook.Application(1);
Microsoft.Office.Interop.Outlook.MailItem mail = (Microsoft.Office.Interop.Outlook.MailItem)aPP.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
Microsoft.Office.Interop.Outlook.Folder f = aPP.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderDrafts) as Microsoft.Office.Interop.Outlook.Folder;
Microsoft.Office.Interop.Outlook.Inspector inSpect = null;
Microsoft.Office.Interop.Outlook.MailItem sMail = null;
Microsoft.Office.Interop.Outlook.MailItem rMail = null;
Microsoft.Office.Interop.Outlook.Attachment phishICO = null;
try
{
inSpect = aPP.ActiveInspector();
sMail = inSpect.CurrentItem as Microsoft.Office.Interop.Outlook.MailItem;
rMail = sMail.ReplyAll();
phishICO = rMail.Attachments.Add(#"H:\ISOIR\PhishMeIcon.jpg", Microsoft.Office.Interop.Outlook.OlAttachmentType.olEmbeddeditem, null, "name");
string imageCid = "PhishMeIcon.jpg#123";
phishICO.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001E", imageCid);
rMail.Subject = ("INC- ") + inc_num;
rMail.HTMLBody = Resource1.SPAM_Response_P1 + String.Format("<body><img src=\"cid:{0}\"></body>", imageCid) + Resource1.SPAM_Response_P2 + rMail.HTMLBody;
rMail.HTMLBody = rMail.HTMLBody.Replace("XXXX", inc_num);
rMail.Save();
MessageBox.Show("Your Email has been saved in your DRAFT Folder for review");
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message,
"An exception is occured in the code of add-in.");
}
finally
{
if (sMail != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(sMail);
if (rMail != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(rMail);
if (inSpect != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(inSpect);
}
}