9. Debugging the Extension

Applies to TestComplete 14.70, last modified on April 22, 2021

After creating the script code and the description.xml file, we can test whether our custom object functions properly. To test the extension, we need to change the TestComplete settings to specify the extension folder and call some methods from our scripts. Below is the steps that demonstrate how to do this.

One note before we proceed: the names of the extension and object we created in this tutorial (WMI Object and WMI) coincides with the name of the extension and objects that are installed in TestComplete. Before installing our extension, disable the TestComplete extension:

  1. Select File | Install Script Extensions from the main menu to invoke the Script Extensions dialog.

  2. In the dialog, uncheck the WMI Object extension.

  3. Press OK to close the dialog and save the changes.

Now we can install our script extension into TestComplete:

  1. Select the Tools | Options from the TestComplete main menu. This will invoke the Options dialog.

  2. In the dialog, select the Engines | Script Extensions node from the tree on the left of the dialog. This will open the Script Extensions Options page. Here you can specify the folders that contain script extensions.

  3. In the dialog, click Add Folder. This will invoke the standard Browse for Folder dialog.

  4. In the dialog, choose the C:\My Extension Files folder and click OK. The folder will be added to the script extensions list:

    Script Extensions Options Dialog
    We would like to note that we specified C:\My Extension Files, rather than C:\My Extension Files\Extension1. So far our script extension files are not packed to the .tcx package. When specifying the path to the unpacked extensions, you should not specify the folder that contains the description.xml file, but the folder that is one level up. This happens because TestComplete treats each item of the Script Extensions Folders list like the folder that may contain several extensions, but not just one extension. So, unpacked files cannot reside in the root of these folders, they must be in subfolders. That is why we specified C:\My Extension Files, but not C:\My Extension Files\Extension1. If we had the packed extension (the .tcx file) in the Extension1 folder, we would have specified C:\My Extension Files\Extension1.
  5. Make sure that the check box of the C:\My Extension Files item is selected in the list.

  6. Click OK to save the changes and to close the Options dialog.

After you added the our extension’s path to the Script Extension Options dialog and saved the changes, TestComplete scans the folder and automatically installs the extension. To check whether the extension is installed successfully:

  • Open the Script Extensions dialog (select File | Install Script Extension from the main menu). The extension should be listed in the dialog:

    Extension name in the Script Extensions Dialog
  • The object should appear in the Code Completion window. Open any script unit for editing and check this:

    Object in the Code Completion Window

To test whether our WMI object functions properly, create script code that call the object’s methods and properties. Below is sample code that calls some of the object’s methods:

JavaScript, JScript

