Wednesday, May 20, 2009

Using Adobe BridgeTalk synchronously

So let's face it : writing synchronous code is a lot easier than writing asynchronous code. I can write asynchronous code easily enough, but it looks so much more cluttered, and/or takes so many more lines of code. I'm a big believer in simplicity.

So, if you want to whip up a simple script, and it doesn't need to win design awards, then I suggest you check out xbytor's cool little trick for using BridgeTalk synchronously. (Search on that page for "Send a synchronous message".)

Nice one, xbytor.

And in case that forum goes down, here's a copy of the most pertinent bit. (See the original thread for more questions, answers and examples.)

First, some utility code :

// Send a synchronous message. The result is returned.
// If a result doesn't come back in 'timeout' seconds, undefined is returned.
BridgeTalk.prototype.sendSynch = function(timeout) {
var self = this;
self.onResult = function(res) {
this.result = res.body;
this.complete = true;
}
self.complete = false;

self.send();

if (timeout) {
for (var i = 0; i < timeout; i++) {
BridgeTalk.pump(); // process any outstanding messages
if (!self.complete) {
$.sleep(1000);
} else {
break;
}
}
}

var res = self.result;
self.result = self.complete = self.onResult = undefined;
return res;
};
// for typos, provide an alias
BridgeTalk.prototype.sendSync = BridgeTalk.prototype.sendSynch;

And now an example of how to use it :

function test() {
var bridgeApp = "bridge-1.0";

if (!BridgeTalk.isRunning(bridgeApp)) {
BridgeTalk.launch(bridgeApp);
}

var bt = new BridgeTalk();
bt.target = bridgeApp;
bt.body = "new Date().toString()";
var res = bt.sendSynch(10);
alert(res);
};

test();

Once again, mega kudos to xbytor. Some coding purists might gag at the internal polling loop, but remember, we're not talking about production scripts released to thousands of users worldwide - we're talking about helping every developer be more productive by writing their own scripts more easily.

Adobe BridgeTalk woes

As just another look at how mind-numbingly idiotic a large proportion of Adobe APIs are, consider this attempt to use BridgeTalk :

var bt = new BridgeTalk();
bt.send();
alert('The message was sent successfully.');

We haven't specified a recipient, and we haven't specified an actual message.

But run the code, and guess what? Apparently, the message was sent successfully.

Especially for such a poorly-documented class as BridgeTalk is, developers rely on meaningful error messages (including input validation error messages) to figure out what's going wrong and how on God's beautiful green earth we're supposed to use the thing.

My original script looked more like this :

function BridgeTalkResultHandler(result)
{
alert('Hurrah! The callback came!' + result.body);
}
var bt = new BridgeTalk();
bt.targetApp = 'illustrator';
bt.body = 'alert("hello");';
bt.onResult = BridgeTalkResultHandler;
bt.send();
alert('The message was sent successfully.');

Translation : get Illustrator to show a message. Just an elementary test.

But plug this into the ExtendScript ToolKit and run it, and all you get is meaningless nonsense.

'The message was sent successfully' is given every time you run it, but Illustrator doesn't budge.

Well, thought I, perhaps Illustrator refuses to interact with the user (e.g. with the alert method) under some circumstances. So I changed the script to app.activeDocument.close(), and I left a document open in Illustrator, so I could see if something happened. Still nothing.

I removed the onResult handler to see if somehow that was causing the problem. Nothing changed.

And I stripped out more and more things, until I got to the code at the start of this article.

The behaviour is identical, for every version of the script.

In other words, BridgeTalk is doing absolutely nothing - not even any validation.

I even tried crazy things like :

bt.targetApp = 'illustraaaaaaaaaaaator';

... but there wasn't any complaint about the non-existent target.

Examples of BridgeTalk are not as plentiful as one might wish for, but my original code looks very much like the few examples I found.

All I can say is : this is typical of Adobe APIs. Poorly documented, counter-intuitive, and requiring wasteful trial-and-error to master.

