Outlook UI thread is blocked when convert msg to eml using Outlook redemption - outlook-redemption

Outlook UI thread is blocked when convert msg to eml using redemption
I'm developping an outlook addin to sync emails to server as eml. When the addin start, it will start a timer and check whether there's new emails and then upload them. For each email, I will convert them to eml using outlook Redemption. It works, but I found during the convertion, outlook UI would be blocked. I'm using following code to do the convertion. I also tried RDOSession.GetMessageFromID(mail.EntryID). It's the same. Anyone
public static string ToEmlFile(this Outlook.MailItem mail)
{
var msgFilename = Path.ChangeExtension(Path.GetTempFileName(), ".msg");
mail.SaveAs(msgFilename);
var item = RDOSessionMgr.GetInstance().GetMessageFromMsgFile(msgFilename);
var emlFilename = Path.ChangeExtension(Path.GetTempFileName(), ".eml");
item.SaveAs(emlFilename, Redemption.rdoSaveAsType.olRFC822);
return emlFilename;
}
public class RDOSessionMgr
{
private static Redemption.RDOSession _session;
static RDOSessionMgr()
{
_session = new Redemption.RDOSession();
_session.Logon(Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
}
public static Redemption.RDOSession GetInstance()
{
return _session;
}
}

You should be able to run that code on a secondary thread - save the value of the Application.Session.MAPIOBJECT property in a global/class variable, then assign that value to the RDOSession.MAPIOBJECT property of the RDOSession object that you create on the secondary thread.
ON a related note, you can use that technique on the main thread as well - there is no reason to RDOSession.LOgon from inside an Outlook addin since you already have the MAPI session used by Outlook.

Related

HttpClient doesn't works in IOs app builded in VS with Xamarin

I try to make an awaitable request in my PCL/Android/iOs project. Function for request is in PCL
public class DataService
{
private static string _requestUri = "https://requesturi.com/";
public static async Task<LocalizationData> GetLocalization(string code)
{
string queryString = _requestUri + "get_localization.php" + "?code=" + code;
HttpClient client = new HttpClient();
var response = await client.GetAsync(queryString);
dynamic data = null;
if (response != null)
{
string json = response.Content.ReadAsStringAsync().Result;
data = JsonConvert.DeserializeObject(json);
if (data["status"] == "success")
{
List<string> aliases = new List<string>();
List<string> translations = new List<string>();
foreach (var localization in data["localizations"])
{
aliases.Add((string)localization["alias"]);
translations.Add((string)localization["translation"]);
}
LocalizationData localizationData = new LocalizationData(code, aliases.ToArray(), translations.ToArray());
return localizationData;
}
else
{
return null;
}
}
return null;
}
}
Both in Android and iOS I call this function with
localizationData = await DataService.GetLocalization(langCode);
In Android it works without problems both on simulator and on real device.
But when I try run it in iOS, on simulator it works fine, on real device app crash on
var response = await client.GetAsync(queryString);
Is it something about permissions? Or something else?
Can anybody help me with this problem?
UPDATED
There are exception for client.GetAsync(queryString) I get in app on real device:
"Operation is not valid due to the current state of the object"
According to the thread in Xamarin forum this is issue with Reference. Seems like httpClient instance was created in mono memory but not in iOS memory, due to a difference between an iOS device(AOT) and simulator(JIT) build nature.
Try :
1) Go to References of ios Project
2) Edit References
3) Check 'System.Net.Http'
In general - use ModernHttpClient - it provides wrappers for native networking API, it is secure and faster then default .Net HttpClient.

Generating/Sending pdf email attachments within a hangfire job

