'Allows for keeping configuration data for a class or script separate from the code. ' 'Requirements '1. The configuration files are manually created with comma-delimited key/value pairs that are read/loaded into a dictionary and accessed through the Item property. '2. The configuration files must have the configure filename extension. See LoadUserConfig for the one exception. '3. The configuration files must have the same base name as the associated class file or calling script. Two exceptions: the UserConfigFile and GlobalConfigFile do not have base names. '4. The configuration file for a script must be located in the same folder as the script. '5. The configuration file for a class should be in the project's class folder, or else in another folder that is specified by the LibraryPath property. If using another folder, then the LibraryPath property must be set before calling the LoadClassConfig method or getting the ClassConfigFile property. '6. The configuration files can have in-line or whole-line # comments. '7. Leading and trailing whitespace is ignored in both the key and the value. ' ' Note: Three config files GlobalConfigFile, UserConfigFile, and ScriptConfigFile, are loaded in that order on instantiation of the Configure class. The most recently loaded file takes precedence if there is a conflict, so if a different precedence is desired, then the files can be reloaded in a different order. A class configuration file is loaded by the LoadClassConfig method or the LoadFile method. ' 'Example: ' '
 'Test1.vbs (located anywhere)
With CreateObject( "VBScripting.Includer" )
Execute .Read( "Configurer" )
End With
With New Configurer
If .Exists( "command1" ) Then
MsgBox "command1: " & .Item( "command1" )
Else MsgBox "command1 key not found."
End If
End With
' ' # Test1.configure (located in the same folder as Test1.vbs) ' command1, wt powershell # requires Windows Terminal ' '
 'Test2.vbs (located in the "class" folder)
