Services class

The predeclared singleton coordinator for the WinServicesLib package. Every interaction with the package starts with Services: the class is tagged [PredeclaredId], so a project-wide instance named Services exists at start-up and the consumer calls Services.X directly without New. The instance also doubles as an enumerable collection of the ServiceManager instances that have been configured (For Each manager In Services).

' Configure two services at start-up:
With Services.ConfigureNew
    .Name             = "MyServiceA"
    .InstanceCreator  = New ServiceCreator(Of MyServiceA)
End With
With Services.ConfigureNew
    .Name             = "MyServiceB"
    .InstanceCreator  = New ServiceCreator(Of MyServiceB)
End With

' Run the dispatcher if launched as a service host:
If InStr(Command, "-startService") > 0 Then Services.RunServiceDispatcher

See the package overview for the broader lifecycle, the two-thread split, and the elevation rules around installation.

Methods

ConfigureNew

Allocates a fresh ServiceManager, adds it to the internal collection, and returns it for the caller to populate. Typically used during Sub Main to declare every service the EXE knows how to host.

Syntax: Services.ConfigureNew As ServiceManager

With Services.ConfigureNew
    .Name             = "MyService"
    .Description      = "An example twinBASIC service"
    .Type             = tbServiceTypeOwnProcess
    .InstallStartMode = tbServiceStartOnDemand
    .InstallCmdLine   = """" & App.ModulePath & """ -startService"
    .InstanceCreator  = New ServiceCreator(Of MyService)
End With

Configuration is purely in-memory; ConfigureNew does not touch the SCM. The configured services are available via the For Each enumerator, GetConfiguredService, and the bulk Install / Uninstall / RunServiceDispatcher helpers.

ControlService

Sends an SCM control code to a running service.

Syntax: Services.ControlService ServiceName, ControlCode

ServiceName
required A String naming an installed service.
ControlCode
required A ServiceControlCodeConstants value — typically vbServiceControlStop, vbServiceControlPause, vbServiceControlContinue, or vbServiceControlInterrogate. User-defined codes in the range 128–255 are also accepted.

The method opens the SCM, requests the minimum required permission for the chosen control code (SERVICE_STOP, SERVICE_PAUSE_CONTINUE, SERVICE_INTERROGATE, or SERVICE_USER_DEFINED_CONTROL), opens the service, calls ControlServiceExW, and closes the handles. For vbServiceControlStop the reason code is filled with SERVICE_STOP_REASON_FLAG_PLANNED | SERVICE_STOP_REASON_MAJOR_NONE | SERVICE_STOP_REASON_MINOR_NONE (“planned stop, no specific reason”). Customising the reason code is not currently exposed.

Raises run-time error 5 with a descriptive message if the SCM cannot be opened (typically a permissions issue), the service is not installed, or ControlServiceExW fails.

GetConfiguredService

Looks up a previously-configured ServiceManager by its Name.

Syntax: Services.GetConfiguredService( Name ) As ServiceManager

Name
required A String matching the Name of one of the ServiceManager instances created with ConfigureNew.

Raises run-time error 5 “service not found” if no configured service carries that name. Typical use is in the interactive / install branch of Sub Main, where a UI button needs to act on a single configured service:

Private Sub btnInstallA_Click()
    If App.IsInIDE() Then Err.Raise 5, , "Run the compiled EXE as administrator."
    Services.GetConfiguredService("MyServiceA").Install
End Sub

Despite the Property Get syntax, the lookup is parameterised by name — it reads as a property in source code, but behaves like a function.

InstallAll

Iterates every ServiceManager created through ConfigureNew and calls its Install method. Convenience for the typical case where the EXE registers every service it hosts in one shot.

Syntax: Services.InstallAll

Important

InstallAll writes registry entries under HKEY_LOCAL_MACHINE and requires administrator rights. The usual pattern is to call it once from an elevated installer.

