Files
Facilitor/UTILS/Exchange/exchange.js
Jos Groot Lipman 9e4a50bc20 AKZA#39963 Bij falende exchange-import eerder afbreken
svn path=/Website/branches/v2017.1/; revision=34472
2017-06-29 08:14:29 +00:00

346 lines
15 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)
Result: Errorlevel 10 betekent alles goed
Errorlevel 1 betekent dat we een foutsituatie hebben ontdekt
Errorlevel 0 betekent dat er iets is fout gegaan wat we niet hebben onderkend
Parameters (0) e-mail adres van de zaal
(1) "EXCHFULL" calendarview-import (in de toekomst tot config.fullfuture)
alle andere waarden: synchronisatie-import (voorheen werd hier de syncstate in meegegeven)
*/
var fso = new ActiveXObject("Scripting.FileSystemObject");
var zaalemail = WScript.Arguments(0);
var import_app_key = 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");
if (import_app_key != "EXCHFULL")
{
var udl = "../Oracle.udl";
var Oracle = new ActiveXObject("ADODB.Connection");
Oracle.Open('File Name='+udl);
var sql = "SELECT res_ruimte_syncstate"
+ " FROM res_ruimte"
+ " WHERE res_ruimte_verwijder IS NULL"
+ " AND res_ruimte_extern_id = '" + zaalemail + "'";
var oRs = Oracle.Execute(sql);
var zaalsync = oRs("res_ruimte_syncstate").Value||"";
oRs.Close();
var sha = oCrypto.hex_sha1(zaalsync); // Gemakkelijker verschillen te zien
WScript.Echo("\n\n==== Room: " + zaalemail + "\nOld sync hash: " + sha);
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 (import_app_key != "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);
}
}
}
// Als we hier komen is alles goed
WScript.Quit(10);
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;
}