If it was open-source, or some cheapo product, I'd excuse it. But when they want to charge thousands for their suite of products, you expect a LOT better than this.



Got it working - bt.target is the correct property name, not bt.targetApp. I'm not sure whether I read a bad example, or whether I misread an example, but in either case, my thesis stands - Adobe APIs are a dog to work with except only when everything is working perfectly. A helpful input validation error message, such as 'target property is required', would have saved me valuable time! C'mon Adobe, you know you can do better than this... What's worst is that Microsoft is now actually pretty good at making meaningful error messages (something they were hopeless at ten years ago) - so it's possible to have a moderately-self-documenting API, and that means Adobe has dropped the ball.

Tuesday, May 19, 2009

Automate Adobe Flash using VB.NET or C#

Adobe is very thin as usual on technical details, so I've had to poke around and try different things.

Here's what I've found :

* .NET can communicate directly with Illustrator, but not directly with Flash, nor with BridgeTalk.

* .NET can send javascript to Illustrator, including javascript which utilises BridgeTalk to communicate between Illustrator and Flash.

* So it is possible to automate both Illustrator and Flash using VB.NET/C#/other-.NET-languages, but it requires .NET <-> Illustrator <-> BridgeTalk <-> Flash.

* In VB.NET, you connect to Illustrator as follows :

Dim app As Object = CreateObject("Illustrator.Application")

* To get Illustrator to run arbitrary javascript :

app.DoJavaScript(myJavaScript);

e.g. to get Illustrator to greet us :

app.DoJavaScript("alert('Hello!');");

* To get Illustrator to return a useful value from a script we have sent it, don't use the "return" keyword - just include the return value as the final expression in the script block.

e.g. :

Dim ReturnValueFromIllustrator As Object = app.DoJavaScript("1 + 2;")
MsgBox(ReturnValueFromIllustrator) ' Shows "3"

* The .NET <-> Illustrator leg is synchronous, even though the Illustrator <-> BridgeTalk <-> Flash legs are ASYNCHRONOUS. (Yeah - just to make life more interesting.)

Hmmm - in short, I wouldn't say it's "easy", but it is possible.

Maybe Adobe will enter the 21st century sometime and really make all their apps easily cross-scriptable, and their scripting engines a lot more robust. Until then...

... your brother in the pain of scripting Adobe products.



