MAPI and Outlook address book - visual-studio

I'm developing one project using VC++ (MSDEV 2008), which has one function to send EMAIL with some attachments. I used MAPI functions to achieve this task.
I build the project with project setting "Character set: use Unicode character set" for UNICODE compatible support and file type is EXE extension. Here everything works fine.
Same project I build as OCX file extension. And I can display "new send mail" window with some attachments. Here the problem is
When I click address book icon ("To" button) to select the receiver mail id from the list. It displays the Address book dialog with title only "S" instead "Select Name: *". But this also works fine in EXE project.
Code:
HWND hWnd = this->GetSafeHwnd();
MAPIINIT_0 tMapInit = { 0, MAPI_MULTITHREAD_NOTIFICATIONS };
HRESULT hResult = MAPIInitialize( &tMapInit );
HMODULE hMapiMod = LoadLibrary(_T("mapi32.dll"));
ProcMapiLogon = (LPMAPILOGON)GetProcAddress( hMapiMod, "MAPILogon" );
(ProcMapiLogon)( (ULONG)hWnd, NULL, NULL, MAPI_LOGON_UI | MAPI_NEW_SESSION, 0, &hCurrentSession );
LPMAPISENDMAIL ProcMapiSendMail = NULL;
ProcMapiSendMail = (LPMAPISENDMAIL)GetProcAddress(hMapiMod, "MAPISendMail");
(ProcMapiSendMail)(hCurrentSession, (ULONG)hWnd, &myMsg, MAPI_DIALOG | MAPI_LOGON__UI, 0);
The question is why Address Book dialog’s title shows only “S” in OCX project. Same it works in exe project.
Kindly help me how to resolve the issue.
Additional note:
Thanks for your reply.
With Simple MAPI code, everything works fine (means I can display new send mail window and send it when I click ‘Send” button) except the “Address Book” dialog’s title (caption).
I tried extended MAPI functionalities also. When the project is built as .OCX file extension like “SendMail.ocx” still Address Book dialog’s title shows only “S”.
Extended MAPI Code:
HMODULE hMapiMod = LoadLibrary(_T("mapi32.dll"));
LPMAPISESSION lppSession;
LPMAPILOGONEX ProcMapiLogonEx = NULL;
ProcMapiLogonEx = (LPMAPILOGONEX)GetProcAddress( hMapiMod, "MAPILogonEx" );
ProcMapiLogonEx)( (ULONG)hWnd, NULL, NULL, MAPI_USE_DEFAULT | MAPI_UNICODE | MAPI_EXTENDED | MAPI_LOGON_UI | MAPI_NEW_SESSION, &lppSession );

Simple MAPI is ANSI only. The external MAPI dll has no idea that you complied as Unicode, all it sees is a string that it expects to be 0x0 terminated. Your Unicode (2 byte) strings have 0x0 for each ANSI character and double 0x0 at the end. Hence your string is terminated at the first 0x0.

Related

Windows crash in ChangeServiceConfig2

I'm having a problem with ChangeServiceConfig2(...SERVICE_CONFIG_TRIGGER_INFO...)
Relevant code:
WCHAR test[] = L"TEST12";
SERVICE_TRIGGER_SPECIFIC_DATA_ITEM stdata {
SERVICE_TRIGGER_DATA_TYPE_STRING,
wcslen(test)*sizeof(WCHAR),
reinterpret_cast<BYTE*>(test)
};
SERVICE_TRIGGER st {
SERVICE_TRIGGER_TYPE_NETWORK_ENDPOINT,
SERVICE_TRIGGER_ACTION_SERVICE_START,
const_cast<GUID*>(&NAMED_PIPE_EVENT_GUID),
1, &stdata
};
ChangeServiceConfig2(Service, SERVICE_CONFIG_TRIGGER_INFO, &st);
This causes an Access Violation on address 00000009, so clearly an unchecked null pointer. And it's not a null pointer in st or stdata. The address 00000009 does not depend on the length of test[].
Stack dump:
rpcrt4.dll!NdrpEmbeddedRepeatPointerBufferSize()
rpcrt4.dll!NdrConformantArrayBufferSize()
rpcrt4.dll!NdrSimpleStructBufferSize()
rpcrt4.dll!NdrpUnionBufferSize()
rpcrt4.dll!_NdrNonEncapsulatedUnionBufferSize#12()
rpcrt4.dll!NdrComplexStructBufferSize()
rpcrt4.dll!NdrClientCall2() rpcrt4.dll!_NdrClientCall4()
sechost.dll!ChangeServiceConfig2W()
The Service member is not the problem, or ChangeServiceConfig2 itself: I can set the service description via ChangeServiceConfig2(Service, SERVICE_CONFIG_DESCRIPTION, &desc);. The problem appears to be in the parsing of SERVICE_TRIGGER. Named Pipe service triggers apparently work for the Remote Registry service, so it's not fundamentally broken.
Q: which part of my SERVICE_TRIGGER is wrong?
Obviously there is at least one bug in Windows; at the very least it fails in parameter validation.
The SERVICE_TRIGGER object is correct, but ChangeServiceConfig2 wants a SERVICE_TRIGGER_INFO. Simple solution: wrap st using SERVICE_TRIGGER_INFO sti{ 1, &st, NULL };

