问题
So I have been working on a way to automate searches on FOPE. Microsoft annoyingly doesn't provide an API to access the data that it gathers so I have to emulate a webbrowser with all the quirks of logging in, cookies, scrapping pages and then using that data to further scrape certain links off of it. The last few parts I should be able to get, but the setup that MS uses for FOPE just perplexes me. I'll post what I know and what I have so hopefully some other admin or coder can help. Maybe the information in here will help other admins with this issue since it seems that MS doesn't actually feel like making it user friendly anyway.
https://sts.messaging.microsoft.com/login.asp
<form name="signin" method="post" id="signin" action="">
<span class="normal">Sign in:</span>
<fieldset>
<label for="email">User name:</label>
<input type="text" id="email" name="email" maxlength="384" value="" /><br />
<label for="Password">Password:</label>
<input type="password" id="Password" name="Password" maxlength="256" /><br />
<input type="submit" id="submit_signin" name="SignIn" value="Sign in" />
</fieldset>
</form>
After logging in it takes me to https://admin.messaging.microsoft.com/Home.mvc/ (after a few automatic redirects). Here I click on the Tools button which takes me to the search page.
https://admin.messaging.microsoft.com/TraceMessage.mvc/Index/123456
<fieldset>
<div class="legend">
<h3><span>Search Parameters</span></h3>
</div>
<ul class="fieldset">
<li class="row large">
<div class="field"><div class="shell">
<label for="Sender">* Sender address:</label>
<span class="input"><span><input type="text" id="Sender" name="Sender" value="" /></span></span>
</div></div>
<div class="field last"><div class="shell">
<label for="Recipient">* Recipient address:</label>
<span class="input"><span><input type="text" id="Recipient" name="Recipient" value="" /></span></span>
</div></div>
</li>
<li class="row large">
<div class="field"><div class="shell">
<label for="Start">* Start date:</label>
<span class="input"><span><input type="text" id="Start" name="Start" value="10/29/2012 1:19:20 PM"/></span></span>
</div></div>
<div class="field last"><div class="shell">
<label for="End">* End date:</label>
<span class="input"><span><input type="text" id="End" name="End" value="10/31/2012 1:19:20 PM"/></span></span>
</div></div>
</li>
<li class="row large">
<div class="field"><div class="shell">
<label for="Adjust">Time zone:</label>
<div class="input select">
<select id="Adjust" name="Adjust">
<option value="-5" selected="selected" >
UTC-5</option>
</select>
</div></div></div>
<div class="field last"><div class="shell">
<label for="MessageId">Message ID:</label>
<span class="input"><span><input type="text" id="MessageId" name="MessageId" value=""/></span></span>
</div></div>
</li>
</ul>
<br />* Fields marked with the star are required.
</fieldset>
<div>
<span class="buttons">
<input type="submit" id="submit" value="Search" />
</span>
</div>
On this page I would need to insert a sender (user@exampleSender.com) and the recipient domain (exampleReceiver.com). The results get populated in the page after using some JS to show that is working.
I've taken a look at Post Username and Password to login page programmatically as well as Login to website, via C# and neither solution seems to work at all. I'm hoping that some FOPE admin out there can help me figure out whats going on and how to make this work. If you need me to try something out or provide more information, please just let me know.
Update #1
So I have made a little headway on this. The XAML side isn't anything special (just a textbox with space for a URL, a button to begin the search, and a WPF WebBrowser.
private void executeButton_Click(object sender, RoutedEventArgs e)
{
DateTime timeNow = DateTime.Now;
TimeZone zone = TimeZone.CurrentTimeZone;
TimeSpan offset = zone.GetUtcOffset(DateTime.Now);
this.wbControl.Navigate("https://admin.messaging.microsoft.com/TraceMessage.mvc/Trace/123456?s=" + Uri.EscapeUriString(this.inputTB.Text) + "&r=example.com&d=" + Uri.EscapeUriString(timeNow.AddDays(-29).ToString()) + "&e=" + Uri.EscapeUriString(timeNow.ToString()) + "&a=" + Uri.EscapeUriString(offset.Hours.ToString()));
}
private void wbControl_LoadCompleted_1(object sender, NavigationEventArgs e)
{
string email = "AUSERNAME";
string password = "APASSWORD";
dynamic doc = this.wbControl.Document;
doc.GetElementById("email").SetAttribute("value", email);
doc.GetElementById("Password").SetAttribute("value", password);
doc.GetElementById("submit_signin").InvokeMember("click");
}
As this is currently for testing of the functionality certain aspects are hard-coded (usernames and passwords) that won't be that way in the end. At this point I am stuck when it comes to the InvokeMember("click") portion of the code. It seems to error out with the following:
An exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in >Unknown Module. but was not handled in user code
Additional information: 'System.MarshalByRefObject.InvokeMember(string, >System.Reflection.BindingFlags, System.Reflection.Binder, object[], >System.Reflection.ParameterModifier[], System.Globalization.CultureInfo, string[])' is >inaccessible due to its protection level
If there is a handler for this exception, the program may be safely continued.
So I've wrapped it in a blank try / catch but in essence it still doesn't submit the form. Does anyone have any suggestions?
Update #2
Seems I was sitting on the answer to submit the form the whole time. Another reason of why dynamic should be used carefully as you will not get any IntelliSense help. All I did to submit the form was:
doc.GetElementById("submit_signin").Click();
Now I just need to work on how to get the results from the search (which is only in JavaScript) and determine when the page that loads after the form is submitted is finished loading.
回答1:
Ok, so after much fighting I got a solution working. I'll post the .cs code if anyone needs to replicate it / improve on it. As of now it works pretty well though:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections;
using HtmlAgilityPack;
using System.Data;
namespace TestApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void executeButton_Click(object sender, RoutedEventArgs e)
{
DateTime timeNow = DateTime.Now;
TimeZone zone = TimeZone.CurrentTimeZone;
TimeSpan offset = zone.GetUtcOffset(DateTime.Now);
string email = "USERNAME";
string password = "PASSWORD";
List<string> URLList = new List<string>();
foreach (string domain in Domains)
{
URLList.Add("https://admin.messaging.microsoft.com/TraceMessage.mvc/AsyncMessageList/123456?s=" + Uri.EscapeUriString(this.inputTB.Text) + "&r=" + domain + "&d=" + Uri.EscapeUriString(timeNow.AddDays(-29).ToString()) + "&e=" + Uri.EscapeUriString(timeNow.ToString()) + "&a=" + Uri.EscapeUriString(offset.Hours.ToString()));
}
var domainQueue = new Queue<string>(URLList);
Action navigateQueue = () =>
{
if (domainQueue.Count != 0)
{
this.wbControl.Navigate(domainQueue.Dequeue());
}
else
{
MessageBox.Show("Completed");
}
};
this.wbControl.LoadCompleted += (o, e0) =>
{
if (this.wbControl.IsLoaded == true)
{
dynamic doc = this.wbControl.Document;
try
{
doc.GetElementById("email").SetAttribute("value", email);
doc.GetElementById("Password").SetAttribute("value", password);
doc.GetElementById("submit_signin").Click();
}
catch
{
}
if (e0.Uri.AbsolutePath.Contains("AsyncMessageList"))
{
List<string> DetailsList = new List<string>();
DetailsList.AddRange(ExtractAllAHrefTags(doc));
foreach (string href in DetailsList)
{
domainQueue.Enqueue(href);
}
navigateQueue();
}
if (e0.Uri.AbsolutePath.Contains("Details"))
{
resultsTB.Text += ParseEntries(doc);
navigateQueue();
}
}
};
navigateQueue();
}
private string ParseEntries(dynamic inputDoc)
{
HtmlAgilityPack.HtmlDocument docHAP = new HtmlAgilityPack.HtmlDocument();
docHAP.LoadHtml(inputDoc.Body.InnerHtml.ToString());
string csv = "";
foreach (HtmlNode emNode in docHAP.DocumentNode.SelectNodes("//em"))
{
if (emNode.Attributes["class"] == null)
{
csv += "\"" + emNode.InnerText.ToString() + "\",";
}
}
csv = csv.Remove(csv.Length - 1, 1) + "\"" + Environment.NewLine;
return csv;
}
private List<string> ExtractAllAHrefTags(dynamic inputDoc)
{
HtmlAgilityPack.HtmlDocument docHAP = new HtmlAgilityPack.HtmlDocument();
docHAP.LoadHtml(inputDoc.Body.InnerHtml.ToString());
List<string> hrefTags = new List<string>();
try
{
foreach (HtmlNode link in docHAP.DocumentNode.SelectNodes("//a[@href]"))
{
HtmlAttribute att = link.Attributes["href"];
hrefTags.Add("https://" + this.wbControl.Source.Host.ToString() + System.Web.HttpUtility.HtmlDecode(att.Value));
}
}
catch
{
}
return hrefTags;
}
private List<string> Domains
{
get
{
List<string> currentDomains = new List<string>();
currentDomains.Add("example.com");
currentDomains.Add("sub.example.com");
currentDomains.Add("it.example.com");
return currentDomains;
}
}
}
}
来源:https://stackoverflow.com/questions/13165432/automating-microsoft-fope-forefront-online-protection-for-exchange-searches