Introduction
In this blog post, we will explore how to build a Service Manager CLI in Dart using the win32 package. By leveraging the Windows APIs provided by win32, we'll create a robust command-line tool that can enumerate services, start and stop services, and query service status.
Whether you're looking to enhance your development toolkit or simply learn more about integrating Dart with Windows system functionalities, this guide will provide you with the insights and steps necessary to build your own service manager from scratch.
Here's what we'll cover:
- Feature Overview
- Setting Up the Project
- Defining the Models
- Implementing Service Manager Logic
- Building the CLI
- Conclusion
- Source Code
Feature Overview
Our Service Manager CLI will include the following key features:
- Enumerating services: View a set of all available services on the system.
- Starting and stopping a service: Start or stop a service by its name.
- Querying service status: Retrieve the current operational status of a service by its name.
Setting Up the Project
Before we dive into coding, let’s set up our project.
Creating a New Dart Project
Open your terminal and run:
> dart create service_manager_cli
> cd service_manager_cli
Installing Dependencies
Add the ffi and win32 packages to your project with:
dart pub add ffi win32
Defining the Models
We'll start by defining the models responsible for storing service information and result details for start and stop operations.
Create a new file named models.dart
in the lib\src
directory and add the
following code:
import 'package:win32/win32.dart';
/// The result of an attempt to start a service.
enum ServiceStartResult {
/// The attempt to start the service was denied due to insufficient
/// permissions.
accessDenied,
/// The service is already running.
alreadyRunning,
/// The attempt to start the service failed for an unspecified reason.
failed,
/// The service was started successfully.
success,
/// The attempt to start the service timed out.
timedOut,
}
/// The various states a service can be in.
enum ServiceStatus {
/// The service is not running.
stopped,
/// The service is in the process of starting.
startPending,
/// The service is in the process of stopping.
stopPending,
/// The service is running.
running,
/// The service is in the process of resuming from a paused state.
continuePending,
/// The service is in the process of being paused.
pausePending,
/// The service is paused.
paused;
/// Converts an integer value to a corresponding [ServiceStatus] enum.
///
/// Throws an [ArgumentError] if the value does not correspond to a valid
/// value.
static ServiceStatus fromValue(int value) => switch (value) {
SERVICE_STOPPED => ServiceStatus.stopped,
SERVICE_START_PENDING => ServiceStatus.startPending,
SERVICE_STOP_PENDING => ServiceStatus.stopPending,
SERVICE_RUNNING => ServiceStatus.running,
SERVICE_CONTINUE_PENDING => ServiceStatus.continuePending,
SERVICE_PAUSE_PENDING => ServiceStatus.pausePending,
SERVICE_PAUSED => ServiceStatus.paused,
_ => throw ArgumentError('Invalid value: $value')
};
}
/// The result of an attempt to stop a service.
enum ServiceStopResult {
/// The attempt to stop the service was denied due to insufficient
/// permissions.
accessDenied,
/// The service is already stopped.
alreadyStopped,
/// The attempt to stop the service failed for an unspecified reason.
failed,
/// The service was stopped successfully.
success,
/// The attempt to stop the service timed out.
timedOut,
}
/// A Windows service with its name, display name, and current status.
class Service {
const Service({
required this.displayName,
required this.name,
required this.status,
});
/// The display name of the service.
final String displayName;
/// The name of the service.
final String name;
/// The current status of the service.
final ServiceStatus status;
String toString() =>
'Service(displayName: $displayName, name: $name, status: $status)';
}
Implementing Service Manager Logic
Next, we'll implement the core functionality for managing Windows services, including enumerating services, starting and stopping services, and querying service status.
Create a new file named service_manager.dart
in the lib\src
directory and
add the following code to set up the skeleton for managing Windows services:
import 'dart:collection';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
import 'models.dart';
/// Provides functionality for managing Windows services, including:
/// - Enumerating available services
/// - Starting and stopping services
/// - Retrieving the current status of services
abstract class ServiceManager {
/// Whether to log informative messages to the console.
static bool log = false;
/// Retrieves a set of all services (sorted by display name).
static Set<Service> get services {
// TODO: Implement this method
throw UnimplementedError();
}
/// Starts a service defined by [serviceName].
static ServiceStartResult start(String serviceName) {
// TODO: Implement this method
throw UnimplementedError();
}
/// Stops a service defined by [serviceName].
static ServiceStopResult stop(String serviceName) {
// TODO: Implement this method
throw UnimplementedError();
}
/// Retrieves the status of a service defined by [serviceName].
static ServiceStatus? status(String serviceName) {
// TODO: Implement this method
throw UnimplementedError();
}
/// Logs a message to the console if [log] is `true`.
static void _log(String message) {
if (log) print(message);
}
}
With the skeleton in place, we can start implementing the service manager logic.
Enumerating Services
Now, let's implement the services
getter to enumerate all services on the
system.
/// Retrieves a set of all services (sorted by display name).
static Set<Service> get services {
final services =
SplayTreeSet<Service>((a, b) => a.displayName.compareTo(b.displayName));
// Get a handle to the SCM database.
final scmHandle =
OpenSCManager(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE);
if (scmHandle == NULL) return services;
return using((arena) {
try {
final bytesNeeded = arena<DWORD>();
final servicesReturned = arena<DWORD>();
final resumeHandle = arena<DWORD>();
_log('Getting service list...');
// First call to EnumServicesStatusEx to get the required buffer size.
EnumServicesStatusEx(
scmHandle,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_STATE_ALL,
nullptr,
0,
bytesNeeded,
servicesReturned,
resumeHandle,
nullptr,
);
final buffer = arena<BYTE>(bytesNeeded.value);
// Second call to EnumServicesStatusEx to get the actual data.
if (EnumServicesStatusEx(
scmHandle,
SC_ENUM_PROCESS_INFO,
SERVICE_WIN32,
SERVICE_STATE_ALL,
buffer,
bytesNeeded.value,
bytesNeeded,
servicesReturned,
resumeHandle,
nullptr,
) !=
FALSE) {
final enumBuffer = buffer.cast<ENUM_SERVICE_STATUS_PROCESS>();
for (var i = 0; i < servicesReturned.value; i++) {
final serviceStatus = (enumBuffer + i).ref;
final ENUM_SERVICE_STATUS_PROCESS(:lpServiceName, :lpDisplayName) =
serviceStatus;
final serviceName = lpServiceName.toDartString();
final displayName = lpDisplayName.toDartString();
final status = ServiceStatus.fromValue(
serviceStatus.ServiceStatusProcess.dwCurrentState,
);
final service = Service(
displayName: displayName,
name: serviceName,
status: status,
);
services.add(service);
}
}
} finally {
CloseServiceHandle(scmHandle);
}
return services;
});
}
We first obtain a handle to the Service Control Manager (SCM) database using
OpenSCManager, which allows us to interact with the SCM to query, start, stop,
and configure services. We then make an initial call to EnumServicesStatusEx
to determine the required buffer size for storing the service information. With
the necessary buffer allocated, we make a second call to EnumServicesStatusEx
to retrieve the actual service data. Iterating through the services, we convert
them into Service
objects and add them to a sorted set.
Throughout this process, we log informative messages to track progress and errors. If we fail to open the SCM or enumerate services, we ensure appropriate error handling and logging.
Starting a Service
Next, we’ll implement the start
function to start a service with
serviceName
.
/// Starts a service defined by [serviceName].
static ServiceStartResult start(String serviceName) {
// Get a handle to the SCM database.
final scmHandle = OpenSCManager(
nullptr, // local computer
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS, // full access rights
);
if (scmHandle == NULL) return ServiceStartResult.accessDenied;
return using((arena) {
// Get a handle to the service.
final hService = OpenService(
scmHandle,
serviceName.toNativeUtf16(allocator: arena),
SERVICE_ALL_ACCESS,
);
if (hService == NULL) {
CloseServiceHandle(scmHandle);
return ServiceStartResult.failed;
}
final lpBuffer = arena<SERVICE_STATUS_PROCESS>();
final bytesNeeded = arena<DWORD>();
// Check the status in case the service is not stopped.
if (QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
lpBuffer.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
return ServiceStartResult.failed;
}
final ssp = lpBuffer.ref;
// Check if the service is already running. It would be possible to stop
// the service here, but for simplicity this example just returns.
if (ssp.dwCurrentState != SERVICE_STOPPED &&
ssp.dwCurrentState != SERVICE_STOP_PENDING) {
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
return ServiceStartResult.alreadyRunning;
}
// Save the tick count and initial checkpoint.
var startTickCount = GetTickCount();
var oldCheckPoint = ssp.dwCheckPoint;
// If a stop is pending, wait for it.
while (ssp.dwCurrentState == SERVICE_STOP_PENDING) {
_log('Service stop pending...');
// Do not wait longer than the wait hint. A good interval is one-tenth
// of the wait hint but not less than 1 second and not more than 10
// seconds.
var waitTime = ssp.dwWaitHint ~/ 10;
waitTime = waitTime < 1000
? 1000
: waitTime > 10000
? 10000
: waitTime;
_log('Sleeping for ${ssp.dwWaitHint} ms...');
Sleep(waitTime);
// Check the status until the service is no longer stop pending.
if (QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
lpBuffer.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
return ServiceStartResult.failed;
}
if (ssp.dwCheckPoint > oldCheckPoint) {
// Continue to wait and check.
startTickCount = GetTickCount();
oldCheckPoint = ssp.dwCheckPoint;
} else if (GetTickCount() - startTickCount > ssp.dwWaitHint) {
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
return ServiceStartResult.timedOut;
}
}
// Attempt to start the service.
if (StartService(hService, 0, nullptr) == FALSE) {
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
return ServiceStartResult.failed;
} else {
_log('Service start pending...');
}
// Check the status until the service is no longer start pending.
if (QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
lpBuffer.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
return ServiceStartResult.failed;
}
// Save the tick count and initial checkpoint.
startTickCount = GetTickCount();
oldCheckPoint = ssp.dwCheckPoint;
while (ssp.dwCurrentState == SERVICE_START_PENDING) {
// Do not wait longer than the wait hint. A good interval is one-tenth
// of the wait hint but not less than 1 second and not more than 10
// seconds.
var waitTime = ssp.dwWaitHint ~/ 10;
waitTime = waitTime < 1000
? 1000
: waitTime > 10000
? 10000
: waitTime;
_log('Sleeping for ${ssp.dwWaitHint} ms...');
Sleep(waitTime);
// Check the status again.
if (QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
lpBuffer.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
break;
}
if (ssp.dwCheckPoint > oldCheckPoint) {
// Continue to wait and check.
startTickCount = GetTickCount();
oldCheckPoint = ssp.dwCheckPoint;
} else if (GetTickCount() - startTickCount > ssp.dwWaitHint) {
// No progress made within the wait hint.
break;
}
}
// Determine whether the service is running.
final serviceRunning = ssp.dwCurrentState == SERVICE_RUNNING;
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
return serviceRunning
? ServiceStartResult.success
: ServiceStartResult.failed;
});
}
We begin by obtaining a handle to the Service Control Manager (SCM) database
using OpenSCManager
with full access rights. If this fails, we return an
access denied result. Next, we get a handle to the specific service using
OpenService. If this fails, we close the SCM handle and return a failure
result.
We then check the service's status using QueryServiceStatusEx. If the service is not stopped, we return an already running result. If the service is stopping, we wait for it to finish stopping, periodically checking its status and updating our wait time based on the service's wait hint.
Once the service is stopped, we attempt to start it using StartService. We then check the service's status again, waiting for it to finish starting. If the service starts successfully and transitions to the running state, we return a success result; otherwise, we return a failure result.
Throughout this process, we log informative messages and ensure proper resource management by closing all handles when done.
Stopping a Service
Next, we’ll implement the stop
function to stop a service with
serviceName
.
/// Stops a service defined by [serviceName].
static ServiceStopResult stop(String serviceName) {
// Get a handle to the SCM database.
final scmHandle = OpenSCManager(
nullptr, // local computer
nullptr, // ServicesActive database
SC_MANAGER_ALL_ACCESS, // full access rights
);
if (scmHandle == NULL) return ServiceStopResult.accessDenied;
return using((arena) {
// Get a handle to the service.
final hService = OpenService(
scmHandle,
serviceName.toNativeUtf16(allocator: arena),
SERVICE_STOP | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS,
);
if (hService == NULL) {
CloseServiceHandle(scmHandle);
return ServiceStopResult.failed;
}
try {
final lpBuffer = arena<SERVICE_STATUS_PROCESS>();
final bytesNeeded = arena<DWORD>();
// Make sure the service is not already stopped.
if (QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
lpBuffer.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
return ServiceStopResult.failed;
}
final ssp = lpBuffer.ref;
if (ssp.dwCurrentState == SERVICE_STOPPED) {
return ServiceStopResult.alreadyStopped;
}
final startTime = GetTickCount();
const timeout = 30000; // 30-second timeout
// If a stop is pending, wait for it.
while (ssp.dwCurrentState == SERVICE_STOP_PENDING) {
_log('Service stop pending...');
// Do not wait longer than the wait hint. A good interval is one-tenth
// of the wait hint but not less than 1 second and not more than 10
// seconds.
var waitTime = ssp.dwWaitHint ~/ 10;
waitTime = waitTime < 1000
? 1000
: waitTime > 10000
? 10000
: waitTime;
_log('Sleeping for ${ssp.dwWaitHint} ms...');
Sleep(waitTime);
if (QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
lpBuffer.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
return ServiceStopResult.failed;
}
if (ssp.dwCurrentState == SERVICE_STOPPED) {
return ServiceStopResult.success;
}
if (GetTickCount() - startTime > timeout) {
return ServiceStopResult.timedOut;
}
}
// If the service is running, dependencies must be stopped first.
final result = _stopDependentServices(hService, scmHandle);
if (result
case ServiceStopResult.accessDenied ||
ServiceStopResult.failed ||
ServiceStopResult.timedOut) {
return result;
}
// Send a stop code to the service.
if (ControlService(
hService,
SERVICE_CONTROL_STOP,
lpBuffer.cast<SERVICE_STATUS>(),
) ==
FALSE) {
return ServiceStopResult.failed;
}
// Wait for the service to stop.
_log('Service stop pending...');
while (ssp.dwCurrentState != SERVICE_STOPPED) {
_log('Sleeping for ${ssp.dwWaitHint} ms...');
Sleep(ssp.dwWaitHint);
if (QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
lpBuffer.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
return ServiceStopResult.failed;
}
if (ssp.dwCurrentState == SERVICE_STOPPED) break;
if (GetTickCount() - startTime > timeout) {
return ServiceStopResult.timedOut;
}
}
return ServiceStopResult.success;
} finally {
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
}
});
}
/// Stops dependent services of a service defined by [hService].
static ServiceStopResult _stopDependentServices(
int hService,
int scmHandle,
) {
return using((arena) {
final bytesNeeded = arena<DWORD>();
final servicesReturned = arena<DWORD>();
_log('Checking for dependent services...');
// Pass a zero-length buffer to get the required buffer size.
if (EnumDependentServices(
hService,
SERVICE_ACTIVE,
nullptr,
0,
bytesNeeded,
servicesReturned,
) ==
TRUE) {
_log('No dependent services found.');
} else {
// Allocate a buffer for the dependencies.
final lpServices =
arena<BYTE>(bytesNeeded.value).cast<ENUM_SERVICE_STATUS>();
// Enumerate the dependencies.
if (EnumDependentServices(
hService,
SERVICE_ACTIVE,
lpServices,
bytesNeeded.value,
bytesNeeded,
servicesReturned,
) ==
FALSE) {
return ServiceStopResult.failed;
}
_log('Found ${servicesReturned.value} dependent services:');
for (var i = 0; i < servicesReturned.value; i++) {
final ess = lpServices[i];
_log(' (${i + 1}/${servicesReturned.value}) Stopping '
'${ess.lpServiceName.toDartString()}...');
// Open the service.
final hDepService = OpenService(
scmHandle,
ess.lpServiceName,
SERVICE_STOP | SERVICE_QUERY_STATUS,
);
if (hDepService == NULL) return ServiceStopResult.failed;
try {
final lpServiceStatus = arena<SERVICE_STATUS_PROCESS>();
// Send a stop code.
if (ControlService(
hDepService,
SERVICE_CONTROL_STOP,
lpServiceStatus.cast<SERVICE_STATUS>(),
) ==
FALSE) {
return ServiceStopResult.failed;
}
final startTime = GetTickCount();
const timeout = 30000; // 30-second timeout
final ssp = lpServiceStatus.ref;
// Wait for the service to stop.
while (ssp.dwCurrentState != SERVICE_STOPPED) {
_log('Sleeping for ${ssp.dwWaitHint} ms...');
Sleep(ssp.dwWaitHint);
if (QueryServiceStatusEx(
hDepService,
SC_STATUS_PROCESS_INFO,
lpServiceStatus.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
return ServiceStopResult.failed;
}
if (ssp.dwCurrentState == SERVICE_STOPPED) break;
if (GetTickCount() - startTime > timeout) {
return ServiceStopResult.timedOut;
}
}
} finally {
// Always release the service handle.
CloseServiceHandle(hDepService);
}
}
}
_log('Dependent services stopped.');
return ServiceStopResult.success;
});
}
In the stop
function, we first obtain a handle to the Service Control Manager
(SCM) database using OpenSCManager
with full access rights. If this fails, we
return an access denied result. Next, we get a handle to the specific service
using OpenService
, granting it stop, query status, and enumerate dependents
permissions. If this fails, we close the SCM handle and return a failure
result.
We then check the service's status using QueryServiceStatusEx
. If the service
is already stopped, we return an already stopped result. If the service is
stopping, we wait for it to finish stopping, periodically checking its status
and updating our wait time based on the service's wait hint. Once the service is
no longer in the stop pending state, we attempt to stop any dependent services
first using the _stopDependentServices
helper function.
After ensuring dependent services are stopped, we send a stop code to the service using ControlService. We then wait for the service to transition to the stopped state, periodically checking its status. If the service stops successfully, we return a success result; otherwise, we return a failure or timed out result.
Throughout this process, we log informative messages and ensure proper resource
management by closing all handles when done. The _stopDependentServices
function enumerates and stops any active dependent services in a similar manner,
ensuring they are fully stopped before returning control to the main stop
function.
The implementations for start
and stop
functions are based on the C++
examples provided in the Microsoft documentation.
Querying Service Status
Finally, let's implement the status
function to query service status.
/// Retrieves the status of a service defined by [serviceName].
static ServiceStatus? status(String serviceName) {
// Get a handle to the SCM database.
final scmHandle = OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT);
if (scmHandle == NULL) return null;
return using((arena) {
// Get a handle to the service.
final hService = OpenService(
scmHandle,
serviceName.toNativeUtf16(allocator: arena),
SERVICE_QUERY_STATUS,
);
if (hService == NULL) {
CloseServiceHandle(scmHandle);
return null;
}
try {
final lpBuffer = arena<SERVICE_STATUS_PROCESS>();
final bytesNeeded = arena<DWORD>();
// Query the service status.
if (QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
lpBuffer.cast(),
sizeOf<SERVICE_STATUS_PROCESS>(),
bytesNeeded,
) ==
FALSE) {
return null;
}
return ServiceStatus.fromValue(lpBuffer.ref.dwCurrentState);
} finally {
CloseServiceHandle(hService);
CloseServiceHandle(scmHandle);
}
});
}
We first obtain a handle to the Service Control Manager (SCM) database using
OpenSCManager
with connect permissions. If this fails, we return null
. Next,
we get a handle to the specific service using OpenService
, granting it query
status permissions. If this fails, we close the SCM handle and return null
.
Using the QueryServiceStatusEx
function, we query the service's status. We
allocate the necessary buffer to store the status information. If the query is
unsuccessful, we return null
. If the query succeeds, we retrieve the
service's current state and convert it into a ServiceStatus
object using
ServiceStatus.fromValue
.
Throughout this process, we log informative messages and ensure proper resource
management by closing all handles when done. This ensures that the function
correctly retrieves the status of a specified service, returning the appropriate
status or null
if any step fails.
Building the CLI
Now that we have implemented the functions to interact with Windows services, let's create a CLI tool to manage services directly from the command line.
First, update the lib\service_manager_cli.dart file to export the models and service manager implementation:
library;
export 'src/models.dart';
export 'src/service_manager.dart';
Next, replace the existing code in bin\service_manager_cli.dart with the following implementation for the CLI:
import 'dart:io';
import 'package:service_manager_cli/service_manager_cli.dart';
void main(List<String> arguments) {
if (arguments.isEmpty ||
arguments.contains('-h') ||
arguments.contains('--help')) {
printUsage();
return;
}
bool verbose = false;
if (arguments.contains('-v') || arguments.contains('--verbose')) {
verbose = true;
arguments = arguments.where((arg) => arg != '-v').toList();
}
ServiceManager.log = verbose;
final command = arguments[0];
final serviceName = arguments.length > 1 ? arguments[1] : null;
switch (command) {
case 'list':
listServices();
break;
case 'start':
if (serviceName == null) {
print('Please provide the service name to start.');
exit(1);
}
startService(serviceName);
break;
case 'status':
if (serviceName == null) {
print('Please provide a service name to get status.');
exit(1);
}
status(serviceName);
break;
case 'stop':
if (serviceName == null) {
print('Please provide the service name to stop.');
exit(1);
}
stopService(serviceName);
break;
default:
print('Unknown command: $command');
print('');
printUsage();
exit(1);
}
}
void printUsage() {
print('A command-line interface for managing Windows services.');
print('');
print('Usage: service_manager_cli <command> [arguments]');
print('');
print('Global options:');
print(' -v, --verbose Show additional command output.');
print(' -h, --help Print this usage information.');
print('');
print('Available commands:');
print(' list List all services.');
print(' start <service_name> Start a service.');
print(' status <service_name> Get the status of a service.');
print(' stop <service_name> Stop a service.');
}
void listServices() {
final services = ServiceManager.services;
if (services.isEmpty) {
print('Failed to get services.');
return;
}
print('Found ${services.length} services:');
for (final service in services) {
print(' $service');
}
}
void startService(String serviceName) {
print(switch (ServiceManager.start(serviceName)) {
ServiceStartResult.success =>
'Service "$serviceName" started successfully.',
ServiceStartResult.accessDenied =>
'The attempt to start the service "$serviceName" was denied due to '
'insufficient permissions.',
ServiceStartResult.alreadyRunning =>
'Service "$serviceName" is already running.',
ServiceStartResult.failed => 'Failed to start service "$serviceName".',
ServiceStartResult.timedOut =>
'The attempt to start service "$serviceName" timed out.'
});
}
void status(String serviceName) {
final status = ServiceManager.status(serviceName);
if (status == null) {
print('Failed to get status of service "$serviceName".');
} else {
print('Status of service "$serviceName": ${status.name}');
}
}
void stopService(String serviceName) {
print(switch (ServiceManager.stop(serviceName)) {
ServiceStopResult.success => 'Service "$serviceName" stopped successfully.',
ServiceStopResult.accessDenied =>
'The attempt to stop the service "$serviceName" was denied due to '
'insufficient permissions.',
ServiceStopResult.alreadyStopped =>
'Service "$serviceName" is already stopped.',
ServiceStopResult.failed => 'Failed to stop service "$serviceName".',
ServiceStopResult.timedOut =>
'The attempt to stop service "$serviceName" timed out.'
});
}
Finally, update the pubspec.yaml file to include the executables
section
and specify the entry point for the CLI:
name: service_manager_cli
description: Service Manager CLI
publish_to: none
environment:
sdk: ^3.4.0
dependencies:
ffi: ^2.1.2
win32: ^5.5.1
dev_dependencies:
lints: ^4.0.0
executables:
service_manager_cli:
You now have a powerful CLI tool for efficiently managing Windows services. To use it, run the following command in your terminal:
dart run service_manager_cli
This command provides information about available commands, global options, and usage:
A command-line interface for managing Windows services.
Usage: service_manager_cli <command> [arguments]
Global options:
-v, --verbose Show additional command output.
-h, --help Print this usage information.
q
Available commands:
list List all services.
start <service_name> Start a service.
status <service_name> Get the status of a service.
stop <service_name> Stop a service.
Conclusion
In this blog post, we've explored how to build a command-line interface (CLI) in Dart using the win32 package to manage Windows services. From listing and controlling services to checking their status, we've covered essential tasks that streamline Windows service administration directly from your command line.
I hope you found this tutorial helpful! If you have any questions or feedback, please feel free to reach out. Happy coding! 🚀