Setting device identifier using LoadKeyboardLayout()

The problem is this: I must set the user's language to Simplified Chinese and the keyboard to "Chinese (Simplified) - Microsoft Pinyin New Experience Input st".
By setting this combo manually in the Control Panel (Region -> Keyboards and languages) and then running a small test program which calls GetKeyboardLayoutName(), I've found out that the KLID is 00000804 (supposedly). If I remove Chinese from Keyboards and languages in the Control Panel and run this:
HKL hKeyboardLayout = ::LoadKeyboardLayout(_T("00000804"), KLF_ACTIVATE | KLF_SETFORPROCESS);
Then the language is indeed changed to Chinese, but the keyboard settings are wrong. The little "IME box" is missing when typing something.
The MSDN page for LoadKeyboardLayout() says this about the pwszKLID parameter:
The name of the input locale identifier to load. This name is a string composed of the hexadecimal value of the Language Identifier (low word) and a device identifier (high word). For example, U.S. English has a language identifier of 0x0409, so the primary U.S. English layout is named "00000409". Variants of U.S. English layout (such as the Dvorak layout) are named "00010409", "00020409", and so on.
So it appears as if GeyKeyboardLayout() only reports the language identifier (0x0804 for Chinese), but the "device identifier" is missing. How can I find out the device identifier for "Microsoft Pinyin New Experience Input st"?
Found the solution. Apparently in Vista (and onwards) you must use InstallLayoutOrTip() with the correct GUID (not KLID) to install the proper language-keyboard combo. Then you can call LoadKeyboardLayout() to load it.
typedef HRESULT (WINAPI *PTF_INSTALLLAYOUTORTIP)(LPCWSTR psz, DWORD dwFlasg);
// Install.
HMODULE hInputDLL = LoadLibrary(_T("input.dll"));
BOOL bRet = FALSE;
if(hInputDLL == NULL)
{
// Error
}
else
{
PTF_INSTALLLAYOUTORTIP pfnInputLayoutOrTip;
pfnInputLayoutOrTip = (PTF_INSTALLLAYOUTORTIP)GetProcAddress(hInputDLL, "InstallLayoutOrTip");
if(pfnInputLayoutOrTip)
{
bRet = (*pfnInputLayoutOrTip)(_T("0804:{81D4E9C9-1D3B-41BC-9E6C-4B40BF79E35E}{F3BA9077-6C7E-11D4-97FA-0080C882687E}"), 0);
if(! bRet)
{
// Error
}
}
else
{
// Error
}
FreeLibrary(hInputDLL);
}
// Load.
HKL hKeyboardLayout = ::LoadKeyboardLayout(_T("00000804"), KLF_ACTIVATE | KLF_SETFORPROCESS);
References:
http://msdn.microsoft.com/library/bb847909.aspx
http://www.siao2.com/2007/12/01/6631463.aspx

Programmatically set Outlook 2013 Signature Defaults?