function Test()
{
  WMI.PostProcessorInfo();
  Log.Message("Processor architecture: " + WMI.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"));

  WMI.PostDrivesInfo();
  WMI.PostInstalledAppsInfo();

  WMI.CreateProcess("notepad");
}

Python

def Test():
  WMI.PostProcessorInfo()
  Log.Message("Processor architecture: " + WMI.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE"))

  WMI.PostDrivesInfo()
  WMI.PostInstalledAppsInfo()

  WMI.CreateProcess("notepad")

VBScript

Sub Test
  WMI.PostProcessorInfo
  Log.Message "Processor architecture: " & WMI.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE")

  WMI.PostDrivesInfo
  WMI.PostInstalledAppsInfo

  WMI.CreateProcess "notepad"
End Sub

DelphiScript

procedure Test;
begin
  WMI.PostProcessorInfo;
  Log.Message('Processor architecture: ' + WMI.GetEnvironmentVariable('PROCESSOR_ARCHITECTURE'));

  WMI.PostDrivesInfo;
  WMI.PostInstalledAppsInfo;

  WMI.CreateProcess('notepad');
end;

C++Script, C#Script

function Test()
{
  WMI["PostProcessorInfo"]();
  Log["Message"]("Processor architecture: " + WMI["GetEnvironmentVariable"]("PROCESSOR_ARCHITECTURE"));

  WMI["PostDrivesInfo"]();
  WMI["PostInstalledAppsInfo"]();

  WMI["CreateProcess"]("notepad");
}

If there are any errors, make sure the contents of the description.xml and wmiCode.vbs files are correct and that the script file has the ANSI or UTF-8 character encoding. The full contents of these files is provided below:

description.xml

[description.xml]

<?xml version="1.0" encoding="UTF-8"?>
<ScriptExtensionGroup>

  <Category Name="Runtime Objects">

    <ScriptExtension Name="WMI Object" Author="SmartBear Software" Version="1.1 test" HomePage="smartbear.com">
      <Script Name="wmiCode.vbs" InitRoutine="Initialize">
        <RuntimeObject Name="WMI">
          <Method Name="ConnectToComputer" Routine="ConnectToComputer">
            Connects to WMI on the specified computer using the specified user name and password.
          </Method>
          <Method Name="WaitForComputer" Routine="WaitForComputer">
            Waits until the WMI connection to the specified computer is established during a timeout period.
          </Method>
          <Method Name="RestartComputer" Routine="RestartComputer">
            Restarts the computer.
          </Method>
          <Method Name="CreateProcess" Routine="CreateProcess">
            Launches the specified process.
          </Method>
          <Method Name="ProcessExists" Routine="ProcessExists">
            Checks if the specified process is running.
          </Method>
          <Method Name="WaitForProcessExit" Routine="WaitForProcessExit">
            Waits for a process to end during a timeout period.
          </Method>
          <Method Name="GetServiceState" Routine="GetServiceState">
            Returns the current state of the specified service.
          </Method>
          <Method Name="WaitForServiceState" Routine="WaitForServiceState">
            Waits for a service state to change during a timeout period.
          </Method>
          <Method Name="GetEnvironmentVariable" Routine="GetEnvironmentVariable">
            Returns the value of the specified environment variable.
          </Method>
          <Method Name="PostEnvironmentVariableInfo" Routine="PostEnvironmentVariableInfo">
            Logs information about environment variables.
          </Method>
          <Method Name="PostProcessorInfo" Routine="PostProcessorInfo">
            Logs information about the processor(s) installed on the computer.
          </Method>
          <Method Name="PostDrivesInfo" Routine="PostDrivesInfo">
            Logs information about disk drives available on the computer.
          </Method>
          <Method Name="PostInstalledAppsInfo" Routine="PostInstalledAppsInfo">
            Logs information about all the applications installed on the computer.
          </Method>
          <Method Name="PostEventsInfo" Routine="PostEventsInfo">
            Posts information from the system event log to the test log.
          </Method>
          <Property Name="ComputerName" GetRoutine="GetComputerName" SetRoutine="SetComputerName">
            The name of the computer that the WMI object works with. A dot (".") stands for the local computer.
          </Property>
          <Property Name="Service" GetRoutine="GetWMIService">
            Returns the WMI service object.
          </Property>
          <Property Name="MaxEventCount" GetRoutine="GetMaxEventCount" SetRoutine="SetMaxEventCount">
            Specifies the maximum number of entries to be retrieved from the system event log.
          </Property>
          <Description>
            Provides access to the WMI service.
          </Description>
        </RuntimeObject>
      </Script>
      <Description>
        Provides scripting interface to WMI objects.
      </Description>
    </ScriptExtension>

  </Category>

</ScriptExtensionGroup>

wmiCode.vbs

' ============================== Global Variables ==============================
Dim WMIService
Dim ComputerName
Dim MaxEventCount

' ========== SWbemLocator.ConnectServer() Default Constant Definitions =========
Const CLocalComputer = "."
Const CDefaultNamespace = "Root\CIMv2"
Const CDefaultLocale = Null
Const CDefaultAuthority = Null
Const CZeroConnectionTimeout = &H0 ' = 0
Const ConnectFlag_UseMaxWait = &H80 ' = 2 Min

' ========================= Common Constant Definitions ========================
Const Flags_RtnImmedFwdOnly = &H30 ' The iFlags value for the SWbemServices.ExecQuery method
Const CPollInterval         = 0.1  ' = 100 ms; Polling interval for event queries
Const CDefaultEventCount    = 50   ' The default number of the latest system events to be logged by PostEventsInfo

' ===================== User Messages Constant Definitions =====================
Const UMSG_ComputerConnected            = "A connection to the ""%s"" computer has been established successfully."
Const UMSG_UnableToConnect              = "Unable to connect to the ""%s"" computer."
Const UMSG_ConnectionTimeout            = "Failed to connect to the ""%s"" computer during the timeout period."
Const USMG_WaitingForConnection         = "Connecting to the ""%s"" computer..."
Const UMSG_ProcessWasCreated            = "The ""%s"" process has been created successfully."
Const UMSG_UnableToCreateProcess        = "Unable to create the ""%s"" process."
Const UMSG_ErrorWhileCreatingProcess    = "An error occurred while creating the ""%s"" process. The error code is %u."
Const UMSG_ProcessNotFound              = "The ""%s"" process is not running."
Const UMSG_ProcessExited                = "The ""%s"" process has closed."
Const UMSG_ProcessHasntExited           = "The ""%s"" process has not closed during the timeout period."
Const UMSG_WaitingForProcessExit        = "Waiting for the ""%s"" process to close..."
Const UMSG_ServiceNotFound              = "The ""%s"" service is not registered."
Const UMSG_ServiceStateChanged          = "The state of the ""%s"" service has changed to ""%s""."
Const UMSG_ServiceStateTimeout          = "The state of the ""%s"" service has not changed to ""%s"" during the timeout period."
Const UMSG_WaitingForServiceStateChange = "Waiting until the state of the ""%s"" service changes..."

' ========================== WMI Constant Definitions ==========================
' SWbemLocator.Security_.AuthenticationLevel Values
Const AuthenticationLevel_Default = 0

' SWbemLocator.Security_.ImpersonationLevel Values
Const ImpersonationLevel_Impersonate = 3

' SWbemLocator.Locator.Security_.Privileges Values
Const Privilege_ChangeNotify = "SeChangeNotifyPrivilege"
Const Privilege_RemoteShutdown = "SeRemoteShutdownPrivilege"
Const Privilege_Restore = "SeRestorePrivilege"
Const Privilege_Shutdown = "SeShutdownPrivilege"


' ================ The Script Extension's Initialization Routine ===============
Sub Initialize
  MaxEventCount = CDefaultEventCount
  ComputerName = CLocalComputer
  ConnectToComputerInternal Null, Null
End Sub


' ====================== WMI Object Methods and Properties =====================
Sub ConnectToComputer(Computer, User, Password)
  ComputerName = Computer

  ' Attempt to connect to WMI on the specified computer
  On Error Resume Next
  Indicator.PushText(aqString.Format(USMG_WaitingForConnection, GetComputerDisplayName))
  ConnectToComputerInternal User, Password
  Indicator.PopText

  ' Check if connection was successful
  If Err.Number = 0 Then
    Log.Message(aqString.Format(UMSG_ComputerConnected, GetComputerDisplayName))
  Else
    Log.Error(aqString.Format(UMSG_UnableToConnect, GetComputerDisplayName))
  End If
End Sub


Sub WaitForComputer(Computer, User, Password, Timeout)
  ComputerName = Computer

  ' Convert the timeout value from milliseconds to seconds (round to larger)
  Dim TimeoutSeconds
  TimeoutSeconds = Timeout \ 1000
  If Timeout Mod 1000 <> 0 Then
    TimeoutSeconds = TimeoutSeconds + 1
  End If

  ' Perform multiple attempts to connect to the specified computer during the timeout period
  On Error Resume Next
  Indicator.PushText(aqString.Format(USMG_WaitingForConnection, GetComputerDisplayName))
  Dim StartTime
  StartTime = Now
  Do
    Err.Clear
    ConnectToComputerInternal User, Password
  Loop While (Err.Number <> 0) And (DateDiff("s", StartTime, Now) < TimeoutSeconds)
  Indicator.PopText

  ' Check if connection succeeded
  If Err.Number = 0 Then
    Log.Message(aqString.Format(UMSG_ComputerConnected, GetComputerDisplayName))
  Else
    Log.Error(aqString.Format(UMSG_ConnectionTimeout, GetComputerDisplayName))
  End If
End Sub


Sub RestartComputer
  Dim OSColl, OSItem
  Set OSColl = WMIService.ExecQuery("SELECT * FROM Win32_OperatingSystem")

  For Each OSItem In OSColl
    OSItem.Reboot
  Next
End Sub


Function CreateProcess(CommandLine)
  Dim Process, InParams, OutParams
  Set Process = WMIService.Get("Win32_Process")

  ' Specify the command line to execute
  Set InParams = Process.Methods_("Create").InParameters.SpawnInstance_
  InParams.CommandLine = CommandLine

  ' Launch the process and get its ID
  Set OutParams = WMIService.ExecMethod("Win32_Process", "Create", InParams)
  CreateProcess = OutParams.ProcessId

  ' Check if the process was launched successfully
  If OutParams.ReturnValue = 0 Then
    Log.Message(aqString.Format(UMSG_ProcessWasCreated, CommandLine))
  ElseIf IsNull(OutParams.ReturnValue) Then
    Log.Error(aqString.Format(UMSG_UnableToCreate, CommandLine))
  Else
    Log.Error(aqString.Format(UMSG_ErrorWhileCreatingProcess, CommandLine, OutParams.ReturnValue))
  End If
End Function


Function ProcessExists(ProcessName)
  ProcessExists = False
  Dim ProcessColl, ProcessItem

  ' Search for a process with the specified name
  Set ProcessColl = WMIService.ExecQuery _
    ("SELECT * FROM Win32_Process WHERE Name='" & ProcessName & "'", , Flags_RtnImmedFwdOnly)

  ' Check if the found object is valid
  For Each ProcessItem In ProcessColl
    ProcessExists = Not IsNull(ProcessItem.ProcessId)
    Exit For
  Next
End Function


Sub WaitForProcessExit(ProcessName, Timeout)
  ' Verify that the specified process is running
  If Not ProcessExists(ProcessName) Then
    Log.Message(aqString.Format(UMSG_ProcessNotFound, ProcessName))
    Exit Sub
  End If

  Dim EventSource, EventItem

  ' Subscribe to the process termination event
  Indicator.PushText(aqString.Format(UMSG_WaitingForProcessExit, ProcessName))
  Set EventSource = oWMIService.ExecNotificationQuery _
    ("SELECT * FROM __InstanceDeletionEvent WITHIN " & CPollInterval _
    & " WHERE TargetInstance ISA 'Win32_Process'" _
    & " AND TargetInstance.Name='" & ProcessName & "'")

  ' Wait for the process to exit during the timeout period
  On Error Resume Next
  Set EventItem = EventSource.NextEvent(Timeout)
  Set EventSource = Nothing
  Indicator.PopText

  ' Check if the process has exited
  If IsEmpty(EventItem) Then
    Log.Error(aqString.Format(UMSG_ProcessHasntExited, ProcessName))
  Else
    Log.Message(aqString.Format(UMSG_ProcessExited, ProcessName))
  End If
End Sub


Function GetServiceState(ServiceName)
  Dim ServiceColl, ServiceItem

  ' Search for a service with the specified display name
  Set ServiceColl = WMIService.ExecQuery _
    ("SELECT * FROM Win32_Service WHERE DisplayName='" & ServiceName & "'", , Flags_RtnImmedFwdOnly)

  ' Get the state of the service
  For Each ServiceItem In ServiceColl
    GetServiceState = ServiceItem.State
    Exit For
  Next
End Function


Sub WaitForServiceState(ServiceName, State, Timeout)
  ' Verify that the specified service is registered
  If IsEmpty(GetServiceState(ServiceName)) Then
    Log.Error(aqString.Format(UMSG_ServiceNotFound, ServiceName))
    Exit Sub
  End If

  Dim EventSource, EventItem

  ' Subscribe to the service state change event
  Indicator.PushText(aqString.Format(UMSG_WaitingForServiceStateChange, ServiceName))
  Set Events = WMIService.ExecNotificationQuery _
    ("SELECT * FROM __InstanceModificationEvent WITHIN " & CPollInterval _
    & " WHERE TargetInstance ISA 'Win32_Service'" _
    & " AND TargetInstance.DisplayName='" & ServiceName & "'" _
    & " AND TargetInstance.State='" & State & "'")

  ' Wait for the service state to change during the timeout period
  On Error Resume Next
  Set EventItem = Events.NextEvent(Timeout)
  Set Events = Nothing
  Indicator.PopText

  ' Check if the service state has changed
  If IsEmpty(EventItem) Then
    Log.Error(aqString.Format(UMSG_ServiceStateTimeout, ServiceName, State))
  Else
    Log.Message(aqString.Format(UMSG_ServiceStateChanged, ServiceName, CurrentState))
  End If
End Sub


' The get method for the WMI.ComputerName property
Function GetComputerName
  GetComputerName = ComputerName
End Function

' The set method for the WMI.ComputerName property
Sub SetComputerName(Name)
  ComputerName = Name
  ConnectToComputerInternal Null, Null
End Sub


' The get method for the WMI.MaxEventCount property
Function GetMaxEventCount
  GetMaxEventCount = MaxEventCount
End Function

' The set method for the WMI.MaxEventCount property
Sub SetMaxEventCount(Count)
  MaxEventCount = Count
End Sub


' The get method for the WMI.Service property
Function GetWMIService
  Set GetWMIService = WMIService
End Function


Function GetEnvironmentVariable(Name)
  Dim EnvVarColl, EnvVarItem

  ' Search for an environment variable with the specified name
  Set EnvVarColl = WMIService.ExecQuery _
    ("SELECT * FROM Win32_Environment WHERE Name='" & Name & "'", , Flags_RtnImmedFwdOnly)

  ' Get the variable's value
  For Each EnvVarItem In EnvVarColl
    GetEnvironmentVariable = EnvVarItem.VariableValue
    Exit For
  Next
End Function


Sub PostEnvironmentVariableInfo
  Dim EnvVarColl, EnvVarItem
  Log.AppendFolder("Environment Variables")

  ' Obtain all environment variables
  Set EnvVarColl = WMIService.ExecQuery("SELECT * FROM Win32_Environment", , Flags_RtnImmedFwdOnly)

  ' Log the variables' properties
  For Each EnvVarItem In EnvVarColl
    Log.AppendFolder EnvVarItem.Name
    Log.Message "Value: " & EnvVarItem.VariableValue
    Log.Message "User name: " & EnvVarItem.UserName
    Log.Message "Description: " & EnvVarItem.Description
    Log.Message "System variable: " & EnvVarItem.SystemVariable
    Log.PopLogFolder
  Next
  Log.PopLogFolder
End Sub


Sub PostProcessorInfo
  Dim ObjectsList, CurrentObject, ID
  Log.AppendFolder "Processors"

  ' Obtain all the processors
  Set ObjectsList = WMIService.ExecQuery("SELECT * FROM Win32_Processor", , Flags_RtnImmedFwdOnly)

  ' Log the processors' properties
  ID = 1
  For Each CurrentObject In ObjectsList
    Log.AppendFolder "Processor #" & ID
    Log.Message "Manufacturer: " & CurrentObject.Manufacturer
    Log.Message "Description: " & CurrentObject.Description
    Log.Message "Frequency: " & CurrentObject.CurrentClockSpeed
    Log.PopLogFolder
    ID = ID + 1
  Next
  Log.PopLogFolder
End Sub


Sub PostDrivesInfo
  Dim ObjectsList, CurrentObject
  Log.AppendFolder "Logical Disks"
  
  ' Obtain all the fixed hard drives
  Set ObjectsList = WMIService.ExecQuery("SELECT * FROM Win32_LogicalDisk WHERE MediaType=12", , Flags_RtnImmedFwdOnly)

  ' Log the hard drives' properties
  For Each CurrentObject In ObjectsList
    Log.AppendFolder CurrentObject.Name
    Log.Message "Label: " & CurrentObject.VolumeName
    Log.Message "Size (MB): " & CurrentObject.Size / 1024^2
    Log.Message "Free space (MB): " & CurrentObject.FreeSpace / 1024^2
    Log.PopLogFolder
  Next
  Log.PopLogFolder
End Sub


Sub PostInstalledAppsInfo
  Dim ObjectsList, CurrentObject
  Log.AppendFolder "Installed Applications"

  ' Obtain all the installed products
  Set ObjectsList = WMIService.ExecQuery("SELECT * FROM Win32_Product", , Flags_RtnImmedFwdOnly)

  ' Log the products' properties
  For Each CurrentObject In ObjectsList
    Log.AppendFolder CurrentObject.Caption
    Log.Message "Vendor: " & CurrentObject.Vendor
    Log.Message "Version: " & CurrentObject.Version
    Log.Message "Description: " & CurrentObject.Description
    Log.Message "Location: " & CurrentObject.InstallLocation
    Log.PopLogFolder
  Next
  Log.PopLogFolder
End Sub


Sub PostEventsInfo
  Dim ObjectsList, CurrentObject, Count
  Log.AppendFolder("System Events")

  ' Retrieve entries from the system event log
  Set ObjectsList = WMIService.ExecQuery("SELECT * FROM Win32_NTLogEvent WHERE LogFile='System'")

  ' Log the latest MaxEventCount events
  Count = 1
  For Each CurrentObject in ObjectsList
    Log.AppendFolder(CurrentObject.SourceName)
    Log.Message "User: " & CurrentObject.User
    Log.Message "Category: " & CurrentObject.CategoryString
    Select Case CurrentObject.EventType
      Case 1
        Log.Error "Message", CurrentObject.Message
      Case 2
        Log.Warning "Message", CurrentObject.Message
      Case 3
        Log.Message "Message", CurrentObject.Message
    End Select
    Log.PopLogFolder

    Count = Count + 1
    If Count > MaxEventCount Then
      Exit For
    End If
  Next
  Log.PopLogFolder
End Sub


' ============================== Internal Routines =============================
' Connects to WMI on a computer using the specified credentials and security settings
Sub ConnectToComputerInternal(User, Password)
  ' Specify the local computer's name by default
  If IsNull(ComputerName) Then
    ComputerName = CLocalComputer
  End If

  ' Specify security settings for the WMI connection
  Dim Locator
  Set Locator = CreateObject("WbemScripting.SWbemLocator")
  Locator.Security_.AuthenticationLevel = AuthenticationLevel_Default ' Use default server-side settings
  Locator.Security_.ImpersonationLevel = ImpersonationLevel_Impersonate ' Use the client's security credentials
  Locator.Security_.Privileges.AddAsString(Privilege_ChangeNotify)
  Locator.Security_.Privileges.AddAsString(Privilege_Shutdown)
  Locator.Security_.Privileges.AddAsString(Privilege_RemoteShutdown)
  Locator.Security_.Privileges.AddAsString(Privilege_Restore)

  ' Connect to WMI on the specified computer
  Set WMIService = Nothing
  Set WMIService = Locator.ConnectServer _
    (ComputerName, CDefaultNamespace, User, Password, CDefaultLocale, CDefaultAuthority, CZeroConnectionTimeout)
  Set Locator = Nothing
End Sub

' Returns the display name of the computer to be used in indicator and log messages
Function GetComputerDisplayName
  If ComputerName = CLocalComputer Then
    GetComputerDisplayName = Sys.HostName
  Else
    GetComputerDisplayName = ComputerName
  End If
End Function

Prev     Next

See Also

Debugging Script Extensions
Installing and Uninstalling Script Extensions

Highlight search results