P.S. If you're using C#, or if you're using VB.NET and want a better Intellisense experience (and better runtime performance, and better compile-time checking) than just using VB.NET's CreateObject method, add a reference to the "Adobe Illustrator CS4 Type Library". Once you've done that, you can connect to Adobe Illustrator as simply as "Dim app As New Illustrator.Application" (or in C#, "Illustrator.Application app = new Illustrator.Application();").

ExtendScript: instanceof Number

var xyz = 123.45;
alert(xyz instanceof Number);

Always false. Not very intuitive at first.

Searching for ExtendScript instanceof Number didn't help, but searching for Javascript instanceof Number did.

Unfortunately, the short of it is that we have to resort to the slowness of string comparisons. C'mon - I thought this was the 21st century!

var xyz = 123.45;
alert(typeof xyz == 'number');

But there are situations where instanceof Number DOES work :

var xyz = new Number(123.45);
alert(xyz instanceof Number); // true

... and in such instances, the typeof trick DOESN'T work :

var xyz = new Number(123.45);
alert(xyz instanceof Number); // true
alert(typeof xyz == 'number'); // false

There is a reason for it, and when you understand the reason, it makes sense enough, but it does prove that javascript and all its derivatives are a little idiosyncratic and obscure at times.

For more info :

StackOverflow - why does instanceof return false for some literals?

More technical info (hands-on) about javascript and instanceof

Thanks to the two articles I just listed! They helped solve for me what was seeming a great (and annoying) mystery.

Sunday, May 17, 2009

Running two Engin voiceboxes on the same network

Again, a post that'll only help a very few of you, but for the sake of those few...

Engin is a fabulous VoIP (internet telephony) company. We have two accounts with them - one for our business, and one for our home.

Everything was working perfectly at our old address, with TPG as our ISP.

We've relocated, and TPG doesn't cover our outlying suburb, so we're now with Telstra.

Problem : With Telstra's ADSL2+ modem, only one of our VoIP lines works properly.

Specifically, both make outbound calls just fine, but inbound calls only work consistently for one of the two lines.

If I reset the Engin voiceboxes (which internally are SipuraSPAs), and wait a few seconds for them to re-register with Engin, then inbound calls work fine for both lines, for about five minutes, and after five minutes, its just the one line that inbound calls work for.

I contacted Engin tech support last week, and they changed one of my Engin voiceboxes to use the "bring-your-own" Engin settings. I thought that fixed the problem, but it turned out that the test call had been made within five minutes of a "soft reset" of the Engin device, and that's the only reason the test inbound call worked at that time. i.e. problem not solved.

Today I got it working, at last, and permanently.

It's not intuitive, but I set up two port forwarding ranges on my ADSL2+ modem, one for UDP ports 16384 through 18383, and the other for UDP ports 18384 through 20384. I pointed one port forwarding range to the one Engin voicebox, and the other range to the other voicebox.

It's about half an hour later, and inbound calls for both lines are still working perfectly. Nice...

In my previous attempts to resolve this issue, Google-ing for "two Engin voiceboxes" and such like turned up a grand total of zero people discussing problems with running two Engin voiceboxes side-by-side. So hopefully, for the next person who needs a little help, this post might provide a few ideas...



UPDATE : A few hours later, and I discovered it's NOT working, but I figured out why, and here's the low-down :

It turns out that the port forwarding DIDN'T HELP ONE BIT.

But I've pinpointed exactly what the problem is :

The Engin voiceboxes re-register with Engin every nearly-30-minutes. (It's every roughly 29 and a half minutes.)

But the Telstra ADSL2+ router closes UDP "connections" after about 300 seconds (five minutes) of inactivity.

So every half hour, there is a five minute window of opportunity during which inbound calls will work, and then nearly 25 minutes where they won't.

My confirmation test earlier today must have been in that five minute window of opportunity.

The fix?

Well, I trawled through the options in the admin panel for this Telstra ADSL2+ modem, and don't seem to have any power to change the default timeout.

So I contacted Engin tech support again, and asked them to remotely log in to my machine and change the default timeout on both my Engin voiceboxes down to 280 seconds. That gives them roughly 20 seconds (far more than enough) to execute their re-registration process before the ADSL2+ router deems their UDP "connection" disposable. The re-registration process resets the modem's five minute timer, and this process, continually repeating itself, twelve times an hour, ensures that inbound Engin calls will work at any time.

And I've removed those ADSL2+ port forwards, as they are now completely useless...

Thursday, May 14, 2009

lsass.exe high CPU usage and disk I/O

One of my old computers has had a problem for YEARS.

Windows XP, service pack 2.

Within minutes of reboot, lsass.exe goes to near 100% CPU utilisation, and sits there for sometimes around half an hour.

Rendering the computer pretty much unusable.

ALL attempts to resolve the problem had failed.

And after wasting lots and lots of time, I resigned myself to simply hibernating not rebooting where possible, and losing half an hour of time every time I wanted to reboot the computer.

Killing lsass.exe fails, because the Task Manager informs you that it is a system process.

Trying to reduce its priority, so it hogs less CPU, fails for the same reason.

It is exactly the same file (same file size, and same file bytes - I used fc.exe to compare) as the lsass.exe on my other Windows XP computers - none of which exhibit this problem.

I haven't found a solution, but things have improved now.

1) Earlier today, based on comments I saw on one website, I went into the properties for each hard disk, and unchecked the "Allow Indexing Service to index this disk for fast file searching" option. When I clicked "Apply", it asked whether I want to apply this change to all subfolders as well, and I said yes. It took ages, but it did finally finish applying the change.

2) Note that I had already long prior disabled the Indexing Service itself.

