TBMANLIB_Diagnostic Package

Author: woeoio.

The TBMANLIB_Diagnostic contributed package makes a diagnostic report of a Windows computer and of the processes that run on it, and writes the report as text, JSON or HTML. The package has three parts. A DiagnosticEngine runs collectors, classes that each find one kind of information (the operating system, the processor and its load, the memory, the disks, the network, the running processes, the regional settings, the environment variables, the installed runtimes and the uptime), and each returns a DiagnosticResult, a list of pairs of text. A formatter turns the results into one string. FileHelper can save the string as a UTF-8 file. The package is written in twinBASIC and calls the Windows API directly. It uses no other library, and none of its collectors asks for administrator rights.

The package is the reusable part of woeoio’s entry for twinBASIC Monthly Challenge #1, Self-Contained Diagnostic Tool. The entry is a console program that shows the values on a live screen, or writes a report when it is started with an argument such as /json. That program is not in the package; FormatterFactory.CreateFormatterFromArg reads such an argument.

This section documents version 0.0.0.0, published on TWINSERV on 1 February 2026 with no licence. The package compiles in BETA 1005, as a 32-bit and as a 64-bit program. The repository woeoio/tbman, the toolkit of the same author whose package is TBMANLIB, does not hold the code of this package.

The package has defects: the text formatter and the Dictionary class cannot be used, and several members give a wrong result without an error. Known problems in version 0.0.0.0 lists them. The sample below uses the parts that work.

Dim engine As New DiagnosticEngine
engine.RegisterCollector New OSInfoCollector
engine.RegisterCollector New MemoryInfoCollector
engine.RegisterCollector New UptimeCollector

Dim formatter As IOutputFormatter
Set formatter = FormatterFactory.CreateFormatter("json")

Dim report As String
report = FormatterFactory.FormatResults(engine.RunAll(), formatter)

