How Come You Guys Don't Use .WSF Files?

Posted by Greg Stemp. Ok, so yesterday I got a bit lackadaisical, and screwed up in my blog entry. Therefore, I vowed that today I wouldn’t say anything dumb whatsoever. And, of course, the only way for me to do that is to just not say anything at all. So, see ya tomorrow.

Nah, just kidding. Instead, I thought I’d do something potentially dumber, and address a question that we get asked every now and then, something I don’t know if we’ve ever really answered, “How come you guys never use Windows Script Files?” And that’s a good question; as one recent email pointed out, Windows Script Files (.wsf files) are the latest twist on Windows Script Host scripts. As sort of the public face of scripting, “ … shouldn’t you guys be promoting the most recent innovations?”

To tell you the truth, I don’t know; I have to admit that I’m not always a fan of the latest innovations. For example, fellow Scripting Guy Dean Tsaltas and I once toured Microsoft’s experimental home of the future, a showcase for all the latest innovations in home living and lifestyle. To be honest, I wasn’t all that impressed; although there were some admittedly cool gadgets, I’m not so sure I want my refrigerator calling the grocery store any time we’re out of milk. In fact, I’m not so sure I want my refrigerator calling anybody, regardless of how much milk we have or don’t have. I just don’t like the idea of my household appliances spending all day on the phone when their supposed to be keeping my lettuce cold or microwaving my burrito. “Hey, I’m on the phone right now. Can’t you make your own toast for once?” Scary.

So what about .wsf files: thumbs-up or thumbs-down? That’s something we Scripting Guys actually discussed a long time ago, and the verdict was split: some of us liked the idea of using .wsf files, others didn’t. In turn, that means that what you’re about to read is just my opinion, and doesn’t necessarily reflect the combined wisdom of the Microsoft Scripting Guys. (But until they start blogging, like they all promised to, then I guess my opinion is the only one that matters.)

For those of you that aren’t familiar with Windows Script Files, the .wsf format allows you to use some predefined XML tags within a script. Does that have any advantages? Maybe; that’s something we’ll talk about in a moment. But for the purposes of the Script Center and the scripting documentation we produce, it has one definite disadvantage, which is why we ultimately chose not to use them. Here, for example, is the ubiquitous Hello, World script, written as a .vbs file:

Wscript.Echo "Hello, world."

(The classics never go out of style, do they?)

Now here’s the same Hello, World script written as a .wsf file:

<job id="Job1">

    <script language="VBScript">

        Wscript.Echo "Hello, world."

    </script>

</job>

You can see the immediate problem (at least for us): wsf files, at a minimum, add four extra lines of code to every script, lines of code that – in the case of a very simple script, such as the one above – really don’t add anything of value. We made the decision to keep our scripts as short and as bare-bones as possible; as I’ve explained earlier, that’s why we typically don’t include error-handling, that’s why we typically don’t declare variables, that’s why we typically don’t include code for parsing command-line arguments. Having adopted that philosophy, it didn’t make any sense to take all our scripts and enclose them in four lines of extraneous XML tags. One line of code or five lines of code? No contest.

On the other hand, there’s no doubt that our needs don’t exactly reflect the needs of real, live system administrators. So while the Scripting Guys might have little use for .wsf files, that doesn’t mean they aren’t useful for other people. Or does it? Let’s take a look at some of the capabilities found in Windows Script Files, and then weigh the pros and cons of each.

Include files. This probably seems like a no-brainer: after all, everybody and their dog wants the ability to use include files within a script. For newcomers to the world of scripting, an “include file,” in an admittedly simplistic definition, is a file you can reference from another file. In the case of a script, you can actually run Script A from within the Script B as if the code from Script A was part of Script B. (Eep; that last sentence doesn’t even make any sense to me.)

Let me try to explain this a little better. For example, suppose you have a script that has all the code required to connect to a SQL Server database. Using the .vbs format, you’d have to copy and paste that code into each script that needed to connect to the database (and then, of course, if that code ever changed, you’d have to change every script that uses the code). With an include file, you write then code once, then call that definitive, connect-to-SQL-Server script from within innumerable other scripts. If the code ever has to change, you just modify that lone include file, and you’re done.

