Thursday, August 19, 2004

Why can't I use WScript in my MSI Custom action?

This is a fairly frequent question, and one that deserves a decent answer. VBScript and JScript custom actions run inside of the Windows Installer engine - in technical terms, the MSI engine is the scripting host for all scripts running as a custom action.

The scripting host program can make available objects to the scripts if it so desires. I've written applications that host the scripting engine, so this is familiar to me. In the case of MSI custom actions, the Session object is an example of this. When you run a script from the command line or by double-clicking it, it is hosted by the "Windows Scripting Host" (WSH), and it kindly supplies the WScript object. The main reason these objects are provided is so the script can interact with the host environment. In the case of WSH, you usually need to interact with the user since it is being run from Explorer or the console, hence the "Echo" method. In the MSI world, you are encouraged to interact with the MSI engine and let the MSI engine interact with the user based on the appropriate UILevel settings - this is all handled for you if you use the Session.Message API's to interact with the user or to write log files.

To summarize, if you have a script that contains WScript.something, it will not work in an MSI, as the WScript object does not exist and you cannot create it.

An unrelated issue (but people think it is related...) is the "WScript.Shell" object. You can create the WScript.Shell object (in JScript foo = new ActiveXObject("WScript.Shell")) and use it. In this case, the full name of the object you are creating is merely WScript.Shell by coincidence, and you are not calling a method on the WScript object, as you are in a line similar to WScript.Echo("foo"). An interesting related aside here is an article detailing the differences between CreateObject and WScript.CreateObject.

Danger, Will Robinson! Some antivirus programs and other script blockers will either silently block or loudly proclaim attempts to instantiate the WScript.Shell and/or the Scripting.FileSystemObject. These are commonly used by malware since they can access the registry and filesystem. Write a C++ dll custom action if you need to interact with the system in this way.

A fairly common question posted to the Windows Installer boards is "How do I sleep?" or otherwise delay the script. While I don't have an answer you want to hear, there is an excellent article about the subject here. BTW, the answer you don't want to hear is to write a DLL custom action that merely sleeps, add it to the CustomAction table, and in your CA script call Session.DoAction()

4 comments:

Anonymous said...

But i whant use Wscript.Version property.... why not? my scripts is complecated and cannot run in wsh 5.1, so i whant check wsh version and say about it to user.

Steven Bone said...

Alexander,

Sorry about the delay in responding, I caught the comment notification just now. In order to check the version of wsh without using the WScript object in an MSI, you can add a launch condition and use the AppSearch action to look for the version of the scrrun.dll file in the system32 directory.

If you need to know how to search a system for a file, the Platform SDK or msi.chm help file has a sample excercise on this very subject.

Anonymous said...

In one of my pacakges i need to install a service and then i had to start it.
If i did it too quick the service wasn't yet available then i decided to wait for few seconds before to start it using WScript.Sleep(5000) in my custom Action.
Obviously, i discovered it was'nt possible to use it then i replaced it by the use of the native command "timeout" as shown below:
...
dim objShell = CreateObject("WScript.Shell)
objShell.Run("timeout 5 /nobreak",true,0)
...

This solution is not the best, considering the use of a DLL is much better, but for a simple need it is totally sufficient.

I hope it will help
Regards Olivier CLERGET

Steven Bone said...

Olivier,
I'm not sure this is a good idea. Slower machines will take longer to start services on, faster machines will wait way too long for no reason. Why don't you schedule your custom action to run AFTER the StartServices action? The only reason to not use this approach is if you need to install a service that depends on a managed assembly you are installing into the GAC. See the MSI docs on the "ServiceControl Table" for more info.

I'm sorry, but I'd actually use a native custom action here if you have the situation with a managed assembly installed to the GAC. The service control APIs are fairly simple to use and you can have it not return until the service is running for precise control.

I've become less confident that any machine will have the WScript.Shell object available, and if it is not that will cause your CA to fail for no good reason.