In our ASP.NET MVC web application we send emails as part of scheduled tasks handled by Hangfire for which I am using Postal as described here
The method works fine and we are able to send HTML/text emails. Now we need to generate and attach PDF files as well. The attached PDF needs to be generated dynamically by use of a Razor template. First I tried to use Rotativa in order to generate the PDF. However I encountered the problem that method BuildPdf needs a ControllerContext which is not available in the background HangFire process. I tried to fake the ControllerContext as
using (var memWriter = new StringWriter(sb))
{
var fakeResponse = new HttpResponse(memWriter);
var fakeRequest = new HttpRequest(null, "http://wwww.oururl.com", null);
var fakeHttpContext = new HttpContext(fakeRequest, fakeResponse);
var emailController = new BackgroundEmailController();
var fakeControllerContext = new ControllerContext(new HttpContextWrapper(fakeHttpContext), new RouteData(), emailController);
var attachment = emailController.BillAttachment(email);
var pdf = attachment.BuildPdf(fakeControllerContext);
if (pdf != null && pdf.Count() > 0)
{
using (MemoryStream ms = new MemoryStream(pdf))
{
var contentType = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Application.Pdf);
email.Attach(new System.Net.Mail.Attachment(ms, contentType));
}
}
}
However this raised a NullReference error in Rotativa.
Then I tried first to compile the template view with RazorEngine to HTML(and then convert the HTML to pdf by some mean) as
var engineService = RazorEngineService.Create();
engineService.AddTemplate(cache_name, File.ReadAllText(billAttachmentTemplatePath));
engineService.Compile(cache_name, modelType: typeof(BillEmail));
var html = engineService.Run(cache_name, null, email);
using (var ms = CommonHelper.GenerateStreamFromString(html))
{
var contentType = new System.Net.Mime.ContentType(System.Net.Mime.MediaTypeNames.Text.Html);
email.Attach(new System.Net.Mail.Attachment(ms, contentType));
}
And it throws another NullReference in the RazorEngine dynamic DLL:
System.NullReferenceException: Object reference not set to an instance of an object.
at CompiledRazorTemplates.Dynamic.RazorEngine_bb2b366aaef64f2bbc2997353f88cc9e.Execute()
at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
I was wondering if anybody have suggestions for generating PDF files from a template in a Hangfire process?
If you are open to commercial solutions, you can try Telerik reporting and export it as pdf programmatically. You define your report and then invoke it to generate PDF on the server side, finally email the byte[] as email attachment. You can now kickoff this process using Hangfire job.
Here is a pseudo code assuming you have defined the structure of your report, Please look here for more details on how to create your report programatically.
public void GenerateAndEmailReport()
{
var reportSource = new InstanceReportSource();
Telerik.Reporting.Report report = new MyReport();
//populate data into report
reportSource.ReportDocument = report;
var reportProcessor = new ReportProcessor();
reportSource.ReportDocument = report;
var info = new Hashtable();
var result= reportProcessor.RenderReport("PDF", reportSource, info);
byte[]reportBytes = result.DocumentBytes;
SendEmail(reportBytes, "myreport.pdf"); // a method that takes the bytes and attach it to email.
}
Additional references from telerik.
send report as email
Generating PDF in console application
Saving a report programmatically

How can I run Internet Explorer Selenium tests as a specific domain user?