3) Also earlier today, based on suggestions on another website, I completely disabled the Terminal Services service. (I also disabled Fast User Switching, prior to disabling said Terminal Services service.)

lsass.exe still hogs CPU. It's I/O read and write count goes up to over a hundred thousand. But then it stops. At approximately the same I/O read/write count each time. And once it stops, my computer runs fine.

And fortunately, I don't know what's changed, but it only seems to take about five minutes now. Which is really stupid. But much more bearable than half an hour.

It doesn't seem to be a worm or trojan or anything such like. Antivirus scans turn up nothing.

handle.exe reports exactly the same files open by lsass.exe each time I run it. That's a lot of I/O to generate for the same set of files!!! Unless it's doing some sneaky direct-to-disk I/O and bypassing the usual file-handle system...

And besides, what's with all those writes? Well over a hundred thousand disk writes within the first five minutes of bootup?

Google-ing for lsass.exe issues reveals lots of people experiencing problems, but it turns up remarkably few solutions.

I haven't found a complete solution, but perhaps somehow between unchecking that "Allow Indexing Service ..." option, and disabling the Terminal Services service, it has taken a bite out of the ridiculous amount of time that lsass.exe now wastes each time I boot...

Cross-scripting Adobe Illustrator and Adobe Flash

21st century, expensive products, same company makes them all, and sells them all in the same package.

You'd think they'd play happily together.

And Adobe documentation here and there hints that they do.

But finding out how is mighty difficult.

You fire up the ExtendScript Toolkit, and the documentation makes it sound like you can do almost anything from there.

Play around with Adobe Illustrator scripting in the ExtendScript Toolkit - sweet!

But now try to script Adobe Flash...

... oops, um, well, there doesn't really seem to be a way to do it.

Does Adobe document that you can't? Well, not that I've managed to find.

In fact, one other commentator stopped short of giving a definitive answer that Flash isn't supported by the ExtendScript Toolkit, and instead just said that it seems it isn't.

C'mon Adobe, give us clear, informative, unambiguous documentation!

In fact, if you put "#target flash" in a script in the ExtendScript Toolkit, and try to run it, it actually launches Flash, and then tells you that it failed to connect to the Flash scripting engine. What the???

An Adobe brochure about Flash CS3 claims that it is now compatible with ExtendScript, but frankly, I don't see how, and we're now up to CS4!!! ("Flash CS3 also adds ExtendScript and BridgeTalk support for cross-application scripting.")

So after many hours of wasted time, I've figured out how to make Illustrator and Flash sing to each other.

I'm not impressed by the obtuse steps required.

And I'm not impressed by the extremely non-definitive documentation on Adobe's part.

Here's how you do it :

1) Your Adobe Illustrator scripts will be written using the ExtendScript language, whereas your Adobe Flash scripts must be written using a language called JSFL. Both languages are javascript-based, and have many similarities, but they also have many differences. (e.g. native filesystem access in ExtendScript is done via the File class, whereas in JSFL it's done via the FLfile class. Hello, any reason for that, anybody?)

2) Adobe Bridge is not Adobe BridgeTalk. The names are very similar, but the use is very different, and unfortunately, the difference is not clearly articulated in Adobe docs.

Adobe Bridge is a moderately useless file cataloging and file browsing tool.

In contrast, Adobe BridgeTalk is a simple but powerful inter-process communication tool.

So, we're going to use Adobe BridgeTalk to communicate between Illustrator and Flash.

3) It is very hard to find Adobe BridgeTalk documentation. (e.g. Google-ing for "adobe bridgetalk reference" is close to useless.)

I suspect this is in part because BridgeTalk is SO simple that there's hardly anything to document, and thus, Adobe is content to let developers learn by example.

And learn by example is how I had to do it.

