328 lines
14 KiB
JavaScript
328 lines
14 KiB
JavaScript
/*
|
|
$Revision$
|
|
$Id$
|
|
|
|
File: Exchange.js
|
|
|
|
Description: Exchange EWS koppeling
|
|
Dit bestand maakt voor één ruimte-mailbox Arguments(0) de xml-bestanden
|
|
met calenderitems aan sinds de laatste sync-actie, Arguments(1)
|
|
|
|
Parameters (0) e-mail adres van de zaal
|
|
(1) - syncstate van de zaal of
|
|
"EXCHFULL" alles (in de toekomst tot config.fullfuture)
|
|
*/
|
|
|
|
var fso = new ActiveXObject("Scripting.FileSystemObject");
|
|
var zaalemail = WScript.Arguments(0);
|
|
var zaalsync = WScript.Arguments(1); // Gebruik EXCHFULL voor alles in bepaalde periode
|
|
|
|
var inifile = ".\\exchange.config.js";
|
|
var f = fso.OpenTextFile(inifile, 1); // ForReading
|
|
var config = eval('(' + f.ReadAll() + ')')
|
|
f.Close();
|
|
config.loglevel = config.loglevel || 0;
|
|
|
|
WScript.Echo("Connecting to " + config.endpointurl)
|
|
// ---
|
|
// kunnen we niets mee doExchange(config.endpointurl, "<m:GetRoomLists />", "roomlist.xml");
|
|
|
|
if (!Date.prototype.toISOString) {
|
|
Date.prototype.toISOString = function() {
|
|
function pad(n) { return n < 10 ? '0' + n : n }
|
|
return this.getUTCFullYear() + '-'
|
|
+ pad(this.getUTCMonth() + 1) + '-'
|
|
+ pad(this.getUTCDate()) + 'T'
|
|
+ pad(this.getUTCHours()) + ':'
|
|
+ pad(this.getUTCMinutes()) + ':'
|
|
+ pad(this.getUTCSeconds()) + 'Z';
|
|
};
|
|
}
|
|
|
|
var oCrypto = new ActiveXObject("SLNKDWF.Crypto");
|
|
|
|
var sha = oCrypto.hex_sha1(zaalsync); // Gemakkelijker verschillen te zien
|
|
WScript.Echo("\n\n==== Room: " + zaalemail + "\nOld sync hash: " + sha);
|
|
if (zaalsync != "EXCHFULL")
|
|
{
|
|
var soapRequest = '<m:SyncFolderItems'
|
|
+ ' xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" '
|
|
+ ' xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">'
|
|
+ ' <m:ItemShape>'
|
|
+ ' <t:BaseShape>IdOnly</t:BaseShape>'
|
|
+ ' <t:AdditionalProperties>'
|
|
+ ' <t:FieldURI FieldURI="calendar:CalendarItemType" />'
|
|
+ ' <t:FieldURI FieldURI="calendar:Start" />'
|
|
+ ' </t:AdditionalProperties>'
|
|
+ ' </m:ItemShape>'
|
|
+ ' <m:SyncFolderId>'
|
|
+ ' <t:DistinguishedFolderId Id="calendar">'
|
|
+ ' <t:Mailbox>'
|
|
+ ' <t:EmailAddress>' + zaalemail + '</t:EmailAddress>'
|
|
+ ' </t:Mailbox>'
|
|
+ ' </t:DistinguishedFolderId>'
|
|
+ ' </m:SyncFolderId>'
|
|
+ ' <m:SyncState>' + zaalsync + '</m:SyncState>'
|
|
+ ' <m:MaxChangesReturned>' + config.maxchange + '</m:MaxChangesReturned>'
|
|
+ ' <m:SyncScope>NormalItems</m:SyncScope>'
|
|
+ '</m:SyncFolderItems>';
|
|
}
|
|
else
|
|
{
|
|
var dtFrom = new Date();
|
|
dtFrom.setHours(0, 0, 0, 0);
|
|
var dtTo = new Date(dtFrom);
|
|
dtFrom.setDate(dtFrom.getDate() - config.fullpast);
|
|
dtTo.setDate(dtTo.getDate() + config.fullfuture);
|
|
WScript.Echo("Full syncing from " + dtFrom.toISOString() + " to " + dtTo.toISOString() + " (" + (config.fullfuture + config.fullpast) + " days)");
|
|
var soapRequest = '<m:FindItem Traversal="Shallow"'
|
|
+ ' xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" '
|
|
+ ' xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">'
|
|
+ ' <m:ItemShape>'
|
|
+ ' <t:BaseShape>IdOnly</t:BaseShape>'
|
|
+ ' <t:AdditionalProperties>'
|
|
+ ' <t:FieldURI FieldURI="calendar:CalendarItemType" />'
|
|
+ ' <t:FieldURI FieldURI="calendar:Start" />'
|
|
+ ' </t:AdditionalProperties>'
|
|
+ ' </m:ItemShape>'
|
|
//+ ' <m:CalendarView StartDate="2016-01-01T17:30:24.127Z" EndDate="2016-05-20T17:30:24.127Z" />'
|
|
+ ' <m:CalendarView StartDate="' + dtFrom.toISOString() + '" EndDate="' + dtTo.toISOString() + '" />'
|
|
+ ' <m:ParentFolderIds>'
|
|
+ ' <t:DistinguishedFolderId Id="calendar">'
|
|
+ ' <t:Mailbox>'
|
|
+ ' <t:EmailAddress>' + zaalemail + '</t:EmailAddress>'
|
|
+ ' </t:Mailbox>'
|
|
+ ' </t:DistinguishedFolderId>'
|
|
+ ' </m:ParentFolderIds>'
|
|
+ '</m:FindItem>';
|
|
}
|
|
|
|
var room_id = safefilename(zaalemail);
|
|
var exch = doExchange(config.endpointurl, soapRequest, null);
|
|
if (!exch)
|
|
WScript.Quit(1);
|
|
|
|
var xmlDoc = exch.doc;
|
|
var rescode = exch.response;
|
|
if (exch.response != 'NoError')
|
|
{
|
|
WScript.Echo("Geen NoError responsecode in response: " + exch.response);
|
|
WScript.Quit(1);
|
|
}
|
|
|
|
if (zaalsync != "EXCHFULL")
|
|
{
|
|
var newsyncstate = xmlDoc.selectSingleNode("//m:SyncState");
|
|
var sha = oCrypto.hex_sha1(newsyncstate.text); // Gemakkelijker verschillen te zien
|
|
WScript.Echo("New syncstate: " + newsyncstate.text + "\nHash: " + sha + "\nlength: " + newsyncstate.text.length);
|
|
}
|
|
|
|
var calItems = xmlDoc.selectNodes("//t:CalendarItem");
|
|
if (calItems.length == 0)
|
|
{
|
|
WScript.Echo("No calitems, no need to update syncstate");
|
|
WScript.Quit(1);
|
|
}
|
|
// Stukje robuustheid
|
|
// Eerst alle *andere* sync_*.xml bestanden verwijderen om te voorkomen dat
|
|
// per ongeluk de verkeerde syncstate in onze ruimte terecht komt
|
|
try
|
|
{
|
|
fso.DeleteFile(config.xmlfolder + "Sync_*.xml");
|
|
// Als hierboven geen files gevonden zijn komen we in de exception
|
|
// en niet in onderstaande echo.
|
|
WScript.Echo("Oude syncfile is verwijderd.");
|
|
}
|
|
catch(e)
|
|
{
|
|
// Neem aan dat gelukkig geen files zijn gevonden
|
|
}
|
|
__Log2File("Sync_" + room_id + ".xml", xmlDoc.xml); // moet straks gesynced
|
|
WScript.Echo("Fetching " + calItems.length + " calendar items");
|
|
var curDate = new Date();
|
|
for (var i = 0; i < calItems.length; i++)
|
|
{
|
|
var calItem = calItems[i];
|
|
var Id = calItem.selectSingleNode("t:ItemId").getAttribute("Id");
|
|
var ChangeKey = calItem.selectSingleNode("t:ItemId").getAttribute("ChangeKey");
|
|
var start = calItem.selectSingleNode("t:Start").text;
|
|
var type = calItem.selectSingleNode("t:CalendarItemType").text;
|
|
//var subject = calItem.selectSingleNode("t:CalendarItemType").text;
|
|
var cal_id = room_id + "_" + i;
|
|
WScript.Echo(" " + cal_id + ": " + start + " type: " + type);
|
|
|
|
if (type == "Single" || type == "Occurrence" || type == "Exception")
|
|
{
|
|
var startDate = internal_parsedate(null, start);
|
|
if (startDate.getTime() < curDate.getTime())
|
|
{
|
|
WScript. Echo(" Appointment in the past; skip it.");
|
|
continue;
|
|
}
|
|
var fname = "CalItem_" + cal_id + ".xml";
|
|
getCalenderItem('<t:ItemId Id="' + Id + '" ChangeKey="' + ChangeKey + '" />', fname);
|
|
}
|
|
else if (type == "RecurringMaster") // Haal Occurences en Exceptions op
|
|
{
|
|
getCalenderItem('<t:ItemId Id="' + Id + '" ChangeKey="' + ChangeKey + '" />', "receiveMasterItem_" + cal_id + ".xml");
|
|
// However the compromise with CalendarView is NO restrictions are permitted besides Start and End Date. None.
|
|
// Dat zou nog redelijk zijn als je het tot een specifieke Recurring master kon beperken. Dat kan echter niet.
|
|
// Dan maar op index ophalen
|
|
for (var j = 1; j <= config.maxrecurring; j++)
|
|
{
|
|
var rec_id = cal_id + "_" + j;
|
|
var fname = "CalItem_" + rec_id + ".xml";
|
|
// Wel XML ophalen, maar nog niet wegschrijven; eerst checken of het een toekomstige appointment betreft
|
|
var exch = getCalenderItem('<t:OccurrenceItemId RecurringMasterId="' + Id + '" InstanceIndex="' + j + '" />', null);
|
|
var xmlDoc = exch.doc;
|
|
WScript.Echo(" Child: " + rec_id + " " + exch.response);
|
|
if (exch.response == 'ErrorCalendarOccurrenceIndexIsOutOfRecurrenceRange')
|
|
{
|
|
WScript.Echo(" Tijd om te stoppen");
|
|
break;
|
|
}
|
|
if (exch.response == 'ErrorCalendarOccurrenceIsDeletedFromRecurrence')
|
|
{
|
|
WScript.Echo(" Deleted negeren we");
|
|
continue;
|
|
}
|
|
if (exch.response == 'ErrorAccessDenied')
|
|
{
|
|
WScript.Echo(" Access Denied negeren we");
|
|
continue;
|
|
}
|
|
var start = xmlDoc.selectSingleNode("//t:Start").text;
|
|
var type = xmlDoc.selectSingleNode("//t:CalendarItemType").text;
|
|
var startDate = internal_parsedate(null, start);
|
|
if (startDate.getTime() < curDate.getTime())
|
|
{
|
|
WScript. Echo(" Appointment in the past; skip it.");
|
|
continue;
|
|
}
|
|
// Toekomstig: save
|
|
__Log2File(fname, xmlDoc.xml);
|
|
WScript.Echo(" Start: " + start + " " + type);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getCalenderItem(filter, fname)
|
|
{
|
|
var soapRequest = '<m:GetItem xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" '
|
|
+ ' xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">'
|
|
+ ' <m:ItemShape>'
|
|
+ ' <t:BaseShape>AllProperties</t:BaseShape>' // TODO: Dit kan heel traag zijn. Alleen opvragen wat gewenst is
|
|
+ ' <t:AdditionalProperties>'
|
|
+ ' <t:ExtendedFieldURI DistinguishedPropertySetId="Meeting" PropertyId="35" PropertyType="Binary" />' // 35=PidLidCleanGlobalObjectId
|
|
+ ' <t:ExtendedFieldURI DistinguishedPropertySetId="Meeting" PropertyId="3" PropertyType="Binary" />' // 3=PidLidGlobalObjectId
|
|
+ ' <t:ExtendedFieldURI DistinguishedPropertySetId="Meeting" PropertyId="40" PropertyType="String" />' // 40=PidLidOldLocation
|
|
+ ' </t:AdditionalProperties>'
|
|
+ ' </m:ItemShape>'
|
|
+ ' <m:ItemIds>'
|
|
+ filter // Dit schijnen er ook meer te mogen zijn
|
|
+ ' </m:ItemIds>'
|
|
+ '</m:GetItem>';
|
|
|
|
return exch = doExchange(config.endpointurl, soapRequest, fname);
|
|
}
|
|
|
|
function doExchange(url, body, fname)
|
|
{
|
|
var xmlDoc = doSOAP(url, body, fname);
|
|
if (!xmlDoc)
|
|
return null;
|
|
|
|
var strQuery = "s:Envelope/s:Body/m:ResponseMessages/m:SyncFolderItemsResponseMessage/m:ResponseCode";
|
|
var strQuery = "//m:ResponseCode";
|
|
var rescode = xmlDoc.selectSingleNode(strQuery);
|
|
if (!rescode)
|
|
{
|
|
WScript.Echo("Geen responsecode in response");
|
|
WScript.Quit(1);
|
|
}
|
|
return { response: rescode.text, doc: xmlDoc };
|
|
}
|
|
|
|
function doSOAP(url, body, fname)
|
|
{
|
|
var envsoap = '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" '
|
|
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
|
|
+ ' xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" '
|
|
+ ' xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" '
|
|
+ ' xmlns:wsi="http://ws-i.org/schemas/conformanceClaim/">'
|
|
+ ' <soap:Header>'
|
|
+ ' <t:RequestServerVersion Version=\"Exchange2010\" />'
|
|
+ ' </soap:Header>'
|
|
+ ' <soap:Body>'
|
|
+ body
|
|
+ ' </soap:Body>'
|
|
+ '</soap:Envelope>';
|
|
var res = doHTTP("POST", url, envsoap);
|
|
if (!res)
|
|
return null;
|
|
if (fname)
|
|
__Log2File(fname, res.responseText);
|
|
var xmlDoc = new ActiveXObject("MSXML2.DOMDocument.6.0");
|
|
xmlDoc.loadXML(res.responseText);
|
|
xmlDoc.setProperty("SelectionLanguage", "XPath");
|
|
xmlDoc.setProperty("SelectionNamespaces", 'xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"');
|
|
return xmlDoc;
|
|
}
|
|
|
|
function doHTTP(method, url, body)
|
|
{
|
|
//var SXH_PROXY_SET_PROXY = 2;
|
|
var SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS = 2;
|
|
var SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS = 0x3300;
|
|
|
|
var objXMLHTTP = new ActiveXObject("MSXML2.ServerXMLHTTP.6.0");
|
|
// objXMLHTTP.setProxy(SXH_PROXY_SET_PROXY, "127.0.0.1:8888");
|
|
|
|
objXMLHTTP.open(method, url, false, config.username, config.password);
|
|
objXMLHTTP.setRequestHeader("Content-Type", "text/xml; charset=utf-8")
|
|
|
|
objXMLHTTP.setOption(SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS, SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS);
|
|
|
|
if (config.loglevel > 0)
|
|
__Log2File("request.xml", body);
|
|
objXMLHTTP.send(body);
|
|
|
|
if (objXMLHTTP.status >= 200 && objXMLHTTP.status <= 299)
|
|
{
|
|
return objXMLHTTP;
|
|
}
|
|
// else: er is iets fout
|
|
__Log2File("response.xml", objXMLHTTP.responseText);
|
|
WScript.Echo(objXMLHTTP.status);
|
|
WScript.Echo(objXMLHTTP.statusText);
|
|
WScript.Echo(objXMLHTTP.responseText);
|
|
return null;
|
|
}
|
|
|
|
function safefilename (naam) // geen 'lage' karakters en geen (back)slashes, *,%,<,>, '"', ':', ';' '?' and '|' of '+'
|
|
{
|
|
return naam.replace(/[\x00-\x1F|\/|\\|\*|\%\<\>\"\:\;\?\|\+]+/g, "_"); // " syntax highlight correctie
|
|
}
|
|
function __Log2File(log_file, data)
|
|
{
|
|
var utf8Stream = new ActiveXObject("ADODB.Stream");
|
|
utf8Stream.Open();
|
|
utf8Stream.Type = 2;
|
|
utf8Stream.CharSet = "utf-8";
|
|
utf8Stream.WriteText(data);
|
|
utf8Stream.SaveToFile(config.xmlfolder + safefilename(log_file), 2);
|
|
utf8Stream.Close();
|
|
}
|
|
|
|
// From MyJSON in shared.inc
|
|
function internal_parsedate(key, value)
|
|
{
|
|
var a;
|
|
if (typeof value === 'string') {
|
|
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
|
if (a) {
|
|
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
|
|
}
|
|
}
|
|
return value;
|
|
} |