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."




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 language="VBScript">

       WScript.Echo "Hello, world."




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">


    <script language="VBScript">

        WScript.Echo ForWriting




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:



   <job id="Job1">

        <script language="VBScript">

            WScript.Echo "Hello, world."




    <job id="Job2">

        <script language="VBScript">

            WScript.Echo "Hello again, world."





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:



   <job id="Job1">

        <script language="VBScript">

            WScript.Echo "Hello, world."




    <job id="Job2">

        <script language="JScript">

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





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">



Here are usage instructions for running this script.



     <script language="VBScript">

         WScript.Echo "Hello, world."




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.


Comments (21)

  1. Andreas Häber says:

    I used .wsf files last year 🙂

    I really liked the usage-tags, but it would be really nice if it took care of error-handling for usage as well. (Seems like this will come in MSH (see, so I’m looking forward to that one! Lot’s of other features to look forward to in MSH as well :))

    What I used the .wsf scripts for was to run tests of a system written in C++. So, with that background I really liked the include-feature as well :). Lot’s of the tests had things in common, so for me it was natural to split those out in it’s own file and include it.

    For me the WSH environment is nice to create tests, since it’s easy to get information from the system+network using WMI and such. Creating the tests with c++ would’ve taken a lot more time and effort IMO.

  2. IM says:

    I used wsf files to write scripts that generated software patches, lots of file copying, calling of regssvr32 stopping/starting services, MTS package stuff.

    Then recently, I rewrote it in C# – I’m an intellisense kind of a guy! Much nicer environment to code in, even for ‘scripty’ tasks.

    I used the ‘job’ facility, but found that ultimately it caused me more confusion than it helped. I had a bunch of BAT files that ran other batch files that ran jobs in the WSF file. What a mess!

    Can’t see myself using WSF again, but I think I am one of those people who is ‘allergic’ to run-time checked languages in general. I hated python too. 🙂

  3. Peter Forret says:

    I’ve been using WSF scripts for more than a year now, and I do not make .vbs scripts anymore. Why?

    I don’t use the ‘multiple jobs’ feature.

    I don’t use the ‘multiple languages’ feature (I’m a VBScript guy)

    I don’t use the ‘type library’ feature.

    But one simple feature alone makes it worthwhile, one you didn’t mention. It’s the integrated option parsing (which is related to your ‘usage’ feature)

    I can specify my options in any order, not use them if I don’t want to and simply use WScript.Arguments.Named to access them.

    I’ve added a small wrapper library I include in all my scripts that allows me to do

    sSITE =GetNamedString("site","none")

    (get the /site:yyy option, or if it’s not specified, give me default value ‘none’)

    E.g: one of my scripts is a timer/stopwatch.

    Usage: waTimer.wsf CMD PARAM [/total:value] [/bar:value] [/calc:value] [/unit:value] [/scale:value] [there’s more but you get the picture …]

    I can call it as

    waTimer STOP COPYJOB /total:1000 or

    waTimer /total:1000 /scale:60 STOP COPYJOB /unit:KB/min

    Trivial, but really handy.

    Another thing I use in WSF is the <resource> tag:

    <resource id="History">

    * v1.1 – 2004-02-24 – Bugfix – enhanced performance

    * v1.0 – 2004-01-13 – First public release


    I use it to have all my version/name/copyright/… information in easy editable tags in the first 20 lines of my script, and then use them as getResource("History") further on. This is a nice-to-have, I know.

    If you want to see the templates I made for .wsf scripts, check out .

    Love the blog by the way,


  4. Steven Griffiths says:

    I’ve used the .wsf format in my scripts for about 6 months now and use it mainly for the usage instructions and to include a standard set of subroutines (mainly logging and error handling related) into my scripts. Sure, I could just cut and paste the routines and have a single file, but I have a deployment solution that comprises several scripts and each needs access to the logging and error handling routines. Including a single file simply means there’s less code to manage.

    I take it you’re not a .wsf file fan then?

    So when are Dean, Bob and Ethan going to contribute to this blog? Seems a bit unfair to leave all the work to one man! Which one of the is pro- .wsf?

  5. Eric Lippert says:

    I’ve posted some comments following up on various issues raised in this entry.

  6. Tarjei T. Jensen says:

    Eric Lippert’s comments are to the point.

    However I’d like to point out that .wsf messes up the structure of the script. You really don’t want to have all that extranous stuff there.

    Includes and constants are nice, but it is more important that the script reads well. BTW A readable programming language is usally more verbose than the other ones.


  7. Bob Riemersma says:

    Gonna hold my nose and hope markup brackets don’t break something here…

    Speaking of <resource> tags:


    <object id="objFSO" progid="Scripting.FileSystemObject"/>

    <reference object="ADODB.Recordset"/>

    <object id="objRS" progid="ADODB.Recordset"/>

    <!– Using Excel 9 (Office 2000). Update the next two

    items appropriately for other versions of Excel.

    Next <reference> is EXCEL9.OLB TypeLib’s GUID –>

    <reference guid="{00020813-0000-0000-C000-000000000046}"/>

    <object id="objXL" progid="Excel.Application.9"/>

    <resource id="strConnection">


    Extended Properties="Text";

    Data Source="LogFiles"


    <resource id="sqlJoin">


    [Employee ID],

    [Last Name],

    [First Name],

    [Middle Initial],


    [Job Title],

    Left(DUMP.NTID, InStr(DUMP.NTID, "") – 1) AS [Domain],

    Mid(DUMP.NTID, InStr(DUMP.NTID, "") + 1) AS [User’s Name],


    FROM Dump.txt AS DUMP



    ORDER BY [Last name] ASC, [First Name] ASC, [Middle Initial] ASC;


    <script language="VBScript">

    Option Explicit

    Dim objFld, lngFlds

    Dim objWB, objWS, lngCol, strXLWB

    strXLWB = WScript.ScriptFullName

    strXLWB = Left(strXLWB, InStrRev(strXLWB, "")) & "Report.xls"

    If objFSO.FileExists(strXLWB) Then

    objFSO.DeleteFile strXLWB

    End If

    objRS.Open getResource("sqlJoin"), _

    getResource("strConnection"), _


    lngFlds = objRS.Fields.Count

    Set objWB = objXL.Workbooks.Add

    Set objWS = objWB.WorkSheets(1)

    With objWS


    For lngCol = 1 To lngFlds

    .Cells(1, lngCol) = objRS.Fields(lngCol – 1).Name


    .Rows("1:1").Font.Bold = True

    .Rows("1:1").HorizontalAlignment = xlHAlignCenter

    .Rows("2:2").CopyFromRecordset objRS

    .Columns("A:" & Chr(Asc("@") + lngFlds)).AutoFit


    objXL.ActiveWindow.FreezePanes = True

    .Cells(1, 1).Activate

    End With

    Set objWS = Nothing


    objWB.SaveAs strXLWB, xlWorkbookNormal

    Set objWB = Nothing


    MsgBox "Report.xls Created"



    Small example, and I left out the Schema.ini file guiding the Jet Text Driver. But you get the picture. The SQL is less of a hassle without concatenating a long string up inline.

  8. Bob Riemersma says:

    Got me!

    Lost my leading whitespace. *sigh* Live and learn.

  9. Alex Angelopoulos says:

    I generally have the same feelings that Eric Lippert does about this: WSFs are VERY useful, but for the role that scripts in the script center currently have, they’re a bit too much for clear communication of an idea.

    That said, it is quite possible to completely automate validation of arguments for simple sets – in other words, if all of the arguments are a mix-and-match global group without any constraints (like "/A cannot be specified if /W is also specified, unless it is the third Wednesday in…")

    At the bottom is the code for a WSC that does exactly this. Once registered on a system, you can call it from any XML-compliant WSF to automate validation of arguments; it will read the tags for your WSF and optionally the specific named job, then ensure that supplied commandline arguments match the named/unnamed specification.

    Example code use in a WSF:

    8< ———————————

    Set argEx = CreateObject("WshArgument.Extensions")

    Set argEx.Wsh = WScript

    argEx.RequireCScript = True

    ‘argex.JobId = "main"

    ‘argex.TraceExecution = True

    ‘argex.AllowLooseTyping = True

    ‘argex.ContinueOnFailure = True

    ‘argex.ExitErrorLevel = True


    ‘WScript.Echo "Was validation successful?", argEx.IsValid

    8< ———————————

    and the WSC code:


    <?xml version="1.0" ?>

    <package><component id="main">

    <?component error="true" debug="true"?>

    <registration progid="WshArgument.Extensions" classid="{790E104C-7A48-499F-8EDA-6D5CD0A56D93}" description="A WSF argument extension component, focusing on validation." remotable="no" version="1.0">

    <script language="VBScript">


    Function Register()

    With CreateObject("Scriptlet.TypeLib")

    .MajorVersion = 1: .MinorVersion = 0

    .AddURL "wshargex.wsc"

    .Path = "wshargex.tlb": .Name = .Path

    .Doc = "A WSF argument extension component, focusing on validation."

    .GUID = "{C920CF60-675F-4D7E-87E7-D9076F58BB20}"

    .Write: .Reset

    End With

    End Function

    Function Unregister(): End Function




    <property get name="IsValid" description="Whether or not the last invocation of ValidateArguments() returned a valid parse."/>

    <property put name="Wsh" description="Object reference to the script host to pass in to the component."/>

    <property get put name="TraceExecution" description=""/>

    <property name="JobId" description=""/>

    <property get put name="RequireCScript" description=""/>

    <property get put name="AllowLooseTyping" description=""/>

    <property get put name="ContinueOnFailure" description=""/>

    <property get put name="ExitErrorLevel" description=""/>

    <property get name="wshArgTypeSimple" description=""/>

    <property get name="wshArgTypeBoolean" description=""/>

    <property get name="wshArgTypeString" description=""/>

    <method name="Simple" description="" internalname="get_Simple">

    <parameter name="argName"/>


    <method name="String" description="" internalname="get_String">

    <parameter name="argName"/>

    <parameter name="defaultValue"/>


    <method name="Boolean" description="" internalname="get_Boolean">

    <parameter name="argName"/>


    <method name="ValidateArguments" description=""/>


    <object id="fso" progid="Scripting.FileSystemObject"/>

    <object id="xml" progid="MSXML2.DOMDocument"/>

    <script language="VBScript"><![CDATA[

    Option Explicit

    ‘ Initialization of global/public variables

    Const wshArgTypeSimple = 0

    Const wshArgTypeBoolean = 11

    Const wshArgTypeString = 8

    Dim AllowLooseTyping, ContinueOnFailure, ExitErrorLevel

    Dim IsValid, JobId, RequireCScript, TraceExecution, Wsh


    Sub Initialize()

    xml.async = False

    AllowLooseTyping = False

    ContinueOnFailure = False

    ExitErrorLevel = 2

    IsValid = vbNull

    JobId = vbNullString

    RequireCScript = False

    Set Wsh = Nothing

    TraceExecution = 0

    End Sub

    function put_Wsh(value)

    Set Wsh = value

    end function

    function get_TraceExecution()

    get_TraceExecution = TraceExecution

    end function

    function put_TraceExecution(value)

    TraceExecution = CBool(value)

    end function

    function get_RequireCScript()

    get_RequireCScript = RequireCScript

    end function

    function put_RequireCScript(value)

    RequireCScript = CBool(value)

    end function

    function get_AllowLooseTyping()

    get_AllowLooseTyping = AllowLooseTyping

    end function

    function put_AllowLooseTyping(value)

    AllowLooseTyping = CBool(value)

    end function

    function get_ContinueOnFailure()

    get_ContinueOnFailure = ContinueOnFailure

    end function

    function put_ContinueOnFailure(value)

    ContinueOnFailure = CBool(value)

    end function

    function get_ExitErrorLevel()

    get_ExitErrorLevel = ExitErrorLevel

    end function

    function put_ExitErrorLevel(value)

    ExitErrorLevel = CLng(value)

    end Function

    function get_IsValid()

    get_IsValid = IsValid

    end function

    function ValidateArguments()

    Trace "Entering ValidateArguments()"

    ‘ takes a ref to WScript as an argument.

    ‘ Proceeds to do full parse and assembly of argument data.

    ‘ To make checking IsValid unnecessary if we are NOT using

    ‘ the default exit on failure, this function returns True if the

    ‘ validation succeeded, false otherwise.

    ValidateArguments = False

    ‘ We try to die on failures; this ensures that if errors and

    ‘ interaction are suppressed that we don’t try to validate anyway.

    ‘ The fact that this is a function instead of a sub ensures that

    ‘ the user can actually TEST the return value without even looking

    ‘ at ValidationFailed as described above.

    Trace "Checking Whether host is valid."

    If Not ValidScriptHost(Wsh) Then Exit Function

    Trace "Host is valid."

    Trace "Loading script from " & Wsh.ScriptFullName

    If Not xml.Load(Wsh.ScriptFullName) Then Exit Function

    Trace "Loaded script successfully"

    Dim root

    if JobId = vbNullString Then

    Trace "No job specified, checking all named/unnamed elements."

    set root = xml


    Trace "job specified: " & JobId

    set root = xml.selectSingleNode("//job[@id=’" & JobId & "’]")

    end if

    Dim node

    Trace "starting named node validation."

    For Each node in root.selectNodes("//named")

    validateNamed node


    Trace "starting unnamed node validation."

    validateUnNamed root.selectNodes("//unnamed")

    ValidateArguments = IsValid

    Trace "Exiting ValidateArguments() at end"

    end function

    private sub validateNamed(node)

    ‘ Look for required named arguments find or Die.

    ‘ takes collection of "named" nodes as an argument

    dim name, value, nameType

    name = NodeAttribValue(node, "name")

    Trace "checking named argument node with name " & name

    If Wsh.Arguments.Named.Exists(name) Then

    Trace "This argument was found on the commandline as well"

    If Not AllowLooseTyping Then

    Trace "We require strict argument types."

    ‘ We need to validate argument types.

    value = Wsh.Arguments.Named(name)

    Trace "The commandline argument’s vartype and value: " _

    & VarType(value) & " " & value

    nameType = tovbVarType( NodeAttribValue(node, "type") )

    Trace "The argument spec is for an argument of type: " _

    & nameType

    If Not VarType(value) = nameType Then Die()

    End If

    ElseIf CBool( NodeAttribValue(node, "required") ) <> 0 then

    Trace "This is a required argument, but it wasn’t supplied."

    ‘ if required="true", confirm user supplied it or Die.


    ‘ Argument "Typechecking"

    end if

    end sub

    private sub ValidateUnNamed(unnamedNodes)

    dim MaxIsMin, required, node

    required = 0: MaxIsMin = true

    ‘ count required arguments, determine whether this is open number

    for each node in unnamedNodes

    ‘ see if argument count is unbounded

    if toLong(NodeAttribValue(node, "many")) = 1 then MaxIsMin = false

    ‘ add the count of required arguments from the unnamed

    required = required + toLong(NodeAttribValue( node, "required"))


    Trace "We require " & required & " unnamed arguments."

    Trace "Is the maximum unnamed count the same as minimum? " _

    & CStr(MaxIsMin)

    ‘now we test to see if we have enough

    dim count: count = Wsh.Arguments.UnNamed.Count

    If count < required then


    ElseIf count = required Then

    ‘ we’re fine – required = count

    ElseIf MaxisMin Then

    ‘ We have more than the count and shouldn’t. Die!


    End If

    end sub

    private function NodeAttribValue(node, attributeName)

    dim Attribute

    set Attribute = node.attributes.getNamedItem(attributeName)

    if typename(Attribute) = "Nothing" then

    NodeAttribValue = Empty


    NodeAttribValue = Attribute.nodeValue

    end if

    end function

    private function tovbVarType(typeString)

    ‘ Casts a named argument type attribute value to a vb VarType value.

    ‘ If the type value is not recognized, this returns null.

    select case lcase(typeString)

    case "boolean" tovbVarType = vbBoolean

    case "string" tovbVarType = vbString

    case "simple" tovbVarType = vbEmpty

    case else tovbVarType = vbNull

    end select

    end function

    private function toLong(value)

    ‘ takes any normal numeric or boolean string and coerces to long;

    ‘ treats true as +1

    if StrComp(value, "false", vbTextCompare) = 0 then

    toLong = 0

    elseif StrComp(value, "true", vbTextCompare) = 0 then

    toLong = 1


    toLong = CLng( value )

    end if

    end function

    private function ValidScriptHost(host)

    ‘ This routine first confirms that the hosting class actually IS

    ‘ the WSH host – a necessity for using the WScript.Arguments

    ‘ collection at this point, and for throwing a usable error in

    ‘ another host. Strictly speaking, this is an ugly hack though.

    ‘ Someone could create a class with the name "IHost_Class", and

    ‘ a REALLY cool argument parser would take parsed arguments from

    ‘ anywhere. All of the validation checks here other than testing

    ‘ whether CScript is required as a host are designed to guard

    ‘ against accidental misuse by a scripter, not an end-user.

    ‘ I don’t want to do the work that being cool would require, though.

    If RequireCScript And Not HostIsCscript() Then

    ValidScriptHost = False

    ‘ We’re going to abort processing anyway, but let’s try to

    ‘ Die() first. Trying to Die() will call the help, and then

    ‘ the user can see the note that the scripter (hopefully) put

    ‘ into the help text about needing cscript for some reason.


    ElseIf Not (TypeName(host) = "IHost_Class") Then

    ‘ MsgBox. Ugly again…this is the only way to get the point

    ‘ across if an erstwhile user has his error display suppressed

    ‘ in Internet Explorer.

    MsgBox "This class MUST be used from WSH. Currently hosted by:" _

    & vbCrLf & TypeName(host)

    ValidScriptHost = False

    ElseIf host.Version < 5.6 Then

    ‘ We aren’t going to be able to work on a lesser version of WSH.

    ‘ Just to be courteous, we will use WScript.Echo in case this is

    ‘ a console session…

    Wsh.Echo "This code must be used within a WSF file hosted", _

    "in WSH 5.6 or higher with VBScript 5 or higher installed."

    Wsh.Echo "The correct version of WSH can be downloaded", _




    ‘ We don’t require cscript and we are indeed in WSH, so

    ‘ we’re valid!

    ValidScriptHost = True

    End If

    end function

    private sub Die()

    Trace "Entering Die()."

    Trace "Value of ContinueOnFailure is: " & CStr(ContinueOnFailure)

    IsValid = False

    if Not ContinueOnFailure then

    Trace "We are exiting on failures; you should see help text."



    End If

    end sub

    private sub Trace(s)

    If TraceExecution Then fso.GetStandardStream(2).WriteLine s

    end sub

    public function HostIsCscript()

    HostIsCscript = _

    LCase(Right(Wsh.FullName, 11)) = "cscript.exe"

    Trace "Is the host is cscript? " & HostIsCscript

    end function

    function get_Simple(ByVal argName)

    ‘ Returns true/false for whether a simple argument ArgName was specified

    get_Simple = Wsh.Arguments.Named.Exists(argName)

    end function

    function get_Boolean(ByVal argName)

    ‘ Get an optional boolean named argument if it exists;

    ‘ if it does not exist, return the default value.

    If Wsh.Arguments.Named.Exists(argName) Then

    get_Boolean = CBool(argName)


    get_Boolean = CBool(defaultValue)

    End If

    end function

    function get_String(ByVal argName, ByVal defaultValue)

    ‘ If a string named argument was specified on the script’s

    ‘ commandline, return it. If it wasn’t, return a default value.

    If Wsh.Arguments.Named.Exists(argName) Then

    get_String = WScript.Arguments.Named(argName)


    get_String = defaultValue

    End If

    end function

    function get_wshArgTypeSimple

    get_wshArgTypeSimple = wshArgTypeSimple

    end function

    function get_wshArgTypeBoolean

    get_wshArgTypeBoolean = wshArgTypeBoolean

    end function

    function get_wshArgTypeString

    get_wshArgTypeString = wshArgTypeString

    end function


    <resource id="foo"><![CDATA[This is a test.]]></resource>



  10. Jaspreet Deol says:

    a way to avoid copying .vbs file into the .wsh file is to make .cls files instead.

    Just add the following to your .vbs file and change the extension to .cls

    class classname

    .vbs file code

    end class

    and then in the .wsh file add at the top:

    <script language="VBScript" src="ClassObjects … the path name…clsname.cls" />

    and there you are! No more copying huge chunks of code!

  11. Jaspreet Deol says:

    I get an error when trying to run this:

    Option Explicit

    ‘ *****************************************************************************

    ‘ Class: controller

    ‘ Created: 16 April 2004

    ‘ Version: 1.1

    ‘ Desc: This class monitors all the jobs where each job calculates one M.I.S

    ‘ statistic

    ‘ This allows for different jobs to be contained in a single file

    ‘ instead of multiple script files. Each job can be executed

    ‘ separately and is dealt as a seperate entity.

    ‘ Creator: JD08372

    ‘ *****************************************************************************


    <Job id="users"> // ‘cscript //Job:users controller.wsf

    <script language="VBScript" src="classesdbnames.cls"> </script>

    <script language="VBScript">

    Option Explicit

    dim objdbnames

    set objdbnames = New dbnames


    ‘ Clean up and exit once done

    Set objdbnames = Nothing





    the dbnames.cls runs alone when the class wrapper is taken away.

    I want to use the .wsf file as i have lots of classes like dbnames.

    Please can someone help me asap??

    my email address:

  12. Well I too resisted bothering with WSF files until very recently. The change came as my company got acquired by a big lumbering multinational, whose techs had little experience with scripting.

    I wrote a script to go out and force a "gpupdate /force" on all the computers in a particular OU to use in case of an emergency where we were blocking an executable using Software Restriction. I use WSH Remote Scripting, since some of the boxes seem to have configuraton pecularities that cause WMI’s Win32_Process object to gag when run remotely. I like to spawn a separate Controller process for each targeted PC. That way the parent script doesn’t have to wait around for a powered-down PC to time out before continuing to the next object. So I actually have three scripts: parent, a spawned controller script which in turn spawns a remote script.

    Do I dare send three scripts to these guys and hope that months after my term date (yup, they’re closing us down and telling us to relocate or get out) they’ll have kept all three in the same folder when time comes to actually use it? Uh, I decided to wrap them all into a single WSF file, and enjoy the quick and dirty objArgs.ShowUsage() support for free.

    Now that I have the boilerplate WSF set up, I’m finding myself using it for most of my administrative scripts that have to go out and connect to lots of PCs in turn, even if I don’t anticipate that they’ll get used by some poor Administrator Of The Day months after I’m gone 🙂

  13. Mark says:

    How to use wsf file with C#?

  14. Ken Schaefer says:

    In ASP we’ve always been able to store common routines/classes in a separate file and use an #include…

Skip to main content