Is it possible to programmatically set the Outlook 2013 Default Signature settings? We can generate the user's signature OK but would like to also set the signature to appear by default in user's emails:
The setting itself seems to be tucked-away in the Registry under an Outlook profile:
HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Outlook\Profiles\Outlook\9375CFF0413111d3B88A00104B2A6677\00000002
Reg Values:
New Signature
Reply-Forward Signature
... (which have binary data, presumably encoding the HTML file name/reference).
Not sure if I can use the Outlook Object Model to access and set settings? And whether this would be possible with a ClickOnce application?
I haven't cleaned up the code yet, but this works for me to set the signature in Outlook 2013. In python (yes I know its ugly and not PEP8).
import _winreg
def set_default():
try:
#this makes it so users can't change it.
outlook_2013_key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Office\15.0\Common\MailSettings", 0, _winreg.KEY_ALL_ACCESS)
_winreg.SetValueEx(outlook_2013_key, "NewSignature", 0, _winreg.REG_SZ, "default" )
_winreg.SetValueEx(outlook_2013_key, "ReplySignature", 0, _winreg.REG_SZ, "default" )
# sets the sigs in outlook profile
outlook_2013_base_key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Office\15.0\Outlook\Profiles", 0, _winreg.KEY_ALL_ACCESS)
default_profile_2013_tup = _winreg.QueryValueEx(outlook_2013_base_key,'DefaultProfile')
default_profile_2013 = default_profile_2013_tup[0]
print default_profile_2013
outlook_2013_profile_key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
"Software\\Microsoft\\Office\\15.0\\Outlook\\Profiles\\" + default_profile_2013 + "\\9375CFF0413111d3B88A00104B2A6676", 0, _winreg.KEY_ALL_ACCESS)
for i in range(0, 10):
try:
outlook_2013_sub_key_name = _winreg.EnumKey(outlook_2013_profile_key,i)
print outlook_2013_sub_key_name, "sub_key_name"
outlook_2013_sub_key = _winreg.OpenKey(outlook_2013_profile_key, outlook_2013_sub_key_name, 0, _winreg.KEY_ALL_ACCESS)
_winreg.SetValueEx(outlook_2013_sub_key, "New Signature", 0, _winreg.REG_SZ, "default" )
_winreg.SetValueEx(outlook_2013_sub_key, "Reply-Forward Signature", 0, _winreg.REG_SZ, "default" )
except:
pass
except:
print('no 2013 found')
Outlook signatures are set on the per account basis in profile data (stored in the registry). You can see the data in OutlookSpy - click IOlkAccountManager button and double click on the account.
IOlkAccountManager can be accessed only in C++ or Delphi. If using Redemption is an option (it can be used from any language, including VBA or .Net, I am its author), it exposes the RDOAccount.ReplySignature and NewMessageSignature properties.

Windows: ReportEvent function

As far as I understood, the ReportEvent function requires Message Text Files associated through the registry to receive properly formatted messages. Is there any common Event Ids or any simple way to report an event with no Message Text Files associated?
Or may be, is there special common Event Source which I can use in my application? Something like RegisterEventSource(NULL, "Application")?
You don't have to register your messages in HKLM. (Which is a good thing, because you can't register messages if you're not an administrator).
But that doesn't stop you from writing events to the Windows Application event log. The only downside is that starting with Windows Vista you'll just get some ugly text along with it.
HRESULT LogToEventLog(String Source, String EventText, int EventType, DWORD EventID)
{
/*
EventType is one of:
EVENTLOG_ERROR_TYPE = $0001;
EVENTLOG_WARNING_TYPE = $0002;
EVENTLOG_INFORMATION_TYPE = $0004;
EVENTLOG_AUDIT_SUCCESS = $0008;
EVENTLOG_AUDIT_FAILURE = $0010;
Source is your name for your app or feature, e.g.:
"My Cool App"
"Outlook"
"ESENT"
"Chrome"
*/
HANDLE h = RegisterEventSource(null, Source); //null --> local computer
if (h == 0)
return HResultFromWin32(GetLastError);
try
{
PChar[1] ss;
ss[0] = PChar(EventText);
if (!ReportEvent(
h, // event log handle
EventType, // event type
0, // category zero
EventID, // event identifier
null, // no user security identifier
1, // one substitution string
0, // no data
#ss, // pointer to string array
null // pointer to data
))
{
return HResultFromWin32(GetLastError);
}
}
finally
{
DeregisterEventSource(h);
}
return S_OK;
}
And so now you can log events to the Application event log:
LogToEventLog("Stackoverflow", "Question 5399066 was answered by Ian Boyd",
EVENTLOG_INFORMATION_TYPE, 0x45);
Steal someone else's registration
Unfortunately, starting with Windows Vista, Windows will give ugly complaints that you didn't register the event beforehand:
The description for Event ID 69 from source Stackoverflow cannot be
found. Either the component that raises this event is not installed on
your local computer or the installation is corrupted. You can install
or repair the component on the local computer.
If the event originated on another computer, the display information
had to be saved with the event.
The following information was included with the event:
Question 5399066 was answered by Ian Boyd
But you don't have to live with it. Just because you didn't register an message source file in HKLM, doesn't mean nobody else did.
Notice, for example, a message from the Outlook source in the Event log:
Source: Outlook
EventID: 0x40000020
Event Data: D:\win32app\Exchange\Outlook2003.pst
Message: The store D:\win32app\Exchange\Outlook2003.pst has detected a catalog checkpoint.
You can check registration information for Outlook in:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Application\Outlook
And see:
MessageEventFile: REG_SZ = "D:\Programs\MICROS~4\Office14\1033\MAPIR.DLL"
If you peek into the resources of MAPIR.dll binary, you'll see its Message Table:
1 MESSAGETABLE
{
0x12, "Connection stats for server (%1). Rpcs Attempted (%2), Rpcs Succeeded (%3), Rpcs Failed (%4), Rpcs Canceled (%5), Rpc UI shown (%6), Avg request time (%7) ms, Min request time (%8) ms, Max request time (%9) ms.\r\n"
0x14, "Cancelable RPC started.\r\n"
0x15, "Cancelable RPC shutdown.\r\n"
0x40000010, "Cancelable RPC dialog shown for server (%1), total wait time was (%2) ms, result was (%3).\r\n"
0x40000011, "User canceled request against server (%1) after waiting (%2) ms.\r\n"
0x40000013, "Rpc call (%1) on transport (%2) to server (%3) failed with error code (%4) after waiting (%5) ms; eeInfo (%6).\r\n"
0x40000016, "There was a problem reading one or more of your reminders. Some reminders may not appear.\r\n"
0x40000017, "Unable to update public free/busy data.\r\n"
0x4000001A, "%1\r\n"
0x4000001B, "%1\r\n"
0x4000001D, "The store %1 is being re-pushed to the indexer for the following reason: %2.\r\n"
0x4000001E, "Starting reconciliation for the store %1 for the following reason: %2.\r\n"
0x4000001F, "The store %1 has detected a catalog rebuild.\r\n"
0x40000020, "The store %1 has detected a catalog checkpoint.\r\n"
...
}
You can see that eventid 0x40000020 is assocated with a formatting string:
"The store %1 has detected a catalog checkpoint.\r\n"
You can hijack Outlook's registration:
LogToEventLog("Outlook", "Your mom", EVENTLOG_INFORMATION_TYPE, $40000020);
and you'll get your event added to the event log without all the ugly warnings:
No, you just have to follow the rules and define your message text files, build them into resources, link them to your app etc.
The example provided at MSDN leads you through everything you need to do.
Try this out, it's worked for me before..
http://www.codeproject.com/KB/system/xeventlog.aspx

Determine the registered application for an extension

I've got a file extension and I'd like to get the name of the application (if there is one) that will be invoked when I ShellExecute a file of that type. This is a WTL/C++ app. Is there any sample code out there that does this?
Thanks!
twk,
You're probably looking for the Win32 AssocQueryStringByKey Function.
http://msdn.microsoft.com/en-us/library/bb773473(VS.85).aspx
The ASSOCSTR value that specifies the type of string that is to be returned:
typedef enum {
ASSOCSTR_COMMAND = 1,
ASSOCSTR_EXECUTABLE,
ASSOCSTR_FRIENDLYDOCNAME,
ASSOCSTR_FRIENDLYAPPNAME,
ASSOCSTR_NOOPEN,
ASSOCSTR_SHELLNEWVALUE,
ASSOCSTR_DDECOMMAND,
ASSOCSTR_DDEIFEXEC,
ASSOCSTR_DDEAPPLICATION,
ASSOCSTR_DDETOPIC,
ASSOCSTR_INFOTIP,
ASSOCSTR_QUICKTIP,
ASSOCSTR_TILEINFO,
ASSOCSTR_CONTENTTYPE,
ASSOCSTR_DEFAULTICON,
ASSOCSTR_SHELLEXTENSION,
ASSOCSTR_DROPTARGET,
ASSOCSTR_DELEGATEEXECUTE,
ASSOCSTR_MAX
} ASSOCSTR;
My guess is that you want ASSOCSTR_FRIENDLYAPPNAME.
DWORD dwSize = 255;
TCHAR sBuffer[MAX_PATH] = {0};
HRESULT hr = AssocQueryString(0, ASSOCSTR_EXECUTABLE, _T(".htm"), _T("Open"), sBuffer, &dwSize);
CString csExt;
csExt.Format(_T("%s"), sBuffer);
AfxMessageBox(csExt);
Sorry, no code, but some useful information. See this related question: how-does-vista-generate-the-icon-for-documents-associated-to-my-application
It asked about icons, but it turns out the program associated to an extension is stored in the same place in the registry as the icon for that extension.
It's a Win32 FAQ since 1995 (Shell, see Google Groups, Win32)

Resources