Dim path As String
path = Environ$("TEMP") & "\diag-report" & formatter.FileExtension
Debug.Print FileHelper.WriteFile(report, path)
Debug.Print FileHelper.GetFileSize(path) > 0
Debug.Print Left$(report, 1)
Debug.Print InStr(report, """memory"": {") > 0
FileHelper.DeleteFile path
' Output:
' True
' True
' {
' True

How a report is made

  1. Register collectors. A new DiagnosticEngine has none. A program registers an object of each collector class that it wants with RegisterCollector. The order of registration is the order of the report. The eleven collectors of the package are classes with names such as OSInfoCollector and MemoryInfoCollector, and a program can write its own, see IDiagnosticCollector.
  2. Run them. RunAll runs every collector and returns a list of results. RunCategory runs one by its name, and RunByRefreshMode runs those that change at one rate. The engine raises events and calls callbacks while it works, so a program can show the progress or cancel.
  3. Format. FormatterFactory.CreateFormatter makes a formatter from the name text, json or html, and FormatResults returns the report as a string. A formatter only builds the string. In version 0.0.0.0 the text formatter fails, so a report is made from json or html.
  4. Save. FileHelper.WriteFile writes the string, and the formatter’s FileExtension property gives the extension for the file name.

A result can also be read directly, without a formatter. Each item has a key and a value, both text: the key is the label of the line, such as Total Physical, and the value is its text, such as 15.9 GB.

Summary reports

RunAll takes an argument that asks for the short form. Four collectors have one: EnvironmentCollector, RuntimesCollector, ProcessInfoCollector and NetworkInfoCollector. For the process list it gives the counts instead of every process, and for the environment only three variables. The other seven collectors return their full data also in a summary run.

What a report contains

Note

A full report names the user and the computer, the folders in the environment variables such as USERPROFILE and PATH, every running program, the drives, and the names and IPv4 addresses of the network adapters. Read a report before it goes to someone else.

Failures

A collector never ends a run. An error that a collector raises, and a failure that it reports itself, give a result with Success set to False and a message, and the run goes on; see A collector that raises an error. A formatter prints such a result as one line with the key Error. The functions of FileHelper that write and read return a text that describes the failure and do not raise an error, and List, StringBuilder and RegisterCollector raise run-time errors, which their pages list.

The Windows functions and the registry

The package declares the Windows functions it calls, in a module of its own, and uses no component that has to be installed. Its references are the OLE Automation library and the App object that every twinBASIC project has. These are the libraries and the functions the collectors and the helpers call:

Library Functions
kernel32.dll GetNativeSystemInfo, GetComputerNameW, GlobalMemoryStatusEx, GetSystemTimes, GetTickCount64, GetLogicalDrives, GetDriveTypeW, GetDiskFreeSpaceExW, CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, CloseHandle, GetCurrentProcessId, GetTimeZoneInformation, GetACP, GetOEMCP, GetUserDefaultLCID, GetLocaleInfoW, RtlMoveMemory
ntdll.dll RtlGetVersion
advapi32.dll GetUserNameW, RegOpenKeyExW, RegQueryValueExW, RegCloseKey
shell32.dll IsUserAnAdmin
iphlpapi.dll GetAdaptersInfo
ws2_32.dll WSAStartup, gethostname, WSACleanup

The registry is read, never written, with the access KEY_READ. The keys are named on the page of the collector that reads them: CPUInfoCollector and RuntimesCollector.

Using the package with the Scripting Runtime

The package declares a generic class named Dictionary, and the Scripting Runtime (scrrun.dll) has a class of that name. The package’s class is written with its two type arguments, as in Dictionary(Of String, Long), and the Scripting Runtime’s has none. The name with type arguments is the package’s class in a project that references both libraries, whichever is listed first. A name without type arguments depends on the order of the references:

Declaration Scripting Runtime listed first Package listed first
Dim a As New Dictionary the class of the Scripting Runtime error TB5136, Unable to bind to generic class ‘Dictionary’; no generic specifiers given
Dim b As Dictionary(Of String, Long) the class of the package the class of the package
Dim c As New Scripting.Dictionary the class of the Scripting Runtime the class of the Scripting Runtime
Dim d As New TBMANLIB_Diagnostic.Dictionary(Of String, Long) the class of the package the class of the package

A project that wants the Scripting Runtime’s class writes Scripting.Dictionary, which does not depend on the order. The package’s class cannot be created in version 0.0.0.0 (see Dictionary), so a project has no reason to write its name. A project with only the package, which has no Scripting library, shows the first row of the table:

Dim a As New Dictionary
Dim b As Dictionary(Of String, Long)
Dim d As TBMANLIB_Diagnostic.Dictionary(Of String, Long)

The modules of the package are public, so a project that references it can use their names without the name of the module: GetTickCount64, FILETIME and KEY_READ of the WinAPI modules are visible, and so is FileTimeToLongLong. Where a name is declared in the project as well, such as its own Declare of CloseHandle, its own Type FILETIME or its own Const KEY_READ, written Private or Public, the project compiles and uses its own declaration. A call of FormatDateTime without the module name reaches the function of the runtime library, not the one of FormatHelper. Write the name of the module before a name of the package that another declaration has too, as in WinAPIStructures.KEY_READ and FormatHelper.FormatDateTime.

Types that have no page

The package has four modules whose contents a program has no need for:

  • WinAPI declares the Windows functions that the package calls, eight console and registry functions that it does not call (GetStdHandle, SetConsoleCursorPosition, SetConsoleTextAttribute, GetConsoleScreenBufferInfo, SetConsoleCtrlHandler, FillConsoleOutputCharacterW, FillConsoleOutputAttribute and RegEnumKeyExW), and four helper procedures: MakeCOORD, FileTimeToLongLong, GetStringFromBuffer and GetStringFromByteBuffer. The WinAPI helpers describes the two that matter to the results.
  • WinAPIStructures declares the record types and the constants of those functions: OSVERSIONINFOEXW, SYSTEM_INFO, MEMORYSTATUSEX, FILETIME, PROCESSENTRY32W, IP_ADDR_STRING, IP_ADAPTER_INFO, TIME_ZONE_INFORMATION, SYSTEMTIME, COORD, SMALL_RECT and CONSOLE_SCREEN_BUFFER_INFO.
  • DelegatesModule declares the four delegate types. Three are the types of the callbacks of the engine, see Delegates, and the fourth is FormatterFactoryDelegate.
  • RefreshModeModule holds only the enumeration RefreshMode.

Known problems in version 0.0.0.0

Note

Version 0.0.0.0 has these other problems. Each raises an error.

  • A Dictionary cannot be created. New Dictionary(Of TKey, TValue) raises run-time error 91, Object variable or With block variable not set.
  • The TextFormatter cannot make a report. Its BeginFormat raises error 5, Invalid procedure call or argument, because it builds the box with Chr$ and code points above 255. FormatterFactory.CreateFormatter gives this formatter for text, for txt and for every name it does not know. The names json and html work.
  • List.Contains, IndexOf and Remove raise error 438, Object doesn’t support this property or method, for a list of objects of a class that has no default member.
  • List.Insert with the index 0 raises error 5 on an empty list.
  • WriteFile and AppendFile make only the last folder of a path. For a path whose parent folder is missing they return the error text Error -2147467259: Unspecified error and the path. The Open statement raises error 76, Path not found, for it in VB6.

Two behaviours follow from the code and can look like defects. CPULoadCollector reports 0 for its first reading, because it measures between two calls. JsonFormatter writes every value as a string and turns PATH into pATH.

Pages

Engine and results

Collectors

  • IDiagnosticCollector – the contract of a collector
  • Collectors – the eleven collectors of the package, what each reports and which Windows functions it calls

Formatters

Helpers

  • FormatHelper – formats sizes, durations, percentages, dates and text, and escapes text for JSON and HTML
  • FileHelper – writes, appends to and reads text files, tests for files and folders, and takes paths apart
  • List – a list with a 0-based index
  • Dictionary – values found by a key; it cannot be created in version 0.0.0.0
  • StringBuilder – builds a long string from parts

See Also