Re: Automated WSF argument validation: demo script.
From: Alex K. Angelopoulos [MVP] (aka-at-mvps-dot-org)
Date: 02/22/04
- Next message: Al Dunbar [MS-MVP]: "Re: Declare objects without using them - wasteful?"
- Previous message: Al Dunbar [MS-MVP]: "Re: debugging .wsf files for use under WSH"
- In reply to: name: "Re: Automated WSF argument validation: demo script."
- Next in thread: Dr John Stockton: "Re: Automated WSF argument validation: demo script."
- Reply: Dr John Stockton: "Re: Automated WSF argument validation: demo script."
- Messages sorted by: [ date ] [ thread ]
Date: Sat, 21 Feb 2004 20:37:59 -0500
How much RAM?
The "right" kind of tool for this job is a COM-accessible XML stream reader
of course. Having to roll through the entire DOM causes problems.
name wrote:
> msxml has a 'natural' load degrader once a file passes the
> some 350 kb limit on a 100 MHz Pentium.
>
>
>
> "Alex K. Angelopoulos [MVP]" <aka-at-mvps-dot-org> wrote in message
> news:OUeX%23MS9DHA.972@tk2msftngp13.phx.gbl...
>> Thought I would pass this around to see how people like it. My biggest
>> concern at present is that it is probably too large for easy use as a
>> drop-in class.
>>
>> The material below the dashed line should all be saved as a wsf file
>> for testing. The script is a demo of automating argument validation
>> based on the details of named and unnamed argument tags for a WSF job.
>> It includes lengthy comments and some rudimentary errorchecking and
>> execution tracing support.
>> ============================================================
>>
>> <?xml version="1.0" encoding="ISO-8859-1" ?>
>> <job id="main">
>> <runtime>
>> <description><![CDATA[
>> DESCRIPTION
>> This script is a template for a WSF which includes a class for
>> automated self-checking of correct arguments. The global code section
>> at the top also demonstrates the argument parsing technique.
>> To use the tool, as done here, instantiate the CWsfArgumentValidator
>> class. Validation is performed by then calling its Validate method;
>> this method requires that a WScript instance be passed to it to allow
>> the code to be moved to a WSC with no modifications.
>> By default, if any validation failures occur during the checks, the
>> class will call Wscript.Quit. The following checks are done during
>> validation. + If a named argument has an attribute required = "true"
>> and it was not specified on the commandline, validation will fail.
>> + If a named argument's type does not match the type of the supplied
>> argument, validation will fail. (This can be suppressed - see ALLOWING
>> NON-STRICT ARGUMENT TYPES below).
>> + If the count of unnamed arguments does not meet the numeracy
>> requirements derived by summing the values of the required attributes
>> as non-negative integers and testing upper bound as well if no unnamed
>> arguments have many="true", then validation will fail.
>> Optionally, you can require that the host be cscript and cause the
>> script to exit if it is not (see REQUIRING CSCRIPT AS THE HOST below).
>>
>> ]]></description>
>> <example><![CDATA[
>> The allowed usage of the script is shown above. The following example
>> discussion just shows what behaviors can be expected from modifying
>> the code or from aberrant arguments.
>>
>> REQUIREMENTS
>> (1) This MUST be used from WSH 2.0 or higher.
>> MSHTA, IE, or any other active host will not work. (One exception -
>> you can use this inside the script control if you pass a reference to
>> WScript in to it and then on in to the class).
>> The reasons are that it is built on (a) checking the Arguments.Named
>> and Arguments.UnNamed collections of the host, and (b) parsing named
>> and unnamed nodes in the valid XML document hosting the class, as
>> discovered by checking the host's ScriptFullName property. There might
>> be bits that don't work in WSH versions earlier than 5.1, but I don't
>> know for sure. (2) This most obviously must be used from a WSF written
>> as explicit XML. If it isn't correct XML, it can't be parsed by the
>> XML parser. If it isn't a WSF, it isn't XML anyway.
>> (3) Some recent version of Microsoft's COM-accessible XML parser must
>> be installed - one with the ProgId "MSXML2.DOMDocument" registered.
>>
>> SIMPLEST USAGE
>> Write a single-job WSF. Make sure the arguments in the <runtime>
>> element match what you want. Then insert the class anywhere in the
>> script, and the 3 lines of code below anywhere in the script:
>> With New CWsfArgumentValidator
>> .Validate(WScript)
>> End With
>> That will do all checks for you.
>>
>> SPECIFYING A PARTICULAR JOB
>> If you have multiple jobs and named your job "main", then you're
>> better off using a WSC-based version, but in any case, you can check
>> ONLY arguments specified within the job by setting the JobId property
>> before calling Validate(), like this:
>> .JobId = "main"
>>
>> REQUIRING CSCRIPT AS THE HOST
>> Before calling Validate(), set the cscript requirement property to
>> True: .RequireCScript = True
>> If CScript is not the hosting application, help display will be called
>> and the script will quit.
>>
>> ALLOWING NON-STRICT ARGUMENT TYPES
>> If your specifications use string or boolean named arguments, but you
>> wish to allow "degenerate" values which are not actually string or
>> boolean - or if you wish the simple argument to have a boolean or
>> string value in some circumstances you will check yourself - then you
>> can turn off the typechecking of arguments. To do this, before calling
>> Validate(), set the AllowLooseTyping property to True:
>> .AllowLooseTyping = True
>> Then a nonstandard commandline such as the following will work:
>> /b /d /f arg21
>>
>> SPECIFYING THE EXIT ERRORLEVEL
>> When the script terminates, its exit errorlevel is 2 by default, to
>> make it distinct from "normal" termination due to script errors or
>> explicitly calling the commandline help switch /?. You can set this to
>> a custom numeric by setting ExitErrorLevel before validation:
>> .ExitErrorLevel = 3
>>
>> SUPPRESSING AUTOMATIC FAILURE
>> If you simply want to confirm that there is an error - or to do
>> extended argument processing while tracing class execution - you can
>> set ContinueOnFailure to true before validation:
>> .ContinueOnFailure = True
>> The script will then continue processing even after validation has
>> failed; you can save the return value of Validate() if you wish, or
>> check the ValidationFailed property afterwards:
>> WScript.Echo .ValidationFailed
>> ]]></example>
>>
>> <named name="a" helpstring="returns this help message" type="simple"
>> required="false"/>
>> <named name="b" helpstring="returns this help message" type="simple"
>> required="true"/>
>> <named name="c" helpstring="returns this help message" type="boolean"
>> required="false"/>
>> <named name="d" helpstring="returns this help message" type="boolean"
>> required="true"/>
>> <named name="e" helpstring="returns this help message" type="string"
>> required="false"/>
>> <named name="f" helpstring="returns this help message" type="string"
>> required="true"/>
>> <unnamed name="arg1" helpstring="helptext for arg1" many="false"
>> required="0"/>
>> <unnamed name="arg2" helpstring="helptext for arg1" many="true"
>> required="1"/>
>>
>> </runtime>
>> <script language="VBScript"><![CDATA[
>> Option Explicit
>> 'Dim argValidator
>> 'Set argValidator = new CWsfArgumentValidator
>> With New CWsfArgumentValidator
>> '.JobId = "main"
>> '.RequireCScript = True
>> '.AllowLooseTyping = True
>> '.TraceExecution = True
>> '.ExitErrorLevel = 3
>> '.ContinueOnFailure = True
>> .validate(WScript)
>> 'WScript.Echo .ValidationFailed
>> End With
>>
>>
>>
>>
>>
>>
>> ' =====================================================================
>> ' VBScript CWsfArgumentValidator class
>> '
>> ' Property ContinueOnFailure(value As Boolean) [Get/Let]
>> ' Default value: False
>> ' If the class encounters invalid supplied commands, a
>> ' ContinueOnFailure value of False causes it to call
>> ' WScript.Quit(ExitErrorlevel).
>> ' Validation failure can still be determined by checking whether the
>> ' ValidationFailed property is True.
>> '
>> ' Property AllowLooseTyping(value As Boolean) [Get/Let]
>> ' Default value: False
>> ' If this is set to True, strict compliance with argument type
>> ' specifications - simple, boolean, or string - is not checked.
>> '
>> ' Property ExitErrorlevel(value As Long) [Let]
>> ' Default value: 2
>> ' Sets the errorlevel returned to the shell if the class encounters
>> ' an error in supplied commandline arguments during validation and
>> ' ContinueOnFailure is set to the value False (default).
>> '
>> ' Property RequireCscript(value As Boolean) [Get/Let]
>> ' Default value: False
>> ' If this is set to True, the class will check to see if an
>> ' application named "cscript.exe" is the host, and will fail if it
>> is ' not.
>> '
>> ' Property JobId(value As String) [Get/Let]
>> ' Default value: vbNullString
>> ' If you are using a multi-job WSF, you can specify the id for the
>> ' job which is being validated.
>> '
>> ' Property ValidationFailed() As Boolean [Get]
>> ' Default value: False
>> ' This is set to True whenever a validation failure occurs. Only
>> ' useful if the ContinueOnFailure property has been set to True.
>> '
>> ' Function Validate(host)
>> ' Initiates script validation; returns False if validation failed,
>> ' true if it succeeded.
>> '
>> ' Property TraceExecution() as Boolean
>> ' Default value: False
>> ' If set to true, sends trace output to StdErr.
>> '
>> ' Property Wsh() As IHost_Class [Get]
>> ' Default value: none.
>> ' Used to hold an internal global reference to the WSH host.
>> ' =====================================================================
>> class CWsfArgumentValidator
>>
>> private xml, m_Failed
>> public ExitErrorLevel
>> public ContinueOnFailure
>> public AllowLooseTyping
>> public RequireCScript
>> public JobId
>> public TraceExecution
>> public Wsh
>>
>> public property get ValidationFailed()
>> ' This tells us if argument validation had problems.
>> ' Obviously, this is only useful if ContinueOnFailure was true.
>> ValidationFailed = m_Failed
>> end property
>>
>>
>> public function Validate(host)
>> ' takes a ref to WScript as an argument - not necessary for a class
>> ' embedded directly in a WSF, but good if moving to an external
>> ' COM object.
>> ' Proceeds to do full parse and assembly of argument data.
>> ' To make checking ValidationFailed unnecessary if we are NOT using
>> ' the default exit on failure, this function returns True if the
>> ' validation succeeded, false otherwise.
>> Validate = 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.
>> Set Me.Wsh = host
>> If Not ValidScriptHost(Me.Wsh) Then Exit Function
>> If Not xml.Load(Me.Wsh.ScriptFullName) Then Exit Function
>> if Not Me.JobId = vbNullString Then
>> set xml = xml.selectSingleNode("//job[@id='" & Me.JobId & "']")
>> end if
>> 'validateNamed xml.getElementsByTagName("named")
>> 'validateUnNamed xml.getElementsByTagName("unnamed")
>> Dim node
>> For Each node in xml.selectNodes("//named")
>> validateNamed node
>> Next
>> validateUnNamed xml.selectNodes("//unnamed")
>> Validate = Not(m_Failed)
>> end function
>>
>>
>> private sub class_initialize()
>> 'create XML DOMDocument and load the current script into it.
>> set xml = createobject("MSXML2.DOMDocument")
>> xml.async = False
>> m_Failed = False
>> ' By default, we will quit if we encounter invalid user arguments.
>> Me.ContinueOnFailure = False
>> ' By default, we want arguments to comply with
>> simple/boolean/string Me.AllowLooseTyping = False
>> ' By default, we don't require cscript as the host.
>> Me.RequireCScript = False
>> ' By default, we assume global argument selection - this should
>> ' be set by the scripter to job name for multijob WSFs.
>> Me.JobId = vbNullString
>> ' By default, if we exit on failures the errorlevel returned will
>> ' be 2. This ensures that it is clear the script errored due to
>> ' something other than /? spec or generic compile/runtime errors.
>> Me.ExitErrorLevel = 2
>> ' We have some lines to output information to StdErr along the way;
>> ' By default, none of this will be displayed.
>> Me.TraceExecution = False
>> end sub
>>
>>
>> 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 Me.Wsh.Arguments.Named.Exists(name) Then
>> Trace "This argument was found on the commandline as well"
>> If Not Me.AllowLooseTyping Then
>> Trace "We require strict argument types."
>> ' We need to validate argument types.
>> value = Me.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.
>> 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"))
>> next
>> 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 = Me.Wsh.Arguments.UnNamed.Count
>> If count < required then
>> Die()
>> ElseIf count = required Then
>> ' we're fine - required = count
>> ElseIf MaxisMin Then
>> ' We have more than the count and shouldn't. Die!
>> 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
>> else
>> 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
>> else
>> 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 Me.RequireCScript Then
>> Dim hostName, test
>> hostName = "cscript.exe"
>> test = LCase(Right(host.FullName, Len(hostName))) = hostName
>> If Not test Then
>> ' Ugly - but MsgBox gets the point across in the GUI.
>> MsgBox "This class must be hosted by " & hostname
>> 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.
>> Die()
>> Else
>> ValidScriptHost = True
>> End If
>> 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 < 2 Then
>> ' We are probably running on a system which gets its WSH from
>> ' Win98 original or NT Option Pack 4. In either case, we can't
>> ' possibly be running in a WSF, so we need to die again.
>> ' Since VBScript didn't have classes until version 5, something
>> ' is really messed up, so let's give some explicit feedback.
>> ' Just to be courteous, we will use WScript.Echo in case this is
>> ' a console session...
>> Me.Wsh.Echo "This code must be used within a WSF file hosted", _
>> "in WSH 2.0 or higher with VBScript 5 or higher installed."
>> Me.Wsh.Echo "The correct version of WSH can be downloaded", _
>> "from http://msdn.microsoft.com/scripting."
>> Die()
>> Else
>> ' We don't require cscript and we are indeed in WSH, so
>> ' we're valid!
>> ValidScriptHost = True
>> End If
>> end function
>>
>>
>> private sub Die()
>> m_Failed = true
>> if Not ContinueOnFailure then
>> Me.Wsh.Arguments.ShowUsage()
>> Me.Wsh.Quit(Me.ExitErrorLevel)
>> End If
>> end sub
>>
>> private sub Trace(s)
>> If Me.TraceExecution Then Me.Wsh.StdErr.WriteLine s
>> end sub
>>
>> end class
>> ]]></script>
>> </job>
- Next message: Al Dunbar [MS-MVP]: "Re: Declare objects without using them - wasteful?"
- Previous message: Al Dunbar [MS-MVP]: "Re: debugging .wsf files for use under WSH"
- In reply to: name: "Re: Automated WSF argument validation: demo script."
- Next in thread: Dr John Stockton: "Re: Automated WSF argument validation: demo script."
- Reply: Dr John Stockton: "Re: Automated WSF argument validation: demo script."
- Messages sorted by: [ date ] [ thread ]
Relevant Pages
|