I have created an Outlook 2007 add-in in C#.NET 4.0.
I want to read the safe sender list in my C# code.
if (oBoxItem is Outlook.MailItem)
{
Outlook.MailItem miEmail = (Outlook.MailItem)oBoxItem;
OlDefaultFolders f = Outlook.OlDefaultFolders.olFolderContacts;
if (miEmail != null)
{
string body = miEmail.Body;
double score = spamFilterObject.CalculateSpamScore(body);
if (score <= 0.9)
{
miEmail.Move(mfJunkEmail);
}
}
}
So, the above code moves all email to spam, even though they are present in the safe sender list. Thus I want to get the safe sender list so that I can avoid this spam checking.
Could anybody please help me on this?
The Outlook object model doesn't expose these lists (for more or less obvious reasons). The safe sender list can be read straight from the registry at:
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\[PROFILE NAME]\0a0d020000000000c000000000000046\001f0418
This binary registry key contains double-byte characters, separated by a semicolon (;).
The MAPI property mapping onto this registry key is
PR_SPAM_TRUSTED_SENDERS_W, documented here.
Chavan, I assume since this hasn't been updated in over 4 years, you don't need any more information, but this question and the answer helped me find what I was looking for (it was very hard to find) and enabled me to write the code below that may help if you're still looking for an answer.
This code runs in LINQPad, so if you aren't a LINQPad user, remove the .Dump() methods and replace with Console.WriteLine or Debug.WriteLine.
Cheers!
const string valueNameBlocked = "001f0426";
const string valueNameSafe = "001f0418";
// Note: I'm using Office 2013 (15.0) and my profile name is "Outlook"
// You may need to replace the 15.0 or the "Outlook" at the end of your string as needed.
string keyPath = #"Software\Microsoft\Office\15.0\Outlook\Profiles\Outlook";
string subKey = null;
var emptyBytes = new byte[] { };
var semi = new[] { ';' };
string blocked = null, safe = null;
// I found that my subkey under the profile was not the same on different machines,
// so I wrote this block to look for it.
using (var key = Registry.CurrentUser.OpenSubKey(keyPath))
{
var match =
// Get the subkeys and all of their value names
key.GetSubKeyNames().SelectMany(sk =>
{
using (var subkey = key.OpenSubKey(sk))
return subkey.GetValueNames().Select(valueName => new { subkey = sk, valueName });
})
// But only the one that matches Blocked Senders
.FirstOrDefault(sk => valueNameBlocked == sk.valueName);
// If we got one, get the data from the values
if (match != null)
{
// Simultaneously setting subKey string for later while opening the registry key
using (var subkey = key.OpenSubKey(subKey = match.subkey))
{
blocked = Encoding.Unicode.GetString((byte[])subkey.GetValue(valueNameBlocked, emptyBytes));
safe = Encoding.Unicode.GetString((byte[])subkey.GetValue(valueNameSafe, emptyBytes));
}
}
}
// Remove empty items and the null-terminator (sometimes there is one, but not always)
Func<string, List<string>> cleanList = s => s.Split(semi, StringSplitOptions.RemoveEmptyEntries).Where(e => e != "\0").ToList();
// Convert strings to lists (dictionaries might be preferred)
var blockedList = cleanList(blocked).Dump("Blocked Senders");
var safeList = cleanList(safe).Dump("Safe Senders");
byte[] bytes;
// To convert a modified list back to a string for saving:
blocked = string.Join(";", blockedList) + ";\0";
bytes = Encoding.Unicode.GetBytes(blocked);
// Write to the registry
using (var key = Registry.CurrentUser.OpenSubKey(keyPath + '\\' + subKey, true))
key.SetValue(valueNameBlocked, bytes, RegistryValueKind.Binary);
// In LINQPad, this is what I used to view my binary data
string.Join("", bytes.Select(b => b.ToString("x2"))).Dump("Blocked Senders: binary data");
safe = string.Join(";", safeList) + ";\0"; bytes = Encoding.Unicode.GetBytes(safe);
string.Join("", bytes.Select(b => b.ToString("x2"))).Dump("Safe Senders: binary data");
PST and IMAP4 (ost) stores keep the list in the profile section in the registry. Profile section guid is {00020D0A-0000-0000-C000-000000000046}. To access the data directly, you will need to know the Outlook version and the profile name.
Exchange store keeps this data as a part of the server side rule that processes incoming messages on the server side. You can see the rule data in OutlookSpy (I am its author) - go to the Inbox folder, "Associated Contents" tab, find the entry named (PR_RuleMsgName) == "Junk E-mail Rule", double click on it, take a look at the PR_EXTENDED_RULE_CONDITION property.
Outlook Object Model does not expose Junk mail settings. If using Redemption (I am also its author) is an option, it exposes the RDOJunkEmailOptions.TrustedSenders collection (works both for the PST and Exchange stores):
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set Store = Session.Stores.DefaultStore
set TrustedSenders = Store.JunkEmailOptions.TrustedSenders
for each v in TrustedSenders
debug.print v
next
Related
I'm trying to assign a value to the custom Active Directory attribute msExchRecipientTypeDetails for a user account as below, but it's resulting in an "unspecified error" COM exception. I'm not seeing this problem with other custom attributes (including others related to MS Exchange), nor with small values (e.g., 1) on the right side of the assignment. It doesn't seem to be security-related. How do I resolve this?
using (DirectoryEntry userEntry = result.GetDirectoryEntry())
{
userEntry.Properties["msExchRecipientTypeDetails"].Value = 2147483648;
userEntry.CommitChanges();
}
The msExchRecipientTypeDetails attribute is of type IADsLargeInteger, which is AD's way of storing long (64-bit) integers. The number must be converted to IADsLargeInteger before being assigned.
This does the conversion:
using ActiveDs;
private IADsLargeInteger LongToAdLong(long inputValue)
{
IADsLargeInteger outputValue = new LargeInteger(); // 64-bit
outputValue.HighPart = (int)(inputValue >> 32); // 32-bit
outputValue.LowPart = (int)(inputValue & 0xFFFFFFFF); // 32-bit
return outputValue;
}
Using that, you can now assign the value to the attribute:
using (DirectoryEntry userEntry = result.GetDirectoryEntry())
{
var mailboxTypeValue = LongToAdLong(2147483648);
userEntry.Properties["msExchRecipientTypeDetails"].Value = mailboxTypeValue;
userEntry.CommitChanges();
}
As an aside, if you are directly assigning values to AD Exchange attributes as part of a process to provision mailboxes for new users, don't do this as it is unsupported and will result in email issues, some of which may not be obvious. A better way would be to use the PowerShell API to call into an appropriate PowerShell command, such as Enable-RemoteMailbox.
I know that I can read the Security logs of a Windows PC using:
var securityLog = new EventLog("security");
foreach (EventLogEntry entry in securityLog.Entries) {
...
}
The entry item contains all the interesting log fields I expect to see like: InstanceId, Message and others. What I want to do now is read the same things from an event log that was saved to disk as an .evtx file.
I have seen suggestions for using
string xpathQuery = "*";
var eventsQuery = args.Length == 0
? new EventLogQuery("Security", PathType.LogName, xpathQuery)
: new EventLogQuery(args[0], PathType.FilePath, xpathQuery);
using (var eventLogReader = new EventLogReader(eventsQuery)) {
EventLogRecord entry;
while ((entry = (EventLogRecord) eventLogReader.ReadEvent()) != null) {
...
}
}
but the entry in the second version doesn't contain the same members/values as the first example. I totally dig that I am confused and am looking at the problem the wrong way.
How should one go about reading the actual per record content from either an active or saved system log?
Or, can I go from an EventLogRecord to an EventLogEntry? I have not seen that conversion method yet.
Hello and thank you for you time on this question, I'm trying change a Active Printer according to the choice that user chooses in excel. However I'm having some trouble. For some reason it keeps giving me the same error.
"An exception of type 'System.Runtime.InteropServices.COM Exception'
occurred in DailyReport.dll but was not handled in user code Exception
from HRESULT:0X800A03EC"
I've been goggling this error and im having a hard time finding anything, I did find a link COM Exception and they provided a link to another website but seems when I try to go that site it doesn't open.
I have Tried:
xlApp.ActivePrinter = "CORPPRT58-Copier Room on RR-PS1:";
xlApp.ActivePrinter = "\\RR-PS1\CORPPRT58-Copier Room";
xlApp.ActivePrinter = "CORPPRT58-Copier Room on RR-PS1";
I have checked to make sure that the printer is installed and it is. if someone could point me in the correct direction that would be great thanks!!
The correct answer is (i.e.):
xlApp.ActivePrinter = "\\\\RR-PS1\\CORPPRT58-Copier Room on Ne00:";
An important part is the 'Ne00:' This is the port on which this printer can be found. This is different on each computer and can be found in registry at HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Devices.
Another problem is the concatenation string 'on'. This may be valid when working with an English excel but it's translated to other languages!
I had the same problem but there aren't many complete examples I could find so here is mine:
// Open excel document
var path = #"c:\path\to\my\doc.xlsx";
Microsoft.Office.Interop.Excel.Application _xlApp;
Microsoft.Office.Interop.Excel.Workbook _xlBook;
_xlApp = new Microsoft.Office.Interop.Excel.Application();
_xlBook = _xlApp.Workbooks.Open(path);
_xlBook.Activate();
var printer = #"EPSON LQ-690 ESC/P2";
var port = String.Empty;
// Find correct printerport
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(path))
{
if (key != null)
{
object value = key.GetValue(printer);
if (value != null)
{
string[] values = value.ToString().Split(',');
if (values.Length >= 2) port = values[1];
}
}
}
// Set ActivePrinter if not already set
if (!_xlApp.ActivePrinter.StartsWith(printer))
{
// Get current concatenation string ('on' in enlgish, 'op' in dutch, etc..)
var split = _xlApp.ActivePrinter.Split(' ');
if (split.Length >= 3)
{
_xlApp.ActivePrinter = String.Format("{0} {1} {2}",
printer,
split[split.Length - 2],
port);
}
}
// Print document
_xlBook.PrintOutEx();
It's far for perfect since I'm not aware of any other translations. If 'on' is translated with spaces, above will fail. But i'm guessing that the solution will work for most clients. You can easily get the current concatenation string by looking at the currect value of ActivePrinter.
A more failproof method would be to strip the printer name and the assigned port and what remains is the concatenation string. But then you would have to loop through all installed printers and check for a match.
Another test I personally do it check if the printer is installed on the system:
if(PrinterSettings.InstalledPrinters.Cast<string>().ToList().Contains(printer)) {
//Set active printer...
}
First set your target printer as the default printer on the control pannel. Then print xlApp.ActivePrinter and get its settings(if you don't set the activeprinter the application gets the default setting of windows). At last set the value with the print value. And you can change your default printer setting.
I have an application where I need to print a ticket. Each ticket must be unique. The application is windows forms and written entirely in c#. For our application we're using Samsung ML- 2525 laser monochromatic printers.
The flow is basically the following, the operator picks a product/ticket (which is unique) and then it presses a button that does 2 things:
Connects to a database and updates the product as used
Prints the ticket (this is done using System.Drawing and GDI+)
For some reason, every once in a while, the image that needs to be printed is not sent to the printer. It's a rare case, but it happens.
I tried to connect to the printer using Win32_Printer ( http://msdn.microsoft.com/en-us/library/Aa394363 ) but I can't get to get the current printer's state (online, offline, low toner, paper jam, etc). I can only check if the printer exists and that the paper size is installed correctly. I tried code similar to the following but it didn't work
private string MonitorPrintJobWmi()
{
var jobMessage = String.Empty;
var scope = new ManagementScope(ManagementPath.DefaultPath);
scope.Connect();
var selectQuery = new SelectQuery { QueryString = #"select * from Win32_PrintJob" };
var objSearcher = new ManagementObjectSearcher(scope, selectQuery);
var objCollection = objSearcher.Get();
foreach (var job in objCollection)
{
if (job != null)
{
jobMessage += String.Format("{0} \r\n", job["Name"].ToString());
jobMessage += String.Format("{0} \r\n", job["JobId"].ToString());
_jobId = Convert.ToInt32(job["JobId"]);
jobMessage += String.Format("{0} \r\n", job["JobStatus"].ToString());
jobMessage += String.Format("{0} \r\n", job["Status"].ToString());
}
}
return jobMessage;
}
I tried to get an API for the printer but I couldn't get a hold of it. By the way, the printer's software do indicate different errors in the windows toolbar.
My question is if anyone can lead me in the right direction as to how to connect to a printer and check if printing was successful.
Also, it would be helpful if someone know of some other specific printer in which I may accomplish this ie, changing hardware.
Thanks,
To get a list of print queues on the local machine, try PrintServer's GetPrintQueues method.
Once you have an instance of the PrintQueue object associated with the relevant printer, you can use it to access the printer's status (IsOffline, IsPaperOut, etc.). Also, you can use it to get a list of the jobs in the given queue (GetPrintJobInfoCollection) which then will allow you to get job-specific status information (IsInError, IsCompleted, IsBlocked, etc.).
Hope this helps!
After try to print your PrintDocument (System.Drawing.Printing), try to check status of printjobs.
First step: Initialize your printDocument.
Second step: Get your printer Name From System.Drawing.Printing.PrinterSettings.InstalledPrinters.Cast<string>();
And copy it into your printerDocument.PrinterSettings.PrinterName
Third step: Try to print and dispose.
printerDocument.Print();
printerDocument.Dispose();
Last step: Run the check in a Task (do NOT block UI thread).
Task.Run(()=>{
if (!IsPrinterOk(printerDocument.PrinterSettings.PrinterName,checkTimeInMillisec))
{
// failed printing, do something...
}
});
Here is the implementation:
private bool IsPrinterOk(string name,int checkTimeInMillisec)
{
System.Collections.IList value = null;
do
{
//checkTimeInMillisec should be between 2000 and 5000
System.Threading.Thread.Sleep(checkTimeInMillisec);
// or use Timer with Threading.Monitor instead of thread sleep
using (System.Management.ManagementObjectSearcher searcher = new System.Management.ManagementObjectSearcher("SELECT * FROM Win32_PrintJob WHERE Name like '%" + name + "%'"))
{
value = null;
if (searcher.Get().Count == 0) // Number of pending document.
return true; // return because we haven't got any pending document.
else
{
foreach (System.Management.ManagementObject printer in searcher.Get())
{
value = printer.Properties.Cast<System.Management.PropertyData>().Where(p => p.Name.Equals("Status")).Select(p => p.Value).ToList();
break;
}
}
}
}
while (value.Contains("Printing") || value.Contains("UNKNOWN") || value.Contains("OK"));
return value.Contains("Error") ? false : true;
}
Good luck.
I need to find a way to check if an Active Directory UserAccount has his account locked or not.
I've tried userAccountControl property in a Windows 2000 AD but that property does not change a byte when I force an account to get locked (by trying to log on to a workstation providing the wrong password for that specific user) And I can tell by using ADExplorer.exe utility made by semi-god -> Mr. Russinovich
I've seen that in the 3.5 Framework they use the method .InvokeGet("userLockedOut"); but I'm trying to do this in a Enterprise Application that was written in .Net Framework 1.1 and there's no chance of using newer ones (just if you thought of suggesting so).
Here is a link with all the info on Active Directory stuff...
http://www.codeproject.com/KB/system/everythingInAD.aspx
Found this, it is a little more than I have done in the past (can't find exact snippets) though the key is doing a directory search and limiting based on the lockouttime for your user(s) that are returned. Additionally for a particular user, you can limit your search further using additional properties. The codeproject link above has that particular logic (for search limiting) I believe.
class Lockout : IDisposable
{
DirectoryContext context;
DirectoryEntry root;
DomainPolicy policy;
public Lockout(string domainName)
{
this.context = new DirectoryContext(
DirectoryContextType.Domain,
domainName
);
//get our current domain policy
Domain domain = Domain.GetDomain(this.context);
this.root = domain.GetDirectoryEntry();
this.policy = new DomainPolicy(this.root);
}
public void FindLockedAccounts()
{
//default for when accounts stay locked indefinitely
string qry = "(lockoutTime>=1)";
TimeSpan duration = this.policy.LockoutDuration;
if (duration != TimeSpan.MaxValue)
{
DateTime lockoutThreshold =
DateTime.Now.Subtract(duration);
qry = String.Format(
"(lockoutTime>={0})",
lockoutThreshold.ToFileTime()
);
}
DirectorySearcher ds = new DirectorySearcher(
this.root,
qry
);
using (SearchResultCollection src = ds.FindAll())
{
foreach (SearchResult sr in src)
{
long ticks =
(long)sr.Properties["lockoutTime"][0];
Console.WriteLine(
"{0} locked out at {1}",
sr.Properties["name"][0],
DateTime.FromFileTime(ticks)
);
}
}
}
public void Dispose()
{
if (this.root != null)
{
this.root.Dispose();
}
}
}
Code was pulled from this post: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/5e0fadc2-f27b-48f6-a6ac-644e12256c67/
After seeing the .NET 1.1, check this thread out: http://forums.asp.net/t/434077.aspx, using the lockouttime in the filter should still do the trick.
Specifically in the thread (after the larger code post which provides alot of the syntax):
(&(objectClass=user)(objectCategory=person)(lockoutTime>=1));
One other thing, it turns out that if you are using .NET v.1.1, then S.DS converts the Integer8 to the long integer correctly for you (does not work with 1.0) - which means you can do away with reflection code (in the post):
//use the filter from above
SearchResultCollection src = ds.FindAll();
foreach(SearchResult sr in src)
{
DateTime lockoutTime = DateTime.FromFileTime((long)sr.Properties["lockoutTime][0]);
Response.Output.Write("Locked Out on: {0}", lockoutTime.ToString());
}