So what’s wrong with that? Nothing really. (Well, it does make it a bit harder to share scripts. After all, you can’t just a copy a script and give it to someone; you have to make sure you give them all the include files as well.) For Scripting Guy purposes, however, include files pose a couple of problems. For example, here’s a .wsf file that connects to a database and then does the Hello, World thing again:

<job id="Job1">

   <script language="VBScript" src="connect_to_sql.vbs">

   </script>

   <script language="VBScript">

       WScript.Echo "Hello, world."

   </script>

</job>

As you can see, from an educational point of view, we have a problem: a lot of the things that are going on in this script are “hidden” from view; that’s because they’re taking place in the include file. If our goal is to show people an example of a script that connects to a database and then performs some action, we’ve only done half the job; after all, you don’t see the code that connects to the database.

The other problem we have is logistical: you can’t just copy the script above and run it. Instead, you have to copy not only the script shown above, but also Connect_to_SQL.vbs. As much as possible, we try to create stand-alone scripts, scripts that you can just copy and run. Obviously we can’t do that if a script has an include file.

However, I’d be interested in hearing whether any of you use (or think you might use) include files for system administration scripts. What kind of things do you put in those include files? Why? Is this capability useful enough that we ought to draw more attention to it?

Access to type libraries. Many COM objects require you to specify certain values when carrying out different tasks. For example, when you use the FileSystemObject to work with text files, you need to specify whether a file is being opened for reading, for writing, or for appending. To do that, you either have to pass the name of a constant (ForReading, ForWriting, ForAppending), or the value assigned to that constant (1, 2, 8). Both the names and the values of these constants can be found in the FileSystemObject’s type library. Programming languages such as VB.NET or C# can access type libraries directly; scripting languages typically cannot. Because of that, you have to manually define constants such as ForAppending when writing a WSH script.

Unless, that is, you happen to be using the .wsf format. In that case, you can use the <reference> tag to gain direct access to the type library. Notice that, in the following script, we don’t define the constant ForWriting, However, when we run this script the correct value (2) will be echoed to the screen. Why? Because the reference tag looked at the type library and got the value of ForWriting for us:

<job id="Job1">

    <reference object="Scripting.FileSystemObject">

    </reference>

    <script language="VBScript">

        WScript.Echo ForWriting

    </script>

</job>

At first glance this seems pretty handy, and it is. My only question is this: How often do you need to refer to type libraries in system administration scripts? You will occasionally need to do this with ADSI and you’ll always need to do this with the FileSystemObject. But very few WMI scripts require access to a type library. Furthermore, in a lot of cases you only need to reference a handful of constants anyway. For example, we could rewrite the above script with just two lines of code:

Const ForWriting = 2

Wscript.Echo ForWriting

Again, this is not the most realistic example, but any time you use the FileSystemObject to read and write text files you’ll use no more than three constants: ForReading, ForWriting, ForAppending. So: Is it worth including all the XML tags just to avoid defining these constants? In the case of the FileSystemObject, I’m tempted to say no.

But what about cases involving other COM objects? Well, even then I’m still not convinced. The problem is that the <reference> tag doesn’t actually tell you what the defined constants are (WSH knows what the constants are, but it’s not telling). For example, the WMI provider for System Restore uses a number of constants, and if you know what the names of those constants are then you can use the <reference> tag and have at it. But how many people know the names of the constants for System Restore? (No, I mean besides you. Oh, and that other guy over there.) You’re gonna have to look these up anyway, so I’m not sure that it’s any less work to just copy them after you do look them up.

Besides, for educational purposes I actually like seeing the constants defined right in the script:

Const ForReading = 1

Const ForWriting = 2

Const ForAppending = 8

I think this makes it a little bit easier for people to understand what these things are. Otherwise, and until you are conversant with the FileSystemObject, something like ForWriting seems to materialize out of the blue. But, then again, I tend to look at scripts as teaching tools rather than production tools. What do you guys who actually use scripts think about this?

Multiple jobs. Windows Script Files allow you to divide scripts into jobs. For all intents and purposes, jobs are separate scripts that run only when explicitly called upon. For example, the following sample script has two jobs (Job1 and Job2). If you just run the script as-is, with no parameters, only Job1 will run. To run Job2, you need to specify Job2 as a parameter; for example:

cscript myscript.wsh //job:job2

To run both jobs, you have to specify them both as parameters:

Cscript myscript.vbs //job:job1 //job:job2

Here’s what the code looks like:

<package>

   <job id="Job1">

        <script language="VBScript">

            WScript.Echo "Hello, world."

        </script>

    </job>

    <job id="Job2">

        <script language="VBScript">

            WScript.Echo "Hello again, world."

        </script>

    </job>

</package>

I have to admit that this feature has me a little stumped. I can make a case for the other capabilities of .wsf files (e.g., direct access to a type library), but I’m not sure why you would want to divide a script into multiple jobs. Yes, you can call an individual job within the script, but you could also call an individual function within a script. In addition, suppose I had one script with 50 jobs in it. How do I know what jobs are in that script, and how do I know what names have been assigned to those jobs? I’d probably find it easier to have 50 little scripts, each with a readily-identifiable file name, as opposed to one 50-job script. But I readily concede that I might be missing something here. Does anybody out there use .wsf files with multiple jobs? If so, how come?

Multiple languages. Not only do .wsf files allow you to write scripts that have multiple jobs, but each of those jobs could theoretically be written in a different language. Here, for example, is a variation on our multiple job script; notice that Job1 is written in VBScript, and Job2 is written in Jscript:

<package>

   <job id="Job1">

        <script language="VBScript">

            WScript.Echo "Hello, world."

        </script>

    </job>

    <job id="Job2">

        <script language="JScript">

            WScript.Echo("Hello again, world.");

        </script>

    </job>

</package>

I suppose this gives you added flexibility, but I’m not sure how realistic it is. After all, suppose you were writing a script that either started or stopped a service. Is there any reason why you’d want to start the service using VBScript and stop it using Jscript? That might not be a very good example, but – again, recognizing the fact that our primary focus is on system administration scripting – are there reasons why you might want to write half of a script using VBScript and half a script using Jscript? If so, I’d be interested in hearing them. One reason I can think of not to do this is the fact that it makes it very hard for someone else to edit/modify your script; after all, they’d have to be conversant in at least two scripting languages. A lot of system administrators have taken the time to learn one scripting language; I’m not sure how many have taken the time to learn two.

For us, of course, this was an easy one: Because we write all our scripts in VBScript, the fact that .wsf files allow you to use multiple languages in a single script wasn’t exactly a big selling point.

Usage instructions. One thing that .wsf files do provide is nice usage instructions. Usage instructions are the bits of help text that appear when you type the script name followed by /? or when you fail to enter the proper command-line arguments. Windows Script Files make it very easy to add usage instructions to a script. For example, all you have to do to have instructions displayed when a user types /? is to add the <runtime> and <description> tags:

<job id="Job1">

    <runtime>

        <description>

Here are usage instructions for running this script.

        </description>

    </runtime>

     <script language="VBScript">

         WScript.Echo "Hello, world."

      </script>

</job>

Again, pretty cool, but is it enough to bother with? When we first looked at the XML elements for arguments, we got kind of excited. (Well, as excited you can get by looking at XML elements.) That’s because you can mark an argument as being required. Combined with the usage stuff, we thought this was going to be really cool; we thought that if you started the script without one of the required arguments that the script would automatically quit and display usage instructions. Alas, it didn’t work that way; marking an argument as required is useful for someone reading your code, but it doesn’t affect the way the script runs. In fact, although the usage stuff is pretty cool, that’s about it when it comes to argument handling: you actually have to use the same WSH code for handling arguments that you would in a .vbs file. What you get from the .wsf format is a more automated way to display usage instructions; what you don’t get (and what would arguably be more useful) is a more automated way to handle arguments. You can mark an argument as required, but it’s still up to you to write the code to see if this required argument was supplied and, if it wasn’t, to take some sort of action.

To me, the usage stuff perfectly sums up the .wsf format: it’s got some really nice features, but I’m not convinced that these features are all that useful, at least not for system administrators trying their hand at script writing. On the other hand, I have no particular dog in this hunt; if someone can convince me that the .wsf format is superior than the plain old .vbs format, well, I’m more than willing to listen. (And I know at least one Scripting Guy who’s champing at the bit to see us start using .wsf files.) I’d love to hear opposing viewpoints on this issue.