Scripting Guide - Reusable Code

Two of the biggest problems with using scripts as opposed to a "real" programming language are scalability and reusability of code. Scripts grow like the proverbial house-that-Jack-built as features and behaviors are added, until they become unwieldy. At some point, your eagerness to build more power starts to be tempered by your fear of breaking it in some way or forgetting why or how you did something. Also you find yourself cutting and pasting common code from one script to another, then trying to keep these copies in sync as you add features. It's an age-old story.

You could just pull out your reusable code units and build them into ASCOM components using Visual Basic or even C++. But that assumes you are familiar with Visual Basic 6. And right now getting VB6, which supports Automation and ASCOM natively is difficult. You can use VB.NET which produces .NET components that use "COM-Interop" to expose themselves to ASCOM, but it costs serious money and has a rather large learning curve. Why not build simple components with a text editor?

Enter Windows Script Components (WSCs)

WSCs provide you with an easy way to create reusable ASCOM components in script! You create script components using any Windows scripting language and a text editor such as Notepad. If you read about WSCs on the net, you'll find most articles talk about them in the context of Active Server Pages, and talk about using the WSC Wizard. For ASCOM work we strongly recommend you invest in a copy of PrimalScript from Sapien Technologies. This program is an integrated development environment for WSCs. Nonetheless, you may find that Notepad or some other text editor is plenty good enough.

We'll assume you're familiar with ASCOM concepts of objects, methods, and properties, and you know what a ProgID is (hint: Meade.Telescope and MaxIm.CCDCamera are ProgIDs). The objective is to create a full-fledged ASCOM component with Notepad! Let's cut to the chase:

Component Skeleton:

Put the following into a file and name it ending in .wsc (for Windows Script Component, of course). Each hyperlink is something you need to adjust or expand. Click on it to go to the section explaining what it does.

You can drag through and copy this XML to the clipboard.

<?xml version="1.0" ?>
<package>
    <comment>
<![CDATA[
    Your component documentation goes here
]]>
    </comment>
    <component id="Main">
        <?component error="true" debug="true" ?>
        <registration 
            progid="Your.Component" 
            description="A short description"
            remotable="no">
        </registration>
        <object id="ACPApp" progid="ACP.Application" 
                    events="false" reference="true"/>
        <object id="FSO" progid="Scripting.FileSystemObject" 
                    events="false" reference="false"/>
        <object id="MaxIm" progid="MaxIm.Application" 
                    events="false" reference="true"/>
        <object id="Util" progid="ACP.Util" 
                    events="false" reference="false"/>
        <public>
            Your interface definition
        </public>
        <script id="Main" language="VBScript">
<![CDATA[
Dim Telescope, Camera, Dome, Weather

Set Telescope = Util.ScriptTelescope       ' Shortcuts
Set Camera = Util.ScriptCamera
Set Dome = Util.Dome
Set Weather = Util.Weather

Your script implementation
]]>
        </script>
    </component>
</package>
Documentation
You can put anything here, even XML and HTML, since it is in CDATA brackets. This is where you would describe your component and keep your edit tracks.
Error Switch
When true, the component will pop up an alert with an error message and line number in the WSC whenever a compilation or runtime error occurs. This is useful for debugging the WSC. If false, compilation errors will cause registration to fail (see below), and run time errors are passed to the calling ACP script. Always set this to false when you have completed development!
Debug Switch
When true, the script debugger will be invoked on a runtime error in the WSC. Always set this to false when you have completed development!
ProgID
This is the dotted name you give to your component. It is the name used when ACP scripts create an instance of your component with CreateObject(). A good rule of thumb is to use a first part that is unique to you (your last name, for example). The last part can be a name that describes the overall function of the component. For example: Jones.WeatherStation.
Description
Optional - a short description of your component. Will show in Windows object browsers.
Interface Declaration
This is where you hook up your internal script functions to ASCOM-visible properties and methods. A function in your script can implement a component property or method. For methods:
<method name="xxx">
    <parameter name="param1">
    <parameter name="param2">
</method>
This would be implemented in the <script> section (assuming VBScript) as:
{Function|Sub} xxx(param1, param2)
For properties:
<property name="yyy">
    <get/>
    <put/>
</property>
This would be implemented in the <script> section (assuming VBScript) as:
Function get_yyy()
and
Function put_yyy()
If you don't want to allow setting of the property, omit the <put/> tag from the <property name="yyy"> tag and omit the Function put_yyy() implementation. Methods that don't return a value should be implemented as Sub and not Function. All exposed properties and methods must be hooked up this way, with a declaration in the <public> section and a corresponding implementation in the <script> section.
Language
Set this to the name of the language that you use in the <script> section. For example, VBScript or JScript.
Script Implementation
Here is where you write the implementation of your <public> methods and properties, as well as private functions that support them. You can of course create objects here and use them. As long as you have <?component error="false" debug="false" ?> set in the header XML, raised errors from created objects will flow through your component into the calling ACP script. Any code outside the scope of a Function or Sub will be executed when an ACP script creates your component. Beware, though, that raised errors in such initialization code will not flow through to your script. Instead you'll get a "script stopped unexpectedly" error from your ACP script's call to CreateObject().

Activating Your Component

By default ACP does not "see" your component and it's member functions. To activate it for ACP it must be registered with the Windows OS. This is simple on 32-bit systems: Right click on mycomponent.wsc and select Register. If you see a success popup, it's ready to go. If you see a failure popup, there is a mistake in the code or XML.

On 64-bit systems it's a bit more complicated, as the component must be registered with the 32-bit siubsystem of Windows:

  1. Run C:\Windows\SysWOW64\cmd.exe (yes, SysWOW64!)
  2. Change directory to where your component is located.
  3. Enter the command regsvr32 mycomponent.wsc

Registration Failures

When you attempt to register your WSC, you may get an error popup something like this: Error Message RegSvr32 DllRegisterServerEx in C:\WINNT\system32\scrobj.dll failed. Return code was; 0x800a03ea. If you get this, you have a compilation (scripting) error in your code. To see what the error is, including the line number, turn on the Error Switch described in the preceding section. You will get a popup box with diagnostic info that should help you identify your coding error.

Using Your Component

In your ACP script, create the component exactly as you would any other Windows component. Likewise for using its properties and methods:
Set thing = CreateObject("Your.Component")
...
thing.yyy = "test"
...
Call thing.xxx(foo, bar)
That's it in a nutshell. Now have a look at the Windows Script Component documentation in the Microsoft Windows Script 5.6 help file. It's included in the ACP help content.