Class Test2
Sub Class_Initialize
With CreateObject( "VBScripting.Includer" )
Execute .Read( "Configurer" )
End With
With New Configurer
.LoadClassConfig me
If .Exists( "command2" ) Then
MsgBox .Item( "command2" )
End If
End With
End Sub
End Class
' ' # Test2.configure (also located in the "class" folder) ' command2, pwsh # requires PowerShell 6 or higher ' Class Configurer Private d 'Scripting.Dictionary object Private sh 'WScript.Shell object Private fso 'Scripting.FileSystemObject Private includer 'VBScripting.Includer object Private format 'StringFormatter object Private currentConfigFile 'string: a filespec Private missingCommaMsg, missingFileMsg Private scr 'filespec of the calling script or .hta Private parent 'parent folder of the calling script or .hta Sub Class_Initialize Dim srcX 'temp string Set d = CreateObject( "Scripting.Dictionary" ) Set sh = CreateObject( "WScript.Shell") Set fso = CreateObject( "Scripting.FileSystemObject" ) missingCommaMsg = "The configuration file is missing a required comma. File: " missingFileMsg = "Couldn't find the configuration file " If "HTMLDocument" = TypeName( document ) Then scrX = Mid( document.location.href, 9 ) scrX = Replace( scrX, "%20", " " ) scr = Replace( scrX, "/", "\" ) ElseIf "Object" = TypeName( WScript ) Then scr = WScript.ScriptFullName End If parent = fso.GetParentFolderName( scr ) Delimiter = "|" 'Some scripts (such as PushPrep.hta) may need to use a limited number of class procedures when the project's Setup.vbs has not been run, that is, when the following two objects are not available. So supress the errors. On Error Resume Next Set includer = CreateObject( "VBScripting.Includer" ) Set format = CreateObject( "VBScripting.StringFormatter" ) On Error Goto 0 If fso.FileExists( GlobalConfigFile ) Then LoadGlobalConfig End If If fso.FileExists( UserConfigFile ) Then LoadUserConfig End If If fso.FileExists( ScriptConfigFile ) Then LoadScriptConfig End If End Sub 'Property Item 'Parameter: a key (string) 'Returns: a value (string) 'Remark: Returns the value of the key/value pair for the specified key. Returns Empty if the key is not found. Property Get Item( key ) If d.Exists( key ) Then Item = d.Item( key ) Else Item = Empty End If End Property 'Property Count 'Returns an integer 'Remark: Gets the number of key/value pairs in the Configurer dictionary. Property Get Count Count = d.Count End Property 'Property Exists 'Returns a boolean 'Parameter: a string (key) 'Remark: Gets whether a given key/value pair exists in the Configurer dictionary. Parameter is the key. Property Get Exists( key ) Exists = d.Exists( key ) End Property 'Property Dictionary 'Returns an object reference 'Remark: Returns a reference to the Configurer object's dictionary object. Properties: CompareMode, Item, Key. Methods: Add, Exists, Items, Keys, Remove, RemoveAll. See the online docs for the Dictionary object. Property Get Dictionary Set Dictionary = d End Property 'For testing, allow resetting the dictionary Property Set Dictionary( newValue ) Set d = newValue End Property 'Method: LoadFile 'Parameter: a filespec 'Remark: Loads the specified configuration file's key/value pairs into the object's dictionary. See Item property. See also the LoadClassConfig and LoadScriptConfig methods. Sub LoadFile( file ) Dim stream 'text stream to read from a file currentConfigFile = file If Not fso.FileExists( file ) Then Err.Raise 51,, missingFileMsg & file End If Set stream = fso.OpenTextFile( file ) While Not stream.AtEndOfStream AddItem( stream.ReadLine ) Wend stream.Close End Sub 'Undocumented method AddItem. Parameter is a two-element, comma-delimited string. Adds the key/value pair to the dictionary object. Or if the key exists already, updates its value. Private Sub AddItem( str ) Dim a a = ParseLine( str ) If IsEmpty( a ) Then Exit Sub End If If d.Exists( a(0) ) Then d.Item( a(0) ) = a(1) Else d.Add a(0), a(1) End If End Sub 'Undocumented function ParseLine. Returns an array whose first element is the key and second element is the value. The parameter represents a single line from the configuration file. Returns Empty for empty lines and for lines beginning with #. Removes inline # comment. Function ParseLine( byVal line ) Dim a(1) 'two element array: key/value pair Dim j 'comma pointer Dim k '# pointer ParseLine = Empty ' Ignore a whole-line comment If "#" = Left( Trim( line ), 1 ) Then Exit Function ' Ignore a blank line ElseIf 0 = Len( Trim( line )) Then Exit Function End If 'check for missing comma j = InStr( line, "," ) If 0 = j Then Err.Raise 51,, missingCommaMsg & ConfigFile End If 'check for/remove inline comment k = InStr( line, "#" ) If k Then line = Left( line, k - 1 ) End If 'return the key/value pair a(0) = Trim( Left( line, j - 1 )) a(1) = Trim( Mid( line, j + 1 )) ParseLine = a End Function 'Method LoadScriptConfig 'Returns: 'Remarks: Loads the configuration file associated with the calling script. The configuration file's key/value pairs are added to the Configurer object's dictionary object, or if the key exists already, the value is updated. Sub LoadScriptConfig LoadFile ScriptConfigFile End Sub 'Property ScriptConfigFile 'Returns a filespec 'Remarks: Returns the filespec of the configuration file associated with the script that is using the Configurer object, the calling script or .hta. The file doesn't have to exist. Property Get ScriptConfigFile ScriptConfigFile = _ fso.GetParentFolderName( scr ) & "\" & _ fso.GetBaseName( scr ) & ".configure" End Property 'Method LoadClassConfig 'Parameter: a string or an object reference 'Remarks: Loads the configuration file associated with a class file. The configuration file's key/value pairs are added to the Configurer object's dictionary object, or if the key exists already, the value is updated. The parameter may be 1) the class name, or 2) an object reference to an instance of the class, or 3) the keyword me, if called from within the class. Public Sub LoadClassConfig( classInfo ) LoadFile ClassConfigFile( classInfo ) End Sub 'Property ClassConfigFile 'Returns a filespec 'Parameter: a string or an object reference. 'Remarks: Returns the filespec of the configuration file associated with a class (.vbs) file. The file doesn't have to exist. The parameter may be 1) the class name, or 2) an object reference to an instance of the class, or 3) the keyword me, if called from within the class. Property Get ClassConfigFile( classInfo ) Dim baseName If vbString = VarType( classInfo ) Then baseName = classInfo Else baseName = TypeName( classInfo ) End If ClassConfigFile = format( Array( _ "%s\%s.configure", _ LibraryPath, baseName _ )) End Property 'Method LoadUserConfig 'Remarks: Loads the user configuration file at %UserProfile%\.VBScripting. See Note for UserConfigFile. Sub LoadUserConfig LoadFile UserConfigFile End Sub 'Property UserConfigFile 'Returns a filespec 'Remark: Returns the filespec of a user-specific configuration file, related to the project but outside of the project folders, at %UserProfile%\.VBScripting. The file doesn't have to exist. Note: Care should be taken when privileges are elevated and the user is not a member of the Administrators group, because as privileges are elevated, %UserProfile% changes. Property Get UserConfigFile Dim file : file = "%UserProfile%\.VBScripting" UserConfigFile = Expand( file ) End Property Property Get Expand( str ) Expand = sh.ExpandEnvironmentStrings( str ) End Property 'For testing, allow setting a new process environment variable Property Let MockVar( newName, newValue ) sh.Environment( "process" )( newName ) = newValue End Property Property Get MockVar( name ) MockVar = sh.Environment( "process" )( name ) End Property 'Method LoadGlobalConfig 'Remarks: Loads the configuration file in the project folder. See comments for the GlobalConfigFile property. Equivalent to calling LoadFile GlobalConfigFile. Sub LoadGlobalConfig LoadFile GlobalConfigFile End Sub 'Property GlobalConfigFile 'Returns: a filespec 'Remark: Returns the filespec of the global configuration file. The word global refers to the project only. Depending on the location of the project, the configuration file may or may not be accessible to all users. The file does not have to exist. Expected value: <project folder>\.configure. Property Get GlobalConfigFile If Not IsEmpty( globalConfigFile_ ) Then GlobalConfigFile = globalConfigFile_ Exit Property End If globalConfigFile_ = format( Array( _ "%s\.configure", _ fso.GetParentFolderName( LibraryPath ) _ )) GlobalConfigFile = globalConfigFile_ End Property Property Let GlobalConfigFile( newValue ) globalConfigFile_ = newValue End Property Private globalConfigFile_ 'Property LibraryPath 'Returns: a path 'Remark: Gets or sets the location, i.e. the parent folder, of the class file and/or its associated configuration file. See the LoadClassConfig and LoadFile methods. Obscure. For an example, see the integration test Configurer.spec.wsf. Property Let LibraryPath( newPath ) includer.SetLibraryPath newPath End Property Property Get LibraryPath LibraryPath = includer.LibraryPath End Property 'Undocumented property. Returns the filespec of the previously loaded configuration file. Returns Empty if no file has been loaded. Property Get ConfigFile ConfigFile = currentConfigFile End Property 'Trim whitespace from left and right ends of each array elment Sub TrimElements( byRef arr ) Dim i For i = 0 To UBound( arr ) arr( i ) = Trim( arr( i )) Next End Sub 'Property ToArray 'Returns an array 'Parameter: a string 'Remarks: Converts a string to an array. Uses the delimiter set by the Delimiter property, a vertical bar ( | ) by default. Excess spaces on the left and right of each element are trimmed off. Property Get ToArray( str ) array_ = Split( str, Delimiter ) TrimElements array_ ToArray = array_ End Property Private array_ 'Property PowerShell 'Returns a string 'Remarks: Returns a string useful for starting a PowerShell process. If PowerShell 6 or 7 is installed, then the return value is the expanded filespec of the first "pwsh candidates" executable found that is listed in the file .configure in the project's root folder. If the cross-platform PowerShell is not found, returns the string powershell, which may be used to start a Windows PowerShell process. Since the return value may contain spaces, the string may need to be surrounded by quotes, depending on how it is used. For example, if the return value is used as the first argument of the Shell.Appliction object's ShellExecute method, then quotes are not recommended. But if the return value is used in the first argument of the WScript.Shell object's Run method, then quotes are recommended. Property Get PowerShell Dim candidate 'untested pwsh.exe filespec If Not IsEmpty( powershell_ ) Then PowerShell = powershell_ Exit Property End If LoadGlobalConfig For Each candidate In ToArray( Item( "pwsh candidates" )) If fso.FileExists( Expand( candidate )) Then powershell_ = Expand( candidate ) PowerShell = powershell_ Exit Property End If Next powershell_ = PsFallback PowerShell = powershell_ End Property Property Let PowerShell( newValue ) powershell_ = newValue End Property Private powershell_ 'Property WT 'Returns a string 'Remarks: Returns the filespec of a Windows Terminal executable, if installed and listed in .configure in the project folder. Returns Empty if Windows Terminal is not installed or not found. Property Get WT Dim candidate 'string If Not IsEmpty( wt_ ) Then WT = wt_ Exit Property End If LoadGlobalConfig For Each candidate In ToArray( Item( "wt candidates" )) If fso.FileExists( Expand( candidate )) Then wt_ = Expand( candidate ) WT = wt_ Exit Property End If Next End Property Property Let WT( newValue ) wt_ = newValue End Property Private wt_ 'Property Delimiter 'Returns a character 'Remarks: Gets or sets the delimiter used in converting strings to arrays. Default is a vertical bar ( | ). Property Let Delimiter( newValue ) delimiter_ = newValue End Property Property Get Delimiter Delimiter = delimiter_ End Property Private delimiter_ 'Property PsFallback 'Returns a string 'Remarks: Returns a ten-character string suitable for starting a Windows PowerShell process: powershell. This becomes the default PowerShell when the newer cross-platform PowerShell is not installed or not found. Property Get PsFallback PsFallback = "powershell" End Property 'Property Init 'Parameter: an object 'Returns an object self-reference 'Remarks: Initializes the Configurer object so that it can find the name of the calling script. The parameter is the WScript object, for .vbs or .wsf files, or the 'Document' object for .hta files. Required if the Configurer object was instantiated with the VBScripting.Includer object's experimental LoadObject method. Example:
 With CreateObject( "VBScripting.Includer" )
Set c = .LoadObject( "Configurer" ).Init( WScript )
End With
Function Init( obj ) Dim srcX 'temp string If "HTMLDocument" = TypeName( obj ) Then scrX = Mid( document.location.href, 9 ) scrX = Replace( scrX, "%20", " " ) scr = Replace( scrX, "/", "\" ) ElseIf "Object" = TypeName( obj ) Then scr = obj.ScriptFullName End If If fso.FileExists( ScriptConfigFile ) Then LoadScriptConfig End If Set Init = me End Function End Class