I'm having a similar problem to this question in getting a pair of "custom" MAPI properties from a MailItem in a C# console app, but with the extra twist that it works locally but not on the server.
The two properties are PR_SERVERID and PR_MSGID and both are available on the mailitems when inspected via MFCMAPI, I'm accessing them as follows:
MessageDetails.PR_SERVERID = Message.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/PR_SERVERID").ToString();
MessageDetails.PR_MSG_ID = Message.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/PR_MSGID").ToString();
Where Message is of type Outlook.MailItem. When I run this locally (from built exe or in debug) it works but on a separate server the exact same message fails with:
15:17:26 Error getting getting MAPI properties from the stub:
System.ApplicationException: System.Runtime.InteropServices.COMException (0x8004010F):
The property "http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/PR_SERVERID" is unknown or cannot be found.
Before this, I am successfully retrieving two "standard" MAPI properties (PR_CREATOR_NAME and PR_INTERNET_MESSAGE_ID) from the same item so the MailItem is accessible.
The DASL for the properties shown in MFCMAPI is identical between the two servers and my local machine and both are running Outlook 2010 and accessing the mailItem by invoking an Outlook session.
The only thing I could think of was that Outlook itself is unable to recognise the custom properties without some configuration that's missing on the remote server(s), does Outlook have to have a property configured at the namespace level for it to be recognised even if identified by a DASL?
Edit: Additional information on the mail items:
The mails are "stubs" downloaded from an archive system, the two attributes then provide the identifier for the "full" message content. I'm downloading the stub from the remote server, saving it locally and then accessing each stub in the set using:
Outlook.NameSpace NS = _outlookApplication.GetNamespace("MAPI");
Object item = _outlookApplication.Session.OpenSharedItem(MessageDetails.FilePath)
Outlook.MailItem Message = (Outlook.MailItem)item;
And attempting to add the two properties to the MessageDetails object by getting the properties from Message.
Edit 2:
I've noticed that when retrieving the properties on a "fresh" host, I get a security prompt for "a program is trying to access e-mail address information stored in Outlook" when I access the custom properties but not when I access the subjectline/sender etc. from the standard properties. The seems to indicate that there is something about these custom properties that is "different" when accessed via COM interop using outlook. Nevermind, this is simply default protection on calling PropertyAccessor.GetProperty.
Answer:
Dmitry is spot on and I have marked his response as the answer, the properties were not accessible because they had never been created on the store. With user machines these have been created because at some point we "archived" mails into the remote store and set the custom properties on the stubs left in our mail boxes. On the servers this had never been done so I added an option for a "first run" that creates a dummy object, sets two empty custom properties on it, saves it then deletes it. Subsequent runs are then able to access the properties using PropertyAccessor.GetProperty.
Outlook Object Model always assumes PT_UNICODE / PT_STRING8 type for the properties in the "string" namespace. The type in your case is PT_LONG (0x0003).
As a test, can you install Redemption (I am its author) and try the following script from OutlookSpy (I am also its author - click Script, paste the script, click Run)
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set Msg = Session.GetMessageFromMsgFile("c:\temp\yourmsg.msg")
MsgBox Msg.Fields("http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/PR_SERVERID/0x00000003")
UPDATE
Try the following script in OutlookSpy on a machine where your code fails. When the IMessage window pops up, do you see the named properties?
set msg = Application.Session.OpenSharedItem("c:\temp\yourmsg.msg")
BrowseObject(msg.MAPIOBJECT)
I would guess that the interface defined by {00020329-0000-0000-C000-000000000046} is not installed on the server. Search for it (e.g. using Regedit) on your machine where it works, and note what dll it is associated with (this will be in the InprocServer32 sub key as the default property. Then do the same on the server: it will probably not find the CLSID at all.
If this is the case, then you need to install the missing dll - and all its dependencies which means the package it was wrapped up in.
Related
Developing for: Outlook 2016
Add-In: VSTO (C#)
My company is running on O365 (Exchange) and I am developing an add-in which collects their exchange user information and does various things. However, it appears that when the Outlook Account Settings is configured with Cached Exchange Mode enabled the GetExchangeUser() returns null.
If I disable the clients Cached Exchange Mode everything works fine. However my company wants to keep this feature enabled.
My rough understanding is that GetExchangeUser() only works when connected to the Exchange Server. I suspect that the Cached mode causes this not to be the case all the time and therefore the method fails. So I'm wondering ..
How can I force (temporarily) Outlook to connect to Exchange so that GetExchangeUser() works?
Are there any alternative ways of collecting the Exchange user information?
// Create a singleton of the Application instance.
Outlook.Application app = new Outlook.Application();
// Get the current user object.
Outlook.ExchangeUser currentUser = app.Session.CurrentUser.AddressEntry.GetExchangeUser();
// ***** currentUser == null when "Use Cached Exchange Mode" is enabled.
// ***** currentUser == Outlook.ExchangeUser object when "Use Cached Exchange Mode" is disabled.
// Set the form details.
textBoxName.Text = currentUser.Name;
textBoxEmployeeID.Text = currentUser.Alias;
I have tried a number of suggestions I've found online, none of them have worked. Such as ..
Force a Offline Address book update (Doesn't work).
Delete the old Offline Address Book then force an update (Doesn't work).
Keep in mind that ExchangeUser object (returned from AddressEntry.GetExchangeUser()) does not expose anything you cannot get from AddressEntry.PropertyAccessort.GetProperty().
Verify that the data is actually there - you can do that from OutlookSpy (I am its author): click Namespace button on the OutlookSpy ribbon. Expand CurrentUser property, expand AddressEntry, select MAPIOBJECT property, click "Browse". In the IMailUser window, do you see all the MAPI properties that you need? If you select a property, OutlookSpy will show its DASL name. You can use that DASL property name when calling AddressEntry.PropertyAccessort.GetProperty().
My earlier problem about catching an email message just after it has sent was resolved by using the Items.ItemAdd event handler of the Sent Items folder. This works fine when I don't change the sender mailbox. But if I change it by selecting some other account from the dropdown list of the sender on the interface shown by mailItem.Display(true); , then the sent message lands in the "sent items" folder of this other account, but
Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail);
doesn't change accordingly, so in this way I can't catch the message. How could I get the "sent items" folder of the selected (not default) sender?
(an acceptable workaround would be to change the default mailbox, but I also don't know how to do this).
The GetDefaultFolder method of the Store class returns a Folder object that represents the default folder in the store and that is of the type specified by the FolderType argument. This method is similar to the GetDefaultFolder method of the NameSpace object. The difference is that this method gets the default folder on the delivery store that is associated with the account, whereas NameSpace.GetDefaultFolder returns the default folder on the default store for the current profile.
You can to handle the ItemSend event of the Application class where you can get the sender. Then you can find associated store and use the GetDefaultFolder method to get the correct Sent Items folder.
Be aware, the SaveSentMessageFolder property of the MailItem class returns or sets a Folder object that represents the folder in which a copy of the e-mail message will be saved after being sent (instead of the Sent Items folder).
I have been trying to use IConverterSession to convert from EML to MSG (MIME to MAPI), but I keep stumbling on COM errors.
I use a C# MAPIMethods class to wrap IConverterSession (like the one found here : Save Mail in MIME format (*.eml) in Outlook Add-In).
First, I had the problems of unknown clsid, solved with this post (https://blogs.msdn.microsoft.com/stephen_griffin/2014/04/21/outlook-2013-click-to-run-and-com-interfaces/).
Now that the proper registry keys have been edited, I face a new problem: first, I get an error message The operating system is not presently configured to run this application, and I get a COMException: Retrieving the COM class factory for component with CLSID {4E3A7680-B77A-11D0-9DA5-00C04FD65685} failed due to the following error: 8007013d The system cannot find message text for message number 0x in the message file for . (Exception from HRESULT: 0x8007013D).
My code is :
Type converter = Type.GetTypeFromCLSID(MAPIMethods.CLSID_IConverterSession, true);
object obj = Activator.CreateInstance(converter);
MAPIMethods.IConverterSession session = (MAPIMethods.IConverterSession)obj;
The error is raised on "object obj = Activator.CreateInstance(converter);"
A COMException normally means that "type is a COM object but the class identifier used to obtain the type is invalid, or the identified class is not registered.". So either Type converter = Type.GetTypeFromCLSID(MAPIMethods.CLSID_IConverterSession, true); does not return the proper type, or there is still a registry key missing somewhere.
I am using Office 15 (2013) C2R 32 bits on Win 64bits. The application is setup on an x86 build configuration.
Is there something I am missing somewhere? Can anyone help?
"The operating system is not presently configured to run this application" - it sure sounds like your app is compiled as x64 on a machine with a 32 bit version of Outlook.
Have you tried to use Redemption? It wraps IConverterSession for .Net languages. Something like the following should do the job.
using Redemption;
...
Redemmption.RDOSession session = new Redemmption.RDOSession();
Redemmption.RDOMail msg = session.CreateMessageFromMsgFile(#"c:\temp\test.msg");
msg.Import(#"c:\temp\test.eml", Redemption.rdoSaveAsType.olRFC822);
msg.Save();
olRFC822 format will use IConverterSession if it is available or the internal Redemption converter if IConverterSesison is not available (e.g. under Exchange version of MAPI or the latest versions of Outlook 2016 C2R where IConverterSession cannot be used).
Use olRFC822_Redemption or olRFC822_Outlook if you always want to force Redemption or Outlook (IConverterSession) converter.
I'm developing an open source .NET assembly (WinSCP .NET assembly) that spawns a native (C++) application and communicates with it via events and file mapping objects.
The assembly spawns the application using the Process class, with no special settings. The assembly creates few events (using the EventWaitHandle) and file mapping (using the PInvoked CreateFileMapping) and the application "opens" these using the OpenEvent and the OpenFileMapping.
It works fine in most cases. But now I'm having a user that uses the assembly from an ASPX application on Windows Server 2008 R2 64 bit.
In his case both the OpenEvent and the OpenFileMapping return NULL and the GetLastError returns the ERROR_ACCESS_DENIED.
I have tried to improve the assembly code by explicitly granting the current user necessary permissions to the event objects and the application code to require only the really needed access rights (instead of original EVENT_ALL_ACCESS) as per Microsoft Docs example. It didn't help. So I did not even bother to try the same for the file mapping object.
The C# code that creates the event is:
EventWaitHandleSecurity security = new EventWaitHandleSecurity();
string user = Environment.UserDomainName + "\\" + Environment.UserName;
EventWaitHandleAccessRule rule;
rule =
new EventWaitHandleAccessRule(
user, EventWaitHandleRights.Synchronize | EventWaitHandleRights.Modify,
AccessControlType.Allow);
security.AddAccessRule(rule);
rule =
new EventWaitHandleAccessRule(
user, EventWaitHandleRights.ChangePermissions, AccessControlType.Deny);
security.AddAccessRule(rule);
new EventWaitHandle(
false, EventResetMode.AutoReset, name, out createdNew, security);
The C++ code that "opens" the events is:
OpenEvent(EVENT_MODIFY_STATE, false, name);
(For other events the access level is SYNCHRONIZE, depending on needs).
I have also tried to add Global\ prefix to the object names. As expected this didn't solve the problem.
Does anyone have any idea what causes the "access denied" error in OpenEvent (or CreateFileMapping)?
My guess is that the event is created by either the anonymous user or the logged in user depending on how the website is setup. But the sub-process is being launched with the base process user. This can be checked by using process monitor and looking at the acl for the event handle to see who the creator is. Then look at the sub process to see who it is running as.
If this is the case then you can update the acl on the event to include the base process. In addition to this, you may still need to prefix with "global" to make sure that the event can be used across user boundaries.
I'm trying to create a windows service and I need it to be able to write to the event logs. I've added an EventLog component to my Service project, and set the Log property to be ccs_wscln_log and the Source property to be ccs_wscln (same name as the service).
I have also created and installer for this project. My problem is that whenever I install the service, it creates the ccs_wscln registry key under
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Application
when it SHOULD be
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\ccs_wscln_log.
The problem with this is that when I try to launch the service, I get an error that says
"The source 'ccs_wscln' is not registered in log 'ccs_wscln_log'. (It is registered in log 'Application'). The source and Log properties must be matched, or you may set Log to the empty string, and it will automatically be matched to the source property'.
I've found that if I delete the ccs_wscln registry key under the Application folder, when I start the service it will run and generate the ccs_wscln_log entry under EventLog. So my question is, when I install the application, why is it creating an entry for me automatically under Application, and how do I prevent it from doing this?
I found another post on SO that said I need to restart my computer if I had installed it before under Application, so I tried that but when I reloaded the solution, I couldn't even bring up the designer because it was complaining that the registry entry was missing and it would still install under Application anyways.
I created a tutorial for creating a Windows service from scratch using C#. I address the issue of writing to an application-specific log. See Step 9 here for details.
I think, you would need following in your ServiceInstaller class.
this.Installers.Clear();
Above code needs to be just before you are adding a range of installers.
That is because, EventLogInstaller is added by default. Calling clear will remove it.
Alternatively, you can loop through the installers collection, select the specific type (EventLogInstaller) and assign it required LogName and EventSource name.