Per-service errors raised inside ServiceManager.Install propagate out of InstallAll and abort the bulk operation — there is no per-service On Error Resume Next wrapping. Services already installed before the failure remain installed.

LaunchService

Starts an installed service by name and optionally passes launch arguments through to its ServiceManager.LaunchArgs field.

Syntax: Services.LaunchService ServiceName [, LaunchArgs … ]

ServiceName
required A String naming an installed service.
LaunchArgs
optional A ParamArray of values forwarded to the service through StartServiceW. Each value is converted to a String; the service-side ITbService.EntryPoint reads them through ServiceManager.LaunchArgs.

The method opens the SCM with SC_MANAGER_CONNECT, opens the service with SERVICE_START, and calls StartServiceW. Raises run-time error 5 if the SCM cannot be opened, the service is not installed, the caller lacks the Start permission, or StartServiceW fails (typically because the service is already running).

The launch-args mechanism is commonly used to gate startup on a shared secret:

' UI side — starting the service with a password argument:
Services.LaunchService "MyService", "MySecretPassword"

' Service side — checking the argument inside EntryPoint:
Sub EntryPoint(ByVal ServiceManager As ServiceManager) _
        Implements ITbService.EntryPoint
    If Join(ServiceManager.LaunchArgs) <> "MySecretPassword" Then
        ServiceManager.ReportStatus vbServiceStatusStopped, &H12345678
        Exit Sub
    End If
    ' ...steady-state work
End Sub

This prevents accidental starts from the Services control-panel applet (which calls StartServiceW with no extra arguments).

QueryStateOfService

Returns a fresh ServiceState snapshot of an installed service.

Syntax: Services.QueryStateOfService( ServiceName ) As ServiceState

ServiceName
required A String naming an installed service.

Raises run-time error 5 if the service is not installed or the SCM cannot be opened. The returned ServiceState is a single-shot snapshot; to monitor a service’s state over time, call QueryStateOfService again at each sampling interval.

Private Sub timerRefresh_Timer()
    On Error Resume Next
    Dim state As ServiceState
    Set state = Services.QueryStateOfService("MyService")
    If Err.Number = 0 Then
        lblStatus.Caption = state.CurrentStateText _
                          & " (PID " & state.ProcessId & ")"
    Else
        lblStatus.Caption = "not installed"
    End If
End Sub

RunServiceDispatcher

Hands control of the main thread over to the SCM and runs the service dispatcher loop. Blocks until the SCM signals shutdown.

Syntax: Services.RunServiceDispatcher

Internally builds a SERVICE_TABLE_ENTRYW array from every configured ServiceManager and calls StartServiceCtrlDispatcherW. The SCM spawns a fresh thread for each service the user (or the Start configuration) wants to start, and invokes the package’s dispatcher trampoline on that thread; the trampoline reports StartPending, optionally initialises COM in STA mode (controlled by ServiceManager.AutoInitializeCOM), then calls the user’s ITbService.EntryPoint.

Raises run-time error 5 “Unable to start the service dispatcher” if StartServiceCtrlDispatcherW returns zero. The usual cause is that the EXE was launched normally rather than by the SCM — the dispatcher only works when the process is a service host. The conventional If InStr(Command, "-startService") > 0 Then gate in Sub Main avoids this error.

UninstallAll

Iterates every ServiceManager created through ConfigureNew and calls its Uninstall method.

Syntax: Services.UninstallAll

Important

UninstallAll writes registry entries under HKEY_LOCAL_MACHINE and requires administrator rights. Per-service errors abort the bulk operation; services already uninstalled before the failure remain uninstalled.

Enumerator

_NewEnum

Provides For Each support across every ServiceManager the project has configured.

Syntax: For Each manager In Services

Dim manager As ServiceManager
For Each manager In Services
    Debug.Print manager.Name, manager.Description
Next

The enumeration order is insertion order — services appear in the order they were created with ConfigureNew.

See Also