4) One of the best sources of examples of Adobe BridgeTalk scripting is actually in the Adobe Bridge SDK. Yes, that sounds confusing, but it's true.

Download said Adobe Bridge SDK. It's about 4MB of compressed files. Unzip, and inside are a few interesting PDFs, and a bunch of jsx files showing cool tricks using BridgeTalk (amongst other things).

(I suggest you read the short section "Communicating with Other Applications" in the "Bridge CS4 JavaScript Guide.pdf" that comes in the docs folder in the Adobe Bridge SDK.)

5) In brief, BridgeTalk sends javascript as a string, from one application to another (e.g. Illustrator to Flash), and sends the value of the last-executed-statement in said script back to the calling application. It's a bit obtuse, but it's very powerful.

6) Note that BridgeTalk is ASYNCHRONOUS. And there is no synchronous mode. So you have to be patient with writing the extra code to handle breaking a single synchronous procedure into smaller asynchronous steps.

7) There is an extremely-poorly-documented means of launching ANY application via BridgeTalk. e.g. you can run a Windows batch file, or trigger your source code versioning system, or invoke a custom tool you wrote in a non-Adobe language. You can pretty much run anything you like.

It's based around a function called "system".

As with most Adobe stuff, this "system" function is extremely poorly documented.

The only way I've found to use it is to call app.system('command-line.exe bla bla'); from within Bridge.

So - get this - if you want to fire up a useful external program (e.g. source code control) from within a script that is automating Illustrator or Flash, you need to use BridgeTalk to send a script to Bridge, and the script you send to Bridge needs to include the app.system(<command-line>) invocation. Talk about convoluted!

But at least it works.

Oh - and app.system(...) is SYNCHRONOUS, unlike BridgeTalk which is ASYNCHRONOUS. Just to make sure your neurons are really getting a workout...

8) If you've understood everything to this point, and want a cool trick to make it much easier to generate lengthy scripts that get shared between different Adobe products, you can use the toSource() function, as described by Justin of Ajar Productions in an article entitled "Easy (and Readable) ExtendScript to JSFL Workflow".

Remember : JSFL = scripting the Flash* development ("authoring") environment, whilst ExtendScript is used e.g. for Illustrator. Justin's article covers writing an ExtendScript for Adobe Illustrator, that also uses BridgeTalk to get Flash to do some things, yet all within a single script file, and without much messy code. Very nice work, Justin!

(* Technically, JSFL is used for a few other products, not just Flash, but for my present purposes, it is accurate enough to say JSFL = scripting the Flash development environment.)



Conclusion?

Adobe makes things horribly painful to learn.

Oh - and to use. e.g. obtuse error messages.

But despite the pain along the way, you can actually accomplish a huge amount. If you have spare hair and a high pain threshold, you might find (as I am finding) that ExtendScript, JSFL and BridgeTalk are extremely rewarding tools to be able to use.

Wednesday, May 6, 2009

BSOD, rdbss.sys, network dropouts, unwise_.exe, and the Win32/Heur virus/trojan

This will only help a very small number of you, but for the sake of these few...

I have lost HOURS and HOURS this week with the internet connection on my email computer dropping out.

"Repair Connection" worked a few times, but in the last few days even that stopped helping.

Switching between LAN and wireless also helped initially, but in the past few days, not at all.

It got to the point where I would lose ALL network access within a few minutes of boot.

Just long enough to check email once.

And it got to the point where I could only "fix" it by rebooting.

So get this : my (slow) email machine, that takes roughly five minutes to do a shutdown/reboot cycle, had to be turned off and turned back on practically every time I wanted to check email or send an email!!!!!!! Very frustrating!

(Nor was it just a case of "set & forget". The laptop requires password entry at two points during the boot cycle, so rebooting it is a major distraction.)

I tried using Internet Explorer's diagnose-connection-problems tool. It informed me that "Windows has detected a problem with the winsock provider catalog on this computer", and offered to repair the problem. But that didn't help.