I have a ASP.NET MVC website that uses Windows Authentication to control access. I would like to have a specflow selenium test that checks the configuration is correct by attempting to visit the site as a non-authorised user.
As we're using domain accounts to control access there isn't a username/password login screen. The credentials of the current user are automatically passed to the site by the browser.
So for my Selenium test I need to be able to run Internet Explorer as a specific user.
I have found a number of articles about windows impersonation and I can switch to my test user during the running of the test (using the code from http://support.microsoft.com/kb/306158). However if I then create an InternetExplorerDriver it starts internet explorer with my credentials rather than the test user's (although this question and answer suggests that it should work https://sqa.stackexchange.com/questions/2277/using-selenium-webdriver-with-windows-authentication).
I can also explicitly start an Internet Explorer process as my test user, but I can't see a way of binding an InternetExplorerDriver to an already running Internet Explorer process, so this may be a dead end.
My code, basically taken from the MSDN page above is below. In the debugger I can see that WindowsIdentity.GetCurrent().Name is "testUser" in all the steps of the test.
namespace MyProject.Specs
{
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using TechTalk.SpecFlow;
[Binding]
public class AuthorisationSteps
{
public const int LOGON32_LOGON_INTERACTIVE = 2;
public const int LOGON32_PROVIDER_DEFAULT = 0;
private static WindowsImpersonationContext impersonationContext;
private static IWebDriver driver;
[BeforeScenario]
public static void impersonateUser()
{
if (!impersonateValidUser("testUser", "testDomain", "password"))
{
throw new Exception();
}
driver = new InternetExplorerDriver();
}
[AfterScenario]
public static void cleanupUser()
{
undoImpersonation();
driver.Quit();
}
[Given(#"I am an unauthorised user")]
public void GivenIAmAnUnauthorisedUser()
{
var temp = WindowsIdentity.GetCurrent().Name;
}
[When(#"I go to the home page")]
public void WhenIGoToTheHomePage()
{
var temp = WindowsIdentity.GetCurrent().Name;
driver.Navigate().GoToUrl(BaseUrl);
}
[Then(#"I should see an error page")]
public void ThenIShouldSeeAnErrorPage()
{
var temp = WindowsIdentity.GetCurrent().Name;
Assert.That(driver.Title.Contains("Error"));
}
[DllImport("advapi32.dll")]
public static extern int LogonUserA(String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
private static bool impersonateValidUser(String userName, String domain, String password)
{
WindowsIdentity tempWindowsIdentity;
var token = IntPtr.Zero;
var tokenDuplicate = IntPtr.Zero;
if (RevertToSelf())
{
if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, ref token) != 0)
{
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
if (impersonationContext != null)
{
CloseHandle(token);
CloseHandle(tokenDuplicate);
return true;
}
}
}
}
if (token != IntPtr.Zero)
{
CloseHandle(token);
}
if (tokenDuplicate != IntPtr.Zero)
{
CloseHandle(tokenDuplicate);
}
return false;
}
private static void undoImpersonation()
{
impersonationContext.Undo();
}
}
}
We have many enterprise clients that use Windows Authentication for intranet facing applications and we are starting to run many Selenium tests for confirmation, regression, etc.
We've taken the helpful code from Steven's answer and refactored it into a re-usable class similar to other Impersonate posts that just weren't working for us because we wanted the tests to work both locally in development and deployed as part of the Visual Studio Team System release process.
The uri method was not working locally and neither were impersonating methods using Win32 native methods.
This one worked so here it is.
Example of a test using Steven's code refactored into a helper
[TestMethod]
public void ThisApp_WhenAccessedByUnathorizedUser_ShouldDisallowAccess()
{
string userName = "ThisAppNoAccess";
string password = "123456";
string domainName = Environment.MachineName;
using (new Perkins.Impersonator(userName, domainName, password))
{
// - Use Remote Web Driver to hook up the browser driver instance launched manually.
using (var driver = new RemoteWebDriver(new Uri("http://localhost:9515"), DesiredCapabilities.Chrome()))
{
var desiredUri = Helper.Combine(Helper.BaseURL, "/ThisApp/#/appGrid");
TestContext.WriteLine("desiredUri: {0}", desiredUri);
driver.Navigate().GoToUrl(desiredUri);
Helper.WaitForAngular(driver);
var noPermissionNotificationElement = driver.FindElementByXPath("//div[#ng-show='!vm.authorized']/div/div/div/p");
var showsNoPermissionNotification = noPermissionNotificationElement.Text.Contains("You do not have permissions to view ThisApp.");
Assert.AreEqual(true, showsNoPermissionNotification, "The text `You do not have permissions to view ThisApp.` is not being displayed!");
}
}
}
The helper class
// Idea from http://stackoverflow.com/a/34406336/16008
// - Launch the browser driver manually with other user's credentials in background
public class Perkins
{
public class Impersonator : IDisposable
{
Process _driverProcess = null;
string _driverPath = #"chromedriver.exe";
/// <summary>
/// Impersonates the specified user account by launching the selenium server under that account. Connect to it via RemoteWebDriver and localhost on port 9515.
/// </summary>
/// <remarks>
/// We may later want to enhance this by allowing for different ports, etc.
/// </remarks>
/// <param name="userName">Name of the user</param>
/// <param name="domainName">Name of the domain or computer if using a local account.</param>
/// <param name="password">The password</param>
public Impersonator(string userName, string domainName, string password)
{
ProcessStartInfo processStartInfo = new ProcessStartInfo(_driverPath);
processStartInfo.UserName = userName;
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (char c in password)
{
securePassword.AppendChar(c);
}
processStartInfo.Password = securePassword;
processStartInfo.Domain = domainName; // this is important, mcollins was getting a 'stub received bad data' without it, even though rglos was not
processStartInfo.UseShellExecute = false;
processStartInfo.LoadUserProfile = true; // this seemed to be key, without this, I get Internal Server Error 500
Thread startThread = new Thread(() =>
{
_driverProcess = Process.Start(processStartInfo);
_driverProcess.WaitForExit();
})
{ IsBackground = true };
startThread.Start();
}
public void Dispose()
{
// - Remember to close/exit/terminate the driver process and browser instance when you are done.
if (_driverProcess != null)
{
// Free managed resources
if (!_driverProcess.HasExited)
{
_driverProcess.CloseMainWindow();
_driverProcess.WaitForExit(5000);
// Kill the process if the process still alive after the wait
if (!_driverProcess.HasExited)
{
_driverProcess.Kill();
}
_driverProcess.Close();
}
_driverProcess.Dispose();
_driverProcess = null;
}
}
}
}
Perhaps this will help someone else with the same issue.
This is in fact possible. I ran into the exact problem you had. Basically, here are the steps you need to do.
Launch the browser driver manually with other user's credentials in background
Process driverProcess;
string driverPath; // The path to Selenium's IE driver.
ProcessStartInfo info = new ProcessStartInfo(driverPath)
{
UserName = "UserName", // The user name.
Password = new SecureString(), // The password for the user.
UseShellExecute = false,
LoadUserProfile = true,
Arguments = "about:blank"
};
// Start the driver in background thread
Thread startThread = new Thread(
() => {
try
{
driverProcess = Process.Start(info);
driverProcess.WaitForExit();
}
catch
{
// Close the process.
}
})
{
IsBackground = true
};
startThread.Start();
Use Remote Web Driver to hook up the browser driver instance launched manually.
var remoteDriver = new RemoteWebDriver(Uri("http://localhost:5555"), DesiredCapabilities.InternetExplorer());
Remember to close/exit/terminate the driver process and browser instance when you are done.
// Close the process when done.
if (driverProcess != null)
{
// Free managed resources
if (!driverProcess.HasExited)
{
driverProcess.CloseMainWindow();
driverProcess.WaitForExit(5000);
// Kill the process if the process still alive after the wait
if (!driverProcess.HasExited)
{
driverProcess.Kill();
}
driverProcess.Close();
}
driverProcess.Dispose();
driverProcess = null;
}
This similar question links to this Microsoft support article. Essentially you need
System.Security.Principal.WindowsImpersonationContext impersonationContext;
impersonationContext =
((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
IWebDriver webDriver = new InternetExplorerDriver();
// do your stuff here.
impersonationContext.Undo();
There's additional code in the support article about impersonating a specific user.
Do you have a couple of old PCs? Or the capacity for some virtual machines?
If so, build a Selenium Grid set-up, and configure one to automatically login as the desired domain user and one as a non-domain user.
http://code.google.com/p/selenium/wiki/Grid2
I was having same problem when I was doing automation project for web based application which required window authentication. However, I have achieved this with using firefox, following are the steps to achieve it.
FIREFOX SETUP
OPEN RUN DIALOG OF YOUR SYSTEM AND TYPE 'firefox.exe -p' (CLOSE YOUR FIREFOX BROWSER BEFORE RUNNING THIS COMMAND) http://www.wikihow.com/Create-a-Firefox-Profile
CLICK ON CREATE PROFILE AND GIVE A NAME AS REQURIED
SELECT CREATED PROFILE AND START BROWSER AND OPEN ADD-ONS MANAGER (TOOLS - ADD-ONS)
SEARCH FOR 'AutoAuth' AND INSTALL IT. IT WILL ASK FOR RESTART, DO IT
ONCE THE FIREFOX IS RESTARTED, THAN OPEN URL IT WILL ASK YOU FOR AUTHENTICATION
ENTER USERNAME AND PASSWORD - SUBMIT IT, FIREFOX WILL ASK YOU TO REMEMBER THE PASSWORD
CLICK ON REMEMBER AND IT WILL SAVE THE PASSWORD IN FIREFOX PROFILE
COPY CREATED FIREFOX PROFILE AND SAVE IT TO REQUIRED FOLDER
IN YOUR SELENIUM SCRIPT CALL ABOVE CREATED PROFILE WITH FIREFOX DRIVER AND PASS THE SAME URL, IT WILL NOT ASK FOR AUTHENTICATION DIALOG
This is working very successfully in my project.
We use https://stackoverflow.com/a/31540010/3489693 approach for IE and Chrome over 2 years. It works fine
So it seems the problem that the question is trying to circumvent has to do with NTLM Auto Login. See Google Chrome and NTLM Auto Login Using Windows Authentication
The solutions above did not work for me since the auto-login would successfully authenticate with any user on my system, so it didn't matter which user I used for impersonation.
However, I noticed that you can outsmart auto-login by replacing localhost with any other domain name, such as the local IP address. No impersonation required :)
This may / may not work.
Try to launch your site in "CHROME".
Hit F-12, go to Application Tab -> Cookies -> Click on your site link. on left hand side look for something that represent your session id, may be JSESSIONID or similar that represents user's session, copy that.
Now open your Internet Explorer,
hit F-12 and manually create that JSESSIONID ( or similar key ) by running this command in console window
document.cookie = "JSESSIONID=your-session-id-from-chrome"
hit play button to execute script
Refresh your browser

Serving an iCalendar file in ASPNET MVC with authentication

I'm trying to serve an iCalendar file (.ics) in my MVC application.
So far it's working fine. I have an iPhone subscribing to the URL for the calendar but now I need to serve a personalised calendar to each user.
When subscribing to the calendar on the iPhone I can enter a username and password, but I don't know how to access these in my MVC app.
Where can I find details of how the authentication works, and how to implement it?
It turns out that Basic Authentication is what is required. I half had it working but my IIS configuration got in the way. So, simply returning a 401 response when there is no Authorization header causes the client (e.g. iPhone) to require a username/password to subscribe to the calendar.
On the authorization of the request where there is an Authorization request header, the basic authentication can be processed, retrieving the username and password from the base 64 encoded string.
Here's some useful code for MVC:
public class BasicAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var auth = filterContext.HttpContext.Request.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var encodedDataAsBytes = Convert.FromBase64String(auth.Replace("Basic ", ""));
var value = Encoding.ASCII.GetString(encodedDataAsBytes);
var username = value.Substring(0, value.IndexOf(':'));
var password = value.Substring(value.IndexOf(':') + 1);
if (MembershipService.ValidateUser(username, password))
{
filterContext.HttpContext.User = new GenericPrincipal(new GenericIdentity(username), null);
}
else
{
filterContext.Result = new HttpStatusCodeResult(401);
}
}
else
{
if (AuthorizeCore(filterContext.HttpContext))
{
var cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
else
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusDescription = "Unauthorized";
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Calendar\"");
filterContext.HttpContext.Response.Write("401, please authenticate");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new EmptyResult();
filterContext.HttpContext.Response.End();
}
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Then, my controller action looks like this:
[BasicAuthorize]
public ActionResult Calendar()
{
var userName = HttpContext.User.Identity.Name;
var appointments = GetAppointments(userName);
return new CalendarResult(appointments, "Appointments.ics");
}
I found this really helpful, but i hit a few problems during the development and i thought i would share some of them to help save other people some time.
I was looking to get data from my web application into the calendar for an android device and i was using discountasp as a hosting service.
The first problem i hit was that the validation did not work when uploaded to the server, stangely enough it was accepting my control panel login for discountasp but not my forms login.
The answer to this was to turn off Basic Authentication in IIS manager. This resolved the issue.
Secondly, the app i used to sync the calendar to the android device was called iCalSync2 - its a nice app and works well. But i found that it only worked properly when the file was delivered as a .ics (duh for some reason i put it as a .ical.. it must have been late) and i also had to choose the webcal option
Lastly i found i had to add webcal:// to the start of my url instead of http://
Also be careful as the code posted above ignores the roles input variable and always passes nothing so you might need to do some role based checks inside your calendar routine or modify the code above to process the roles variable.

Using Redemption, How can I add a default Signature to an outgoing Email Message?

Here is the code that I am using. I have spent some time looking at the Redemption objects, but, nothing jumps out at me:
public static bool PopEmail(string domainUserName, string mSubject, string mBody, string mTo, string mCc = "", string mBcc = "", List<String> fileAttachments = null)
{
log.Info("Starting to Pop Outlook Email Message");
RDOSession oSession = new RDOSession();
try
{
oSession.LogonExchangeMailbox(domainUserName, string.Empty);
if (oSession.LoggedOn)
{
RDOMail oMail = oSession.GetDefaultFolder(rdoDefaultFolders.olFolderOutbox).Items.Add("IPM.Note");
oMail.Subject = mSubject;
oMail.Body = mBody;
oMail.To = mTo;
oMail.CC = mCc;
oMail.BCC = mBcc;
if (fileAttachments != null)
{
foreach (string file in fileAttachments)
{
object newFile = file;
oMail.Attachments.Add(newFile, Type.Missing, Type.Missing, Type.Missing);
newFile = null;
}
}
oMail.Display();
Marshal.FinalReleaseComObject(oMail);
oMail = null;
}
oSession.Logoff();
Marshal.FinalReleaseComObject(oSession);
oSession = null;
GC.Collect();
GC.WaitForPendingFinalizers();
log.Info("Outlook Email has been Popped.");
return true;
}
catch (Exception)
{
log.Error("Outlook Pop Email Failed.");
throw;
}
}
Thank you,
The signature is actually inserted by the Outlook inspector object on instantiation, so if your code is running inside an Outlook addin you could probably try saving the item and then reopening it from the OOM as a _MailItem via _Namespace.GetItemFromId and then calling its GetInspector method (you don't actually have to do anything with the returned inspector reference).
Note that I haven't tried this with an item initially created via RDO. I usually create the items in OOM and then create an RDO wrapper.
If your code is running outside of Outlook you'd have to use OLE to get a reference to its _Application object and then pull the _Namespace object from there. If you are using standalone MAPI without Outlook installed the signature functionality is completely unavailable.
I have added code to append to the oMail.HTMLBody which reads the signature from the C:\Users\UserName\AppData\Roaming\Microsoft\Signatures folder. This file is generated via a plug in written by one of our developers that reads information from Exchange to determine User Name, Title, Phone, Fax, etc.

Resources