I'm using MailSystem.NET and trying to delete a message from the server. The problem is the IndexOnServer property is 0, and I get the following error:
Command "store 0 +flags.silent (\Deleted)" failed : 121031084812790 BAD Error in IMAP command STORE: Invalid messageset
Here's my code:
Imap4Client client = new Imap4Client();
client.Connect(account.Server, account.Username, account.Password);
var inbox = client.SelectMailbox("Inbox");
MessageCollection messages = inbox.SearchParse("SINCE " + DateTime.Now.AddHours(-hours).ToString("dd-MMM-yyyy"));
foreach (Message newMessage in messages)
{
inbox.DeleteMessage(newMessage.IndexOnServer, true);
}
How do I get the correct index of the message so I can delete it?
Edit:
The problem with the suggestions using the standard 1-based loop is that the counter index won't be in sync with the message index, since in my case I'm searching to retrieve a specific subset of messages only (as I understand it).
Thank you.
You can try deleting by the UID which should be more reliable and unique to each message. This has worked well for me in the past.
Edit: Since deleting the message causes all the indexes to shift down by one, you can use two separate counters. One to keep track of when you have iterated through the entire box (messagesLeft) and the other will keep track of the current message index which will be decreased by 1 if a message is deleted (since it would move up one place in line).
Mailbox box = client.AllMailboxes["inbox"];
Fetch fetch = box.Fetch;
int messagesLeft = box.Count;
int msgIndex = 0;
while (messagesLeft > 0)
{
msgIndex++;
messagesLeft--;
Message email = fetch.MessageObject(msgIndex);
if (criteria)
{
box.UidDeleteMessage(fetch.Uid(msgIndex), true);
msgIndex--;
}
}
In response to your comment, here is a fuller example of how you can use the UID for deletion without being concerned with the numeric position / index.
class Email
{
int UID { get; set; }
DateTime Sent { get; set; }
public string Body { get; set; }
// put whichever properties you will need
}
List<Email> GetEmails(string mailbox);
{
Mailbox box = client.AllMailboxes[mailbox];
Fetch fetch = box.Fetch;
List<Email> list = new List<Email>();
for (int x = 1; x <= box.MessageCount; x++)
{
Message msg = fetch.MessageObject(x);
list.Add(new Email() { } // set properties from the msg object
}
return list;
}
void DeleteEmail(Email email, string mailbox)
{
Mailbox box = client.AllMailboxes[mailbox];
box.UidDeleteMessage(email.Uid, true);
}
static void Main()
{
List<Email> emails = GetEmails("inbox");
emails = emails.Where(email => email.Sent < DateTime.Now.AddHours(-hours))
foreach (Email email in emails)
DeleteEmail(email);
}
This is official example from documantation.
instead of inbox.search("all") change it to your search query, etc.
http://mailsystem.codeplex.com/discussions/269304
//action_id is the Message.MessageId of the email
//action_flag is the Gmail special folder to move to (Trash)
//copying to Trash removes the email from the inbox, but it can't be moved back once done, even from the web interface
public static void move_msg_to(string action_id, string action_flag)
{
Imap4Client imap = new Imap4Client();
imap.ConnectSsl("imap.gmail.com", 993);
imap.Login("heythatsme#gmail.com", "heythatsmypassword");
imap.Command("capability");
Mailbox inbox = imap.SelectMailbox("inbox");
int[] ids = inbox.Search("ALL");
if (ids.Length > 0)
{
Message msg = null;
for (var i = 0; i < ids.Length; i++)
{
msg = inbox.Fetch.MessageObject(ids[i]);
if (msg.MessageId == action_id)
{
imap.Command("copy " + ids[i].ToString() + " [Gmail]/" + action_flag);
break;
}
}
}
}
just add 1 to the message index.
foreach (Message newMessage in messages)
{
inbox.DeleteMessage(newMessage.IndexOnServer + 1, true);
}
Try deleting last message first then second last and then come to delete first message.
IF you delete and expunge have been set then message number changes.
Related
I need to move a mail from a list of 5 mails that a class reads. This mail already has been processed by a logical that I created and has met the conditional created. The problem is that it moves those 5 emails and some do not meet the conditions. If the mail that has fulfilled the condition manages to enter the data into the database then it must be moved to the processed mail folder otherwise it must be moved to the error folder.
This is the class that gets the emails
int bufferLength = 5;
int indiceMail = 0;
string from = "mail#gmail.com>";
do
{
emailList.getEmails(bufferLength);
while(indiceMail<emailList.emails.Count)
{
indiceMail++;
}
Console.WriteLine("Reading: {0}", emailList.emails.Count);
}while (emailList.MoreAvailable);
And this is the condition to move the mails
string bodyMail = emailList.emails[indiceMail].body;
match3 = Regex.Match(bodyMail, #"(?<=Status:) (\S+\b)");
statuscompare = match3.Value;
List<String> statusList = new List<string> { "i2", "i3", "i4", "i8" };
bool ex = false;
foreach (string item1 in statusList)
{
if (item1.Contains(statuscompare.Trim()))
{
ex = true;
if (item1.Contains("i4"))
{
bool moveEmail = false;
foreach (Email item in emailList.emails)
{
if (emailList.emails[indiceMail].body.Contains("i4"))
{
// if (item.body.Contains(item1))
//{
moveEmail = true;
emailList.moveMail(item.id, emailList.config.PathSuccess);
break;
// }
}
if (moveEmail)
{
continue;
}
}
}
}
}
This is part of a class to move the mails
public void moveMail(string emailId, string folderPath)
{
string folderId = getMailFolderId(folderPath);
EmailMessage message = EmailMessage.Bind(service, emailId);
message.Move(folderId);
}
Once you process an email with error, you set the flag 'emailbool' to false, which moves every other email to the PathSuccess. Not sure how emailbool actually serves any purpose other than making sure only one email from all goes to error path.
foreach (Email item in emailList.emails)
{
// This IF statement will either be true of false for ALL items.
if (!Cmmd.Parameters["p_retorno"].Value.ToString().Equals("0"))
{
emailList.moveMail(item.id, emailList.config.PathError);
}
else
{
emailList.moveMail(item.id, emailList.config.PathSuccess);
}
}
EDIT: For new code you posted, try this to move all emails that contain any string from statusList in its body.
// This will move all emails that have one or more statusList in their body
foreach (Email item in emailList.emails)
{
if (statusList.Where(x => item.body.Contains(x)).Count > 0)
{
emailList.moveMail(item.id, emailList.config.PathSuccess);
break; // This statement will stop processing of any other emails.
}
}
I'm working writing a script to migrate emails from one account to another. I'm having two issues.
The messages are moving over, but the mail client is showing their
received date as the date/time it was moved (in the list of messages
view), not the date/time that's showing in the message header. I'm
guessing a file date isn't being retained?
The message flags aren't being copied over. Such as the message
being read already. I'd like to see all of the flags being passed...basically I need to move the message as it exists on the previous account.
protected void CopyBtn_Click(object sender, EventArgs e)
{
try
{
ImapClient Client = new ImapClient();
ImapClient Client2 = new ImapClient();
Client.Connect(SourceHostBox.Text.Trim(), 993, SecureSocketOptions.SslOnConnect);
Client.Authenticate(SourceUsernameBox.Text.Trim(), SourcePasswordBox.Text.Trim());
Client.Inbox.Open(FolderAccess.ReadWrite);
Client2.Connect(DestinationHostBox.Text.Trim(), 993, SecureSocketOptions.SslOnConnect);
Client2.Authenticate(DestinationUsernameBox.Text.Trim(), DestinationPasswordBox.Text.Trim());
Client2.Inbox.Open(FolderAccess.ReadWrite);
var folders = Client.GetFolders(Client.PersonalNamespaces[0]);
//move all messages in folders & create folders if necessary
foreach (var folder in folders)
{
folder.Open(FolderAccess.ReadWrite);
var uids = folder.Search(SearchQuery.All);
foreach (var uid in uids)
{
var folders2 = Client2.GetFolders(Client2.PersonalNamespaces[0]);
var message = folder.GetMessage(uid);
string currentFolder = folder.ToString().Replace("INBOX.", ""); //Remove the 'INBOX.' text that's getting prepended by cPanel/Dovecot
var toplevel = Client2.GetFolder(Client2.PersonalNamespaces[0]);
var folderExists = FindFolder(toplevel, currentFolder);
if (folderExists == null)
toplevel.Create(currentFolder, true);
Client2.GetFolder(currentFolder).Append(message);
}
}
//move inbox messages
Client.Inbox.Open(FolderAccess.ReadWrite);
Client2.Inbox.Open(FolderAccess.ReadWrite);
var inboxuids = Client.Inbox.Search(SearchQuery.All);
foreach (var uid in inboxuids)
{
var message = Client.Inbox.GetMessage(uid);
Client2.Inbox.Append(message);
}
label1.Text = "Finished Successfully.";
label1.ForeColor = System.Drawing.Color.Green;
}
catch (Exception ex)
{
label1.Text = ex.Message;
label1.ForeColor = System.Drawing.Color.Red;
}
}
You need to use one of the other Append() methods that takes a MessageFlags argument and a DateTimeOffset argument to specify the timestamp for when the message arrived.
But in order to get that information, you will also need to Fetch() that metadata for each of the messages.
Here's how I would change your loop:
var inboxuids = Client.Inbox.Search(SearchQuery.All);
foreach (var uid in inboxuids)
{
var message = Client.Inbox.GetMessage(uid);
Client2.Inbox.Append(message);
}
Fixed:
var uids = Client.Inbox.Search (SearchQuery.All);
var items = Client.Inbox.Fetch (uids, MessageSummaryItems.InternalDate | MessageSummaryItems.Flags);
foreach (var item in items)
{
var message = Client.Inbox.GetMessage (item.UniqueId);
Client2.Inbox.Append (message, item.Flags.Value, item.InternalDate.Value);
}
Like the OP, I am having the same issue. I used the solution provided by #jstedfast but the destination account still shows the copied email having the date and time of the copy and not the original date and time. Below is my code. Any suggestions would be greatly appreciated!
Edit (12/21/2021):
I figured out the cause of this issue! I had initially tried copying over a bunch of emails without using the flags that retains the date and time. I didn't know that would be required. So I then implemented the changes necessary to use those flags and before running the program again, I deleted all of the original emails that I had copied over from the previous version of my program, but I never emptied the trash in gmail. So although I was copying over the messages again, it must have just used the original copied messages and just removed the deleted flag. Once I emptied the gmail trash and ran my program again, the emails copied over correctly while retaining the original received date.
private void CopyEmailsAndFolders()
{
try
{
ImapClient yahooEmailAccnt = connectToEmailServerAndAccount(SrcEmailAccount.ImapServer, SrcEmailAccount.LogFileName, SrcEmailAccount.AccountUserName, SrcEmailAccount.AccountPassword);
ImapClient gmailEmailAccnt = connectToEmailServerAndAccount(DestEmailAccount.ImapServer, DestEmailAccount.LogFileName, DestEmailAccount.AccountUserName, DestEmailAccount.AccountPassword);
try
{
List<string> yahooFoldersList = getFoldersList(yahooEmailAccnt);
//loop through each of the folders in the source (Yahoo) email account
foreach (string folderName in yahooFoldersList)
{
if (folderName.ToLower() == "inbox")
{
continue;
}
var gmailFolder = gmailEmailAccnt.GetFolder(folderName);
var yahooFolder = yahooEmailAccnt.GetFolder(folderName);
yahooFolder.Open(FolderAccess.ReadOnly);
//get a list of all email UID's
var uids = yahooFolder.Search(SearchQuery.All);
//now get a list of all the items in the folder to include the critical date time properties we want to preserve
var items = yahooFolder.Fetch(uids, MessageSummaryItems.InternalDate | MessageSummaryItems.Flags);
//now loop through all of the emails we found in the current folder and copy them to the destination (gmail) email account
foreach (var item in items)
{
var message = yahooFolder.GetMessage(item.UniqueId);
gmailFolder.Append(message, item.Flags.Value, item.InternalDate.Value);
}
}
}
catch (Exception fEx)
{
MessageBox.Show(fEx.Message);
}
finally
{
yahooEmailAccnt.Disconnect(true);
gmailEmailAccnt.Disconnect(true);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private ImapClient connectToEmailServerAndAccount(string imapServer, string logFile, string userName, string password)
{
ImapClient newEmailClient = new ImapClient();
try
{
newEmailClient = new ImapClient(new ProtocolLogger(logFile));
newEmailClient.Connect(imapServer, 993, SecureSocketOptions.SslOnConnect);
newEmailClient.Authenticate(userName, password);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return newEmailClient;
}
private List<string> getFoldersList(ImapClient emailClient)
{
List<string> foldersList = new List<string>();
try
{
// Get the first personal namespace and list the toplevel folders under it.
var personal = emailClient.GetFolder(emailClient.PersonalNamespaces[0]);
foreach (var folder in personal.GetSubfolders(false))
{
foldersList.Add(folder.Name);
Console.WriteLine("[folder] {0}", folder.Name);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return foldersList;
}
The title sums it up. I need to get all the MessageId properties from an Imap folder without downloading the entire message.
...
IMailFolder inbox = imapClient.Inbox;
SearchQuery query = SearchQuery.All;
IList<UniqueId> idsList = inbox.Search(query);
foreach (var uid in idsList)
{
MimeMessage message = inbox.GetMessage(uid);//This gets the entire message
//but I only need the .MessageId, without getting the other parts
if (message != null)
{
string messageId = message.MessageId;
}
}
...
Try this instead:
var summaries = client.Inbox.Fetch (0, -1, MessageSummaryItems.Envelope);
foreach (var message in summaries) {
Console.WriteLine (message.Envelope.MessageId);
}
That should get you what you want.
I am trying to download unseen emails from pop server.I am trying this code(copied from official site of openPop.net)
public static List<OpenPop.Mime.Message> FetchUnseenMessages(string hostname, int port, bool useSsl, string username, string password, List<string> seenUids)
{
// The client disconnects from the server when being disposed
using (Pop3Client client = new Pop3Client())
{
// Connect to the server
client.Connect(hostname, port, useSsl);
// Authenticate ourselves towards the server
client.Authenticate(username, password,AuthenticationMethod.UsernameAndPassword);
// Fetch all the current uids seen
List<string> uids = client.GetMessageUids();
// Create a list we can return with all new messages
List<OpenPop.Mime.Message> newMessages = new List<OpenPop.Mime.Message>();
// All the new messages not seen by the POP3 client
for (int i = 0; i < uids.Count; i++)
{
string currentUidOnServer = uids[i];
if (!seenUids.Contains(currentUidOnServer))
{
// We have not seen this message before.
// Download it and add this new uid to seen uids
// the uids list is in messageNumber order - meaning that the first
// uid in the list has messageNumber of 1, and the second has
// messageNumber 2. Therefore we can fetch the message using
// i + 1 since messageNumber should be in range [1, messageCount]
OpenPop.Mime.Message unseenMessage = client.GetMessage(i + 1);
// Add the message to the new messages
newMessages.Add(unseenMessage);
// Add the uid to the seen uids, as it has now been seen
seenUids.Add(currentUidOnServer);
}
}
// Return our new found messages
//client.Disconnect();
return newMessages;
}
}
When i debugged this code i found that code is stopping at
OpenPop.Mime.Message unseenMessage = client.GetMessage(i + 1);
And after that no debug buttons work. Even i press F10 then it does nothing.
If i dont debug code let the application run then it keeps running and never comes out of loop.
What i am doing wrong here?Thanks
I was wondering if there is a way to programmatically check how many messages are in a private or public MSMQ using C#? I have code that checks if a queue is empty or not using the peek method wrapped in a try/catch, but I've never seen anything about showing the number of messages in the queue. This would be very helpful for monitoring if a queue is getting backed up.
You can read the Performance Counter value for the queue directly from .NET:
using System.Diagnostics;
// ...
var queueCounter = new PerformanceCounter(
"MSMQ Queue",
"Messages in Queue",
#"machinename\private$\testqueue2");
Console.WriteLine( "Queue contains {0} messages",
queueCounter.NextValue().ToString());
There is no API available, but you can use GetMessageEnumerator2 which is fast enough. Sample:
MessageQueue q = new MessageQueue(...);
int count = q.Count();
Implementation
public static class MsmqEx
{
public static int Count(this MessageQueue queue)
{
int count = 0;
var enumerator = queue.GetMessageEnumerator2();
while (enumerator.MoveNext())
count++;
return count;
}
}
I also tried other options, but each has some downsides
Performance counter may throw exception "Instance '...' does not exist in the specified Category."
Reading all messages and then taking count is really slow, it also removes the messages from queue
There seems to be a problem with Peek method which throws an exception
If you need a fast method (25k calls/second on my box), I recommend Ayende's version based on MQMgmtGetInfo() and PROPID_MGMT_QUEUE_MESSAGE_COUNT:
for C#
https://github.com/hibernating-rhinos/rhino-esb/blob/master/Rhino.ServiceBus/Msmq/MsmqExtensions.cs
for VB
https://gist.github.com/Lercher/5e1af6a2ba193b38be29
The origin was probably http://functionalflow.co.uk/blog/2008/08/27/counting-the-number-of-messages-in-a-message-queue-in/ but I'm not convinced that this implementation from 2008 works any more.
We use the MSMQ Interop. Depending on your needs you can probably simplify this:
public int? CountQueue(MessageQueue queue, bool isPrivate)
{
int? Result = null;
try
{
//MSMQ.MSMQManagement mgmt = new MSMQ.MSMQManagement();
var mgmt = new MSMQ.MSMQManagementClass();
try
{
String host = queue.MachineName;
Object hostObject = (Object)host;
String pathName = (isPrivate) ? queue.FormatName : null;
Object pathNameObject = (Object)pathName;
String formatName = (isPrivate) ? null : queue.Path;
Object formatNameObject = (Object)formatName;
mgmt.Init(ref hostObject, ref formatNameObject, ref pathNameObject);
Result = mgmt.MessageCount;
}
finally
{
mgmt = null;
}
}
catch (Exception exc)
{
if (!exc.Message.Equals("Exception from HRESULT: 0xC00E0004", StringComparison.InvariantCultureIgnoreCase))
{
if (log.IsErrorEnabled) { log.Error("Error in CountQueue(). Queue was [" + queue.MachineName + "\\" + queue.QueueName + "]", exc); }
}
Result = null;
}
return Result;
}
//here queue is msmq queue which you have to find count.
int index = 0;
MSMQManagement msmq = new MSMQManagement() ;
object machine = queue.MachineName;
object path = null;
object formate=queue.FormatName;
msmq.Init(ref machine, ref path,ref formate);
long count = msmq.MessageCount();
This is faster than you selected one.
You get MSMQManagement class refferance inside "C:\Program Files (x86)\Microsoft SDKs\Windows" just brows in this address you will get it. for more details you can visit http://msdn.microsoft.com/en-us/library/ms711378%28VS.85%29.aspx.
I had real trouble getting the accepted answer working because of the xxx does not exist in the specified Category error. None of the solutions above worked for me.
However, simply specifying the machine name as below seems to fix it.
private long GetQueueCount()
{
try
{
var queueCounter = new PerformanceCounter("MSMQ Queue", "Messages in Queue", #"machineName\private$\stream")
{
MachineName = "machineName"
};
return (long)queueCounter.NextValue();
}
catch (Exception e)
{
return 0;
}
}
The fastest method I have found to retrieve a message queue count is to use the peek method from the following site:
protected Message PeekWithoutTimeout(MessageQueue q, Cursor cursor, PeekAction action)
{
Message ret = null;
try
{
ret = q.Peek(new TimeSpan(1), cursor, action);
}
catch (MessageQueueException mqe)
{
if (!mqe.Message.ToLower().Contains("timeout"))
{
throw;
}
}
return ret;
}
protected int GetMessageCount(MessageQueue q)
{
int count = 0;
Cursor cursor = q.CreateCursor();
Message m = PeekWithoutTimeout(q, cursor, PeekAction.Current);
{
count = 1;
while ((m = PeekWithoutTimeout(q, cursor, PeekAction.Next)) != null)
{
count++;
}
}
return count;
}
This worked for me. Using a Enumarator to make sure the queue is empty first.
Dim qMsg As Message ' instance of the message to be picked
Dim privateQ As New MessageQueue(svrName & "\Private$\" & svrQName) 'variable svrnme = server name ; svrQName = Server Queue Name
privateQ.Formatter = New XmlMessageFormatter(New Type() {GetType(String)}) 'Formating the message to be readable the body tyep
Dim t As MessageEnumerator 'declared a enumarater to enable to count the queue
t = privateQ.GetMessageEnumerator2() 'counts the queues
If t.MoveNext() = True Then 'check whether the queue is empty before reading message. otherwise it will wait forever
qMsg = privateQ.Receive
Return qMsg.Body.ToString
End If
If you want a Count of a private queue, you can do this using WMI.
This is the code for this:
// You can change this query to a more specific queue name or to get all queues
private const string WmiQuery = #"SELECT Name,MessagesinQueue FROM Win32_PerfRawdata_MSMQ_MSMQQueue WHERE Name LIKE 'private%myqueue'";
public int GetCount()
{
using (ManagementObjectSearcher wmiSearch = new ManagementObjectSearcher(WmiQuery))
{
ManagementObjectCollection wmiCollection = wmiSearch.Get();
foreach (ManagementBaseObject wmiObject in wmiCollection)
{
foreach (PropertyData wmiProperty in wmiObject.Properties)
{
if (wmiProperty.Name.Equals("MessagesinQueue", StringComparison.InvariantCultureIgnoreCase))
{
return int.Parse(wmiProperty.Value.ToString());
}
}
}
}
}
Thanks to the Microsoft.Windows.Compatibility package this also works in netcore/netstandard.
The message count in the queue can be found using the following code.
MessageQueue messageQueue = new MessageQueue(".\\private$\\TestQueue");
var noOFMessages = messageQueue.GetAllMessages().LongCount();