At around the same time, I began to find that the laptop would bring up the infamous "Blue Screen Of Death" (BSOD) during shutdown. Every time. Yet another source of pain in the email-checking routine.

And of course, I'm used to checking email at least every hour, as it is a primary means of communication with customers...

The BSOD was a red herring. It said that rdbss.sys had been unloaded "without canceling pending operations" - whatever that means. I searched and searched, and of course, found nothing that helped.

I tried resetting the Winsock stack - as suggested by some articles - but the problem persisted.

What if... what if this is a symptom of a common virus?

I haven't had a virus on any of my computers in something like six or seven years.

I don't actually have a virus scanner installed - they usually slow a computer down horribly.

As a very technically-oriented programmer, I am very familiar with the limitations of technology, and instinctively manage to steer clear of most sources of viruses.

But whatever was going on with my email computer was very strange, and it was wasting hours and hours, so I decided to give it a virus scan.

AVG became my tool of choice.

I downloaded it onto another computer and transferred the installer across via USB stick.

Fortunately, it allowed me to install without needing to access the internet - e.g. for updated virus signatures.

Pretty quickly, the virus scan turned up a "Win32/Heur" virus in a file called "C:\Windows\fonts\unwise_.exe". Aha! Likely the culprit!

And sure enough it was. More on the virus in a moment, but I used Task Manager to kill the running instance of unwise_.exe, and within minutes, my network access was working properly again!

It might even be that unwise_.exe and its Win32/Heur were not directly killing the network, but perhaps were engaging in network activity that my network router found objectionable. Or maybe the problem was indeed entirely within that infected laptop.

Either way, virus gone, problem solved.

And the BSODs have disappeared too!

So if you're having strange network dropouts minutes after boot... it might just be a virus.

---

A couple of thoughts about this particular virus :

It uses the file name "unwise_.exe" - note the underscore. I find this intriguing. "unwise.exe" (no underscore) is (IIRC) the name of a popular program uninstaller that works with a very large number of programs. (Is it part of the "InstallShield" suite? I'm hazy on the details now...)

So presumably, the virus writers chose the name "unwise_.exe" (note the underscore) because of its similarity to "unwise.exe" (no underscore) - i.e. to try to make it look more innocuous.

But to the trained eye, it was every bit suspicious.

First up, whilst "unwise.exe" (no underscore) is a common name, "unwise_.exe" (note the underscore) is NOT a common name, and uncommon file names that are so suspiciously similar to common file names don't just happen by accident!

Secondly, the file had both the SYSTEM and HIDDEN file attributes set. Oh yeah - that means you won't actually be able to find it yourself, unless you do something like go to the Command Prompt and type something like "dir C:\Windows\fonts\*.exe /ah".

Thirdly, no executables ever live in the C:\Windows\fonts folder. That alone is almost sufficient proof that the file is malicious. But what a clever choice - the C:\Windows\fonts folder cannot actually be browsed in Windows Explorer like other folders can. If you launch Windows Explorer and navigate to C:\Windows\fonts, you'll see a special "fonts list" view, which fails to show the offending "unwise_.exe", even though it is in that folder.

So whoever wrote this "unwise_.exe" thing knew what they were doing, and went to great lengths to hide their malicious program's presence on affected systems.

Note further that this virus installs itself to start at system boot (or user logon - one or the other, and I'm not quite sure which) and to run under the SYSTEM account. That's another clue that it's a bad boy - the real "unwise.exe", being an uninstaller, and thus always launched by the USER not the SYSTEM, would not be found in the Task Manager running under the SYSTEM account.

---

Oh - and before you flame me - someone else had been using my email computer quite heavily around the time of the infection (1-May-2009 was the date of infection, based on the file datetime stamp on the C:\Windows\fonts\unwise_.exe file), and so whilst that probably means I need to install an anti-virus program permanently if I'm going to let them keeping using my computer, it does potentially vindicate my "I don't need an anti-virus program for myself" attitude of many years.

---

Thanks for reading! I hope this helps some of you...