I have an IE 11 HTA that extracts data from various Excel spreadsheets which the user chooses at run time. There are several categories of spreadsheets, and several examples of each category (daily, weekly, monthly reports, etc) so the user has to choose one spreadsheet from each category at run-time. This is working well, but it's slower than I would like as it's using a blocking, single-threaded logic, so when the HTA is opening a spreadsheet from a particular category and extracting the data the HTA is on hold.
I've been trying to switch to a multi-threaded model using web workers to do the job of opening the spreadsheets so that the HTA is not blocked, but I've been unable to do so as the worker files seem to be unable to access ActiveXObjects.
As an example, here is a trimmed-down main file:
function main () {
var output = document.getElementById ('output');
var myWorker = new Worker ('worker.js');
sendOut ('Calling worker.');
myWorker.postMessage ('');
myWorker.onmessage = function (m) {
sendOut (m.data);
}
}
function sendOut (m) {
output.innerHTML += '<br /> ' + (m);
}
window.callWorker = main;
While the worker.js file looks like this:
var fso = new ActiveXObject ('Scripting.FileSystemObject')
onmessage = function (m) {
postMessage ('Worker responding.');
}
If I comment out the first line of the worker.js file, the HTA runs as expected and the messages 'Calling worker.' and 'Worker responding.' appears in the 'output' element. However, if it's left uncommented, I get the error message 'Automation server can't create object'.
From this, it looks like worker files can't access ActiveXObjects, but I cannot find anything to confirm or explain this.
I've also tried creating ActiveXObjects in the main file and passing them as arguments like this:
function main () {
var fso = new ActiveXObject ('Scripting.FileSystemObject')
var output = document.getElementById ('output');
var myWorker = new Worker ('worker1.js');
sendOut ('Calling worker.');
myWorker.postMessage (fso);
myWorker.onmessage = function (m) {
sendOut (m.data);
}
}
function sendOut (m) {
output.innerHTML += '<br /> ' + (m);
}
window.callWorker = main;
But now I just get the error "DataCloneError". I've not been able to find any reference materials on this, but it looks like what I'm trying to do simple isn't possible. Is anyone able to confirm this, or do you know of a way around it?
Related
I try to schedule a script using the 'Scheduled Tasks' in ML8. The documentation explains this a bit but only for xQuery.
Now I have a JavaScript file I'd like to schedule.
The error in the log file:
2015-06-23 19:11:00.416 Notice: TaskServer: XDMP-NOEXECUTE: Document is not of executable mimetype. URI: /scheduled/cleanData.js
2015-06-23 19:11:00.416 Notice: TaskServer: in /scheduled/cleanData.js [1.0-ml]
My script:
/* Scheduled script to delete old data */
var now = new Date();
var yearBack = now.setDate(now.getDate() - 65);
var date = new Date(yearBack);
var b = cts.jsonPropertyRangeQuery("Dtm", "<", date);
var c = fn.subsequence(cts.uris("", [], b), 1, 10);
while (true) {
var uri = c.next();
if (uri.done == true){
break;
}
xdmp.log(uri.value, "info"); // log for testing
}
Try the *.sjs extension (Server-side JavaScript).
The *.js extension can be used for static JavaScript resources to return to the client instead of executed on the server.
Hoping that helps,
I believe that ehennum found the issue for you (the extension - which is what the mime-type error is complaining about.
However, on the same subject, not all items in ML work quite as you would expect for Serverside Javascript. For example, using sjs as a target of a trigger is (or recently) did not work. So for things like that, it is also possible to wrap the sjs call inside of xqy using xdmp-invoke.
It is a web app, using Google Apps Script, running as the user accessing the app.
We have custom data and code for some users.
That custom information is in a text file within the developer's Google Drive, with only View access from the specific user.
The content of that text file could be like below dummy code:
var oConfig = {
some : "OK",
getinfo : function (s) {
return this.some + s;
}
}
In order to get that custom data / code into the app, we can use eval() as shown below:
var rawjs = DriveApp.getFileById(jsid).getBlob().getDataAsString();
eval(rawjs);
Logger.log(oConfig.getinfo("?")); // OK?
My questions are:
Is there a better way to achieve this goal than eval()?
Is eval() secure enough in this case, considering that the text file is only editable by the developer?
Thanks, Fausto
Well, it looks secure enough. But using eval has other problems, like making it difficult to debug your code, and possibly some other problems.
If you're generating such custom data within your code, I imagine the variety of such customizations is enumerable. If so, I'd leave the code within your script and save in Drive just data and use indicators (like function variants names) of how to rebuild the config object in your script. For example:
function buildConfig(data) {
var config = JSON.parse(data); //only data, no code
config.getInfo = this[config.getInfo]; //hook code safely
return config;
}
function customInfo1(s) { return this.some + s; }
function customInfo2(s) { return s + this.some; }
function testSetup() {
//var userData = DriveApp.getFileById(jsid).getBlob().getDataAsString();
var userData = '{"some":"OK", "getInfo":"customInfo1"}'; //just for easier testing
var config = buildConfig(userdata); //no eval
//let's test it
Logger.log(config.getInfo('test'));
}
It seems secure. But, it will make your execution process slower if you have large data in your text file.
I would still suggest to use JSON.parse() instead of eval() to parse your custom data/code.
{
some : "OK",
getinfo : "function(s){return this.some +\" \"+ s;}"
}
var rawjs = DriveApp.getFileById(jsid).getBlob().getDataAsString();
var oConfig = JSON.parse(rawjs, function(k,v){//put your code here to parse function}); // avoid eval()
Logger.log(oConfig.getinfo("?"));
We are currently using log4javascript-popUpAppender console for development and would like to store the details to local file.
Though we can use the AjaxAppender to send log messages to the server and log those messages to log4j set up with a rolling file appender, we are looking for a way to use something similar to FileAppender in Log4js.
Any idea/suggestion?
This is similar to http://www.techques.com/question/1-3626960/JavaScript-logger-into-a-rolling-file
Since we have already implemented log4javascript, we would like to stick with the same framework.
This is still not really viable in browsers, in my view. I've had another look at it; these are my observations:
In Firefox, I don't think it is currently possible to write to the local file system at all, even if the user approves. From Firefox 17 (I think), privileged code can no longer run in a web page, which rules out the old method floating around on the web (e.g. here)
IE still has its ActiveX method of doing this, but it's more locked-down than ever and requires various actions by the user to enable it.
HTML5 has a file system API which is currently only implemented by new versions of Chrome and Opera. It writes files to a carefully sandboxed location and offers no control over actual file name or path.
Safari currently has no way to do this, as far as I can tell.
In general, browsers sensibly offer little or no access to files on the local file system, so it's an unreliable way to log. However, I've written a rough BrowserFileAppender that implements the HTML5 and ActiveX methods which you're welcome to use if you find it helpful:
https://gist.github.com/timdown/6572000
Adding FileAppender solution for IE and Firefox.
function FileAppender() {}
FileAppender.prototype = new log4javascript.Appender();
FileAppender.prototype.layout = new log4javascript.SimpleLayout();
FileAppender.prototype.append = function(loggingEvent) {
var appender = this;
var getFormattedMessage = function() {
var layout = appender.getLayout();
var formattedMessage = layout.format(loggingEvent);
if (layout.ignoresThrowable()) {
formattedMessage += loggingEvent.getThrowableStrRep();
}
return formattedMessage;
};
writefile = function(destinationFile, message) {
if (isEmpty(destinationFile)) {
log.error("Source location unknown");
return;
}
if ($.browser.msie) {
try {
var fso = new ActiveXObject("Scripting.FileSystemObject");
var file = fso.OpenTextFile(destinationFile, 8, true);
file.WriteLine(message);
file.close();
} catch (e) {
log.error("Please validate if file exist");
}
} else {
netscape.security.PrivilegeManager
.enablePrivilege("UniversalXPConnect");
this.fso.initWithPath(destinationFile);
if (!this.fso.exists()) {
// create file if needed
this.fso.create(0x00, 0600);
}
var file = Components.classes["#mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
file.init(this.fso, 0x04 | 0x08 | 0x10, 064, 0);
var line = message;
file.write(line, line.length); // write data
file.close();
}
};
getFile = function() {
return "c://temp//log//Javascriptlog.log";
};
writefile(getFile(), getFormattedMessage());
};
FileAppender.prototype.toString = function() {
return "FileAppender";
};
log4javascript.FileAppender = FileAppender;
EDIT: I'm trying to read all the files in a specific folder and list the files in there, not read the content of a specific file. I just tried to simply create an FileSystemObject and it doesn't do anything either. I show an alert (which pops up) beforfe making the FileSystemObject, and one after it (which isn't shown). So the problem is in simply creating the object.
Original:
I am trying to read all the files in a folder by using JavaScript.
It is a local HTML file, and it will not be on a server, so I can't use PHP I guess.
Now I'm trying to read all the files in a specific given folder, but it doesn't do anything on the point I make a FileSystemObject
Here is the code I use, The alert shows until 2, then it stops.
alert('1');
var myObject, afolder, date;
alert('2');
myObject = new ActiveXObject("Scripting.FileSystemObject");
alert('3');
afolder = myObject.GetFolder("c:\\tmp");
alert('4');
date = afolder.DateLastAccessed;
alert("The folder"+name+" is a temporary folder.");
Am I doing this the right way?
Thanks!
The method I found with a Google search uses HTML5 so if you are using a modern browser you should be good. Also the tutorial page seems to check if the browser you are using supports the features. If so you should be good to follow the tutorial which seems pretty thorough.
http://www.html5rocks.com/en/tutorials/file/dndfiles/
This solution only works on IE11 or older since it is MS based
<script type="text/javascript">
var fso = new ActiveXObject("Scripting.FileSystemObject");
function showFolderFileList(folderspec) {
var s = "";
var f = fso.GetFolder(folderspec);
// recurse subfolders
var subfolders = new Enumerator(f.SubFolders);
for(; !subfolders.atEnd(); subfolders.moveNext()) {
s += ShowFolderFileList((subfolders.item()).path);
}
// display all file path names.
var fc = new Enumerator(f.files);
for (; !fc.atEnd(); fc.moveNext()) {
s += fc.item() + "<br>";
}
return s;
}
function listFiles() {
document.getElementById('files').innerHTML = showFolderFileList('C:');
}
</script>
<input type='button' onclick='listFiles()' value='List Files' />
<div id="files" />
I have a script CustomAction (Yes, I know all about the opinions that say don't use script CustomActions. I have a different opinion.)
I'd like to run a command, and capture the output. I can do this using the WScript.Shell COM object, then invoking shell.Exec(). But, this flashes a visible console window for the executed command.
To avoid that, I understand I can use the shell.Run() call, and specify "hidden" for the window appearance. But .Run() doesn't give me access to the StdOut of the executed process, so that means I'd need to create a temporary file and redirect the exe output to the temp file, then later read that temp file in script.
Some questions:
is this gonna work?
How do I generate a name for the temporary file? In .NET I could use a static method in the System.IO namespace, but I am using script here. I need to insure that the use has RW access, and also that no anti-virus program is going to puke on this.
Better ideas? I am trying very hard to avoid C/C++.
I could avoid all this if there were a way to query websites in IIS7 from script, without resorting to the IIS6 Compatibility pack, without using .NET (Microsoft.Web.Administration.ServerManager), and without execing a process (appcmd list sites).
I already asked a separate question on that topic; any suggestions on that would also be appreciated.
Answering my own question...
yes, this is going to work.
Use the Scripting.FileSystemObject thing within Javascript. There's a GetTempName() method that produces a file name suitable for temporary use, and a GetSpecialFolder() method that gets the location of the temp folder. There's even a BuildPath() method to combine them.
so far I don't have any better ideas.
Here's the code I used:
function GetWebSites_IIS7_B()
{
var ParseOneLine = function(oneLine) {
...regex parsing of output...
};
LogMessage("GetWebSites_IIS7_B() ENTER");
var shell = new ActiveXObject("WScript.Shell");
var fso = new ActiveXObject("Scripting.FileSystemObject");
var tmpdir = fso.GetSpecialFolder(SpecialFolders.TemporaryFolder);
var tmpFileName = fso.BuildPath(tmpdir, fso.GetTempName());
var windir = fso.GetSpecialFolder(SpecialFolders.WindowsFolder);
var appcmd = fso.BuildPath(windir,"system32\\inetsrv\\appcmd.exe") + " list sites";
// use cmd.exe to redirect the output
var rc = shell.Run("%comspec% /c " + appcmd + "> " + tmpFileName, WindowStyle.Hidden, true);
// WindowStyle.Hidden == 0
var ts = fso.OpenTextFile(tmpFileName, OpenMode.ForReading);
var sites = [];
// Read from the file and parse the results.
while (!ts.AtEndOfStream) {
var oneLine = ts.ReadLine();
var line = ParseOneLine(oneLine);
LogMessage(" site: " + line.name);
sites.push(line);
}
ts.Close();
fso.DeleteFile(tmpFileName);
return sites;
}