My application is using MAPISendMail to send email. The email is formatted with spaces. eg:
Date Sent 12/4/2018
However, when the email pops up in Thunderbird (my default email application), the spaces have all gone. It looks like it is interpreting the message as HTML.
If I make Outlook my default email package, this doesn't happen. Is there anything I can do from the application side (C++, Windows, MAPI) that will convince Thunderbird to leave the message alone? The documentation seems a bit vague on what the message parameters mean.
Fundamentally, the function that does the sending looks like this (where to, subject and body are strings filled and passed in):
MapiRecipDesc rec;
MapiMessage msg;
bool sent = false;
LHANDLE session = 0;
memset(&msg, 0, sizeof(MapiMessage));
memset(&rec, 0, sizeof(MapiRecipDesc));
rec.ulRecipClass = MAPI_TO;
rec.lpszName = (LPSTR)to;
msg.lpszSubject = (LPSTR)subject;
msg.lpszNoteText = (LPSTR)body;
msg.nRecipCount = (to != 0) ? 1 : 0;
msg.lpRecips = &rec;
ULONG e = MAPISendMail(session, (ULONG_PTR)hWnd, &msg, MAPI_LOGON_UI|MAPI_DIALOG, 0);
Related
I am using the PST File Format SDK to try and extract some appointment item data from an Outlook PST export:
int main()
{
pst myfile(L"export.pst");
folder calendar = myfile.open_folder(L"Calendar");
for (folder::message_iterator msg = calendar.message_begin(); msg != calendar.message_end(); ++msg)
{
message m = *msg;
wstring subject = L"";
if (m.has_subject())
subject = m.get_subject();
wstring body = L"";
if (m.has_body())
body = m.get_body();
wstring htmlbody = L"";
if (m.has_html_body())
htmlbody = m.get_html_body();
size_t num_attachments = m.get_attachment_count();
size_t num_recipients = m.get_recipient_count();
property_bag bag = m.get_property_bag();
vector<prop_id> propertyList = bag.get_prop_list();
for (vector<prop_id>::iterator prop = propertyList.begin(); prop != propertyList.end(); ++prop)
{
if (bag.get_prop_type(*prop) == prop_type_systime)
FILETIME property = bag.read_prop<FILETIME>(*prop);
}
break; // Just try the first message for now.
}
return 0;
}
How does Outlook store appointment data? I mean, how can I get at the very least the appointment start time and duration (or end time)? I've been trying to scour the PST file format specification from Microsoft but I can't seem to find this information I need!
Edit: I can parse FILETIME objects as in the above code now, but the question is, how can I distinguish them to what time they are referring to? I mean, how can I tell if this is the time of the event date, or the end of the event date, etc.? They should have names to distinguish them, should they not? How do I get that using pstsdk?
These (and many more) properties are stored as named MAPI properties. Their tags will vary between different stores, so you cannot hardcode them. See the explanation of what named properties are on my web site: https://www.dimastr.com/redemption/utils.htm#named-props
Take a look at an existing appointment in OutlookSpy (I am its author) - click IMessage button.
I am working on a project that uses Mavlink protocol (in c++) to communicate with the ArduPilotMega (2.6).
I am able to read messages such as ATTITUDE for example. The current message rate (for all messages) is 2Hz and I would like to increase this rate.
I found out that I should probably set MESSAGE_INTERVAL using MAV_CMD_SET_MESSAGE_INTERVAL in order to change it.
So my question is:
How do I send this command message using mavlink in c++?
I tried doing it using the code below but it did not work. I guess I have to use the command I mentioned above, but I don't know how.
mavlink_message_t command;
mavlink_message_interval_t interval;
interval.interval_us = 100000;
interval.message_id = 30;
mavlink_msg_message_interval_encode(255, 200, &command, &interval);
p_sensorsPort->write_message(command);
Update: I also tried this code below, maybe I am not giving it the right system id or component id.
mavlink_message_t command;
mavlink_command_long_t interval;
interval.param1 = MAVLINK_MSG_ID_ATTITUDE;
interval.param2 = 100000;
interval.command = MAV_CMD_SET_MESSAGE_INTERVAL;
interval.target_system = 0;
interval.target_component = 0;
mavlink_msg_command_long_encode(255, 0, &command, &interval);
p_sensorsPort->write_message(command);
Maybe I am missing something about the difference between target_system, target_component and sysid, compid. I tried few values for each but nothing worked.
Is there any ACK that will be able to tell me if it even got the command?
I guess you missed start_stop field. the below sample is working.
final msg_request_data_stream msg = new msg_request_data_stream ();
msg.req_message_rate = rate;
msg.req_stream_id = (short) streamId;
msg.target_component = (short)compID;
msg.target_system = (short)sysID;
/*
GCS_COMMON.cpp contains code that sends when value =1
and stop when value = 0
that is it.
*/
if (rate > 0) {
msg.start_stop = 1;
} else {
msg.start_stop = 0;
}
From Robotis Stack Exchange answer,
In order to change the message rate, the simplest way is to change the SR_* parameters value using Mission Planner. The maximum rate is 10Hz.
For example, in order to change the ATTITUDE message rate to be 10Hz I just had to change the SR_EXTRA1 parameter to be 10.
For more information about which parameter changes each message see GCS_Mavlink.cpp file in ArduCopter firmware.
I have a working method which will successfully open Outlook (and other mail clients), open a new message window, and set the body and subject fields with provided strings, and attach one or more files indicated by a vector of our internal filepath object. I'm trying to extend this to add a recipient in the To: field. The code below omits all of the subject/body/path stuff for brevity and just shows what I've done with recipients.
void createMailMessage( const char *recipientAddr, const char *subject, const char *body, const std::vector<FSPath> &attachments) {
// first get all of the const char* into std::wstring
// ommitted here for brevity
MapiMessageW message = { 0 };
MapiRecipDescW *recipients = NULL;
if (recipientAddr != NULL) {
recipients = new MapiRecipDescW[1];
memset(recipients, 0x00, sizeof(MapiRecipDescW) ) // only one recipient
recipients[0].lpszAddress = (PWSTR) recipStrW.c_str(); // recipStrW is from omitted code above
recipients[0].ulRecipClass = MAPI_TO;
message.nRecipCount = (ULONG) 1;
message.lpRecips = recipients;
}
// fill in the rest of the message info here
// this stuff is already working and i left it unchanged
MAPISendMailHelper(NULL, NULL, &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);
}
In the debugger I can see that the message struct is still well-formed, simply with the addition of a pointer to the recipients struct and the nRecipCount field filled correctly. That struct is also well-formed, with the expected address string and class value. When the code executes, it reaches the same call that produces the new message dialog ( pfnMapiSendMailA() in MapiUnicodeHelp.h ), but does not seem to execute it.
Help! What am I missing?!
Thanks in advance!
I'm generating a reporting services report from an ASP.NET (MVC) based application but am having problems setting the parameters for the report.
I believe the issue has only occurred since we upgraded SQL Server from 2005 to 2008 R2 (and Reporting Services along with it).
The original error encountered was from calling rsExec.Render:
Procedure or function 'pCommunication_ReturnRegistrationLetterDetails'
expects parameter '#guid', which was not supplied.
Debugging the code I noticed that rsExec.SetExecutionParameters is returning the following response:
Cannot call 'NameOfApp.SQLRSExec.ReportExecutionService.SetExecutionParameters(NameOfApp.SQLRSExec.ParameterValue[],
string)' because it is a web method.
Here is the function in it's entirety:
public static bool ProduceReportToFile(string reportname, string filename, string[,] reportparams,
string fileformat)
{
bool successful = false;
SQLRS.ReportingService2005 rs = new SQLRS.ReportingService2005();
SQLRSExec.ReportExecutionService rsExec = new NameOfApp.SQLRSExec.ReportExecutionService();
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
rsExec.Credentials = System.Net.CredentialCache.DefaultCredentials;
// Prepare Render arguments
string historyID = null;
string deviceInfo = null;
// Prepare format - available options are "PDF","Word","CSV","TIFF","XML","EXCEL"
string format = fileformat;
Byte[] results;
string encoding = String.Empty;
string mimeType = String.Empty;
string extension = String.Empty;
SQLRSExec.Warning[] warnings = null;
string[] streamIDs = null;
// Define variables needed for GetParameters() method
// Get the report name
string _reportName = reportname;
string _historyID = null;
bool _forRendering = false;
SQLRS.ParameterValue[] _values = null;
SQLRS.DataSourceCredentials[] _credentials = null;
SQLRS.ReportParameter[] _parameters = null;
// Get if any parameters needed.
_parameters = rs.GetReportParameters(_reportName, _historyID,
_forRendering, _values, _credentials);
// Load the selected report.
SQLRSExec.ExecutionInfo ei =
rsExec.LoadReport(_reportName, historyID);
// Prepare report parameter.
// Set the parameters for the report needed.
SQLRSExec.ParameterValue[] parameters =
new SQLRSExec.ParameterValue[1];
// Place to include the parameter.
if (_parameters.Length > 0)
{
for (int i = 0; i < _parameters.Length; i++)
{
parameters[i] = new SQLRSExec.ParameterValue();
parameters[i].Label = reportparams[i,0];
parameters[i].Name = reportparams[i, 0];
parameters[i].Value = reportparams[i, 1];
}
}
rsExec.SetExecutionParameters(parameters, "en-us");
results = rsExec.Render(format, deviceInfo,
out extension, out encoding,
out mimeType, out warnings, out streamIDs);
// Create a file stream and write the report to it
using (FileStream stream = System.IO.File.OpenWrite(filename))
{
stream.Write(results, 0, results.Length);
}
successful = true;
return successful;
}
Any ideas why I'm now unable to set parameters? The report generation works without issue if parameters aren't required.
Looks like it may have been an issue with how reporting services passes parameters through to the stored procedure providing the data. A string guid was being passed through to the report and the stored procedure expected a varchar guid. I suspect reporting services may have been noticing the string followed the guid format pattern and so passed it through as a uniqueidentifier to the stored procedure.
I changed the data source for the report from "stored procedure" to "text" and set the SQL as "EXEC pMyStoredOProcName #guid".
Please note the guid being passed in as a string to the stored procedure is probably not best practice... I was simply debugging an issue with another developers code.
Parameter _reportName cannot be null or empty. The [CLASSNAME].[METHODNAME]() reflection API could not create and return the SrsReportNameAttribute object
In this specific case it looks like an earlier full compile did not finish.
If you encounter this problem I would suggest that you first compile the class mentioned in the error message and see if this solves the problem.
go to AOT (get Ctrl+D)
in classes find CLASSNAME
3.compile it (F7)
I need to add a "Create and email" feauture to our app. Our program creates an output file, and I must then launch the default email client to open a "write email" window, and with the output file preselected as an attachment.
I've seen other programs do it, even if the default client is Thunderbird instead of Outlook.
I ended up using MAPI to achieve it. I used LoadLibrary and GetProcAddress to get the needed functions.
The code I used is this:
bool MailSender::Send(HWND hWndParent, LPCTSTR szSubject)
{
if (!m_hLib)
return false;
LPMAPISENDMAIL SendMail;
SendMail = (LPMAPISENDMAIL) GetProcAddress(m_hLib, "MAPISendMail");
if (!SendMail)
return false;
vector<MapiFileDesc> filedesc;
for (std::vector<attachment>::const_iterator ii = m_Files.begin(); ii!=m_Files.end(); ii++)
{
MapiFileDesc fileDesc;
ZeroMemory(&fileDesc, sizeof(fileDesc));
fileDesc.nPosition = (ULONG)-1;
fileDesc.lpszPathName = (LPSTR) ii->path.c_str();
fileDesc.lpszFileName = (LPSTR) ii->name.c_str();
filedesc.push_back(fileDesc);
}
std::string subject;
if (szSubject)
subject = utf16to8(szSubject).c_str();
else
{
for (std::vector<attachment>::const_iterator ii = m_Files.begin(); ii!=m_Files.end(); ii++)
{
subject += ii->name.c_str();
if (ii+1 != m_Files.end())
subject += ", ";
}
}
MapiMessage message;
ZeroMemory(&message, sizeof(message));
message.lpszSubject = (LPSTR) subject.c_str();
message.nFileCount = filedesc.size();
message.lpFiles = &filedesc[0];
int nError = SendMail(0, (ULONG)hWndParent, &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);
if (nError != SUCCESS_SUCCESS && nError != MAPI_USER_ABORT && nError != MAPI_E_LOGIN_FAILURE)
return false;
return true;
}
Using the mailto scheme may be a solution but it's going to be tricky due to restrictions on what fields are considered safe (see the RFC 2368 and 6067 for the full details if you want to go that route).
Another solution would be to figure out what email client is installed and - wherever possible - launch it and specify all you need via command line. See here for Thunderbird & here for Outlook.
You can use the following command to start the start the default client app with attachment
"Path to default mail client.exe" -mail -compose subject='Subject',attachment='File path',body='body'"
Path to default mail client-> can be taken from registry path
HKEY_LM\SOFTWARE\Clients\Mail\Email Client Name\shell\open\command
Mail Client Name -> can be taken from
HKEY_LM\Software\Clients\Mail