Repository navigation
Expand file tree
/
Copy pathLightCore.Win.System.pas
More file actions
279 lines (229 loc) · 10.8 KB
/
Copy pathLightCore.Win.System.pas
File metadata and controls
279 lines (229 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
UNIT LightCore.Win.System;
{$IFNDEF MSWINDOWS}
{$MESSAGE FATAL 'LightCore.Win.System is Windows-only. Its package LightCore.Win builds for Win32 and Win64 only.'}
{$ENDIF}
{=============================================================================================================
2026.09.30
www.GabrielMoraru.com
--------------------------------------------------------------------------------------------------------------
System-level Windows API utilities
Provides access to:
- Windows Services (start, stop, query status)
- The text of a Win32 error code
- The user name in the formats of the Windows function GetUserNameExW, for example GODZILLA\John Lennon
See also:
LightCore.System.pas - computer and user names, fonts, BIOS, display modes, print screen, mouse jiggle
LightVcl.Common.System.pas - the busy mouse cursor of a VCL application
Windows-only unit, package LightCore.Win. The four service routines drive the Windows Service Control Manager, GetWin32ErrorString asks the Windows function FormatMessage for the text of a Win32 error code, and GetUserNameEx calls GetUserNameExW in secur32.dll. None of these exists off Windows, so a build for Android, macOS or iOS stops at the top of this unit with a fatal compiler message.
=============================================================================================================}
INTERFACE
USES
Winapi.Windows, Winapi.WinSvc, System.SysUtils;
{==================================================================================================
SYSTEM SERVICES
==================================================================================================}
function ServiceStart (CONST aMachine, aServiceName: string): Boolean;
function ServiceStop (CONST aMachine, aServiceName: string): Boolean;
function ServiceGetStatus (CONST sMachine, sService: string): DWord;
function ServiceGetStatusName(CONST sMachine, sService: string): string;
{==================================================================================================
SYSTEM COMPUTER INFO
==================================================================================================}
function GetUserNameEx (ANameFormat: Cardinal): string; { source http://stackoverflow.com/questions/8446940/how-to-get-fully-qualified-domain-name-on-windows-in-delphi }
{==================================================================================================
SYSTEM API
==================================================================================================}
function GetWin32ErrorString(ErrorCode: DWORD): string;
IMPLEMENTATION
function GetWin32ErrorString(ErrorCode: DWORD): string;
var
Buffer: array[0..1023] of Char;
LangID: Word;
begin
if ErrorCode = ERROR_SUCCESS // ERROR_SUCCESS is 0
then Result := 'Operation completed successfully.'
else
begin
LangID := MakeLangID(LANG_NEUTRAL, SUBLANG_DEFAULT); // Default system language
if FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS, nil,
ErrorCode,
LangID,
Buffer,
Length(Buffer) - 1, // nSize is in TCHARs, not bytes! SizeOf(Buffer) would declare 2048 chars for a 1024-char buffer and let FormatMessage overrun the stack. -1 for null terminator space.
nil) = 0
then
Result := 'Windows Error Code ' + IntToStr(ErrorCode) + ' (No system description available).' // FormatMessage failed
else
begin
Result := Buffer;
Result := TrimRight(Result); // Remove trailing CRLF if present
end;
end;
end;
{--------------------------------------------------------------------------------------------------
GET COMPUTER INFO
--------------------------------------------------------------------------------------------------}
{ For 2, returns computer name + user name.
Ex: GODZILLA\John Lennon }
function GetUserNameEx(ANameFormat: Cardinal): string;
{See the constants defined in WinApi.Windows.pas EXTENDED_NAME_FORMAT enum.
NameUnknown = 0;
NameFullyQualifiedDN = 1;
NameSamCompatible = 2;
NameDisplay = 3;
NameUniqueId = 6;
NameCanonical = 7;
NameUserPrincipal = 8;
NameCanonicalEx = 9;
NameServicePrincipal = 10;
NameDnsDomain = 12;}
var
Buf: array[0..511] of WideChar; // Use WideChar for Unicode support.
BufSize: ULONG; // ULONG matches the parameter type.
Secur32: HMODULE;
GetUserNameEx: function(NameFormat: Cardinal; lpNameBuffer: LPWSTR; var nSize: ULONG): BOOL; stdcall;
begin
Result := '';
Secur32 := LoadLibrary('secur32.dll'); // Explicitly load the library.
if Secur32 = 0
then RAISE Exception.Create('Unable to load secur32.dll.');
try
@GetUserNameEx := GetProcAddress(Secur32, 'GetUserNameExW'); // Use Unicode version.
if not Assigned(GetUserNameEx) then
raise Exception.Create('GetUserNameExW function not found in secur32.dll.');
BufSize := Length(Buf);
if GetUserNameEx(ANameFormat, Buf, BufSize)
then Result := WideCharToString(Buf)
else RaiseLastOSError; // Raise an error if the function call fails.
finally
FreeLibrary(Secur32); // Ensure the library is freed.
end;
end;
{--------------------------------------------------------------------------------------------------
SERVICES
aMachine: UNC path (e.g., '\\ServerName') or empty string for local machine.
aServiceName: The short service name (not display name).
Source: BlackBox.pas
--------------------------------------------------------------------------------------------------}
function ServiceStart(CONST aMachine, aServiceName: string): boolean;
var
h_manager,h_svc: SC_Handle;
svc_status: TServiceStatus;
Temp: PChar;
dwCheckPoint: DWord;
begin
svc_status.dwCurrentState := SERVICE_STOPPED; { Initialize to known state }
h_manager := OpenSCManager(PChar(aMachine), nil,SC_MANAGER_CONNECT);
if h_manager > 0 then begin
h_svc := OpenService(h_manager, PChar(aServiceName),
SERVICE_START or SERVICE_QUERY_STATUS);
if h_svc > 0 then begin
temp := nil;
if (StartService(h_svc,0,temp)) then
begin
if (QueryServiceStatus(h_svc,svc_status)) then begin
{ Poll only while START_PENDING (MSDN pattern). The previous condition
'while SERVICE_RUNNING <> state' looped forever when the service failed
to start and fell back to STOPPED with a non-incrementing checkpoint
(0 < 0 never breaks) - an infinite Sleep(0) spin. }
while (SERVICE_START_PENDING = svc_status.dwCurrentState) do begin
dwCheckPoint := svc_status.dwCheckPoint;
Sleep(svc_status.dwWaitHint);
if (not QueryServiceStatus(h_svc,svc_status)) then break;
if (svc_status.dwCheckPoint < dwCheckPoint) then begin
// QueryServiceStatus didn't increment dwCheckPoint
break;
end;
end;
end;
end
else
QueryServiceStatus(h_svc, svc_status); { StartService failed (e.g. service already running) - read the real state so the Result check below is meaningful }
CloseServiceHandle(h_svc);
end;
CloseServiceHandle(h_manager);
end;
Result := (SERVICE_RUNNING = svc_status.dwCurrentState);
end;
{ Stops a Windows service and waits for it to reach STOPPED state.
Returns TRUE if service is stopped. }
function ServiceStop(CONST aMachine, aServiceName: string): boolean;
var h_manager,h_svc : SC_Handle;
svc_status : TServiceStatus;
dwCheckPoint : DWord;
begin
svc_status.dwCurrentState := SERVICE_RUNNING; { Initialize to known state ('not stopped'). Without this, every failure path below (manager/service cannot be opened) made the final Result check read an UNINITIALIZED stack record - random TRUE/FALSE. }
h_manager:=OpenSCManager(PChar(aMachine),nil,SC_MANAGER_CONNECT);
if h_manager > 0 then begin
h_svc := OpenService(h_manager,PChar(aServiceName), SERVICE_STOP or SERVICE_QUERY_STATUS);
if h_svc > 0 then
begin
if(ControlService(h_svc,SERVICE_CONTROL_STOP,svc_status)) then
begin
if(QueryServiceStatus(h_svc,svc_status))then
{ Poll only while STOP_PENDING (MSDN pattern). The previous condition
'while SERVICE_STOPPED <> state' looped forever when the service refused
to stop and stayed RUNNING with a non-incrementing checkpoint. }
while(SERVICE_STOP_PENDING = svc_status.dwCurrentState) do
begin
dwCheckPoint := svc_status.dwCheckPoint;
Sleep(svc_status.dwWaitHint);
if NOT QueryServiceStatus(h_svc,svc_status) then break; // couldn't check status
if (svc_status.dwCheckPoint < dwCheckPoint) then break;
end;
end
else
QueryServiceStatus(h_svc, svc_status); { ControlService failed (e.g. service already stopped) - read the real state so an already-stopped service correctly returns TRUE }
CloseServiceHandle(h_svc);
end;
CloseServiceHandle(h_manager);
end;
Result := (SERVICE_STOPPED = svc_status.dwCurrentState);
end;
// ================================
// Status Constants
// SERVICE_STOPPED
// SERVICE_RUNNING
// SERVICE_PAUSED
// SERVICE_START_PENDING
// SERVICE_STOP_PENDING
// SERVICE_CONTINUE_PENDING
// SERVICE_PAUSE_PENDING
// =================================
function ServiceGetStatus(CONST sMachine, sService: string): DWord; { From BlackBox.pas }
var h_manager,h_svc : SC_Handle;
service_status : TServiceStatus;
hStat : DWord;
begin
hStat := 0;
h_manager := OpenSCManager(PChar(sMachine) ,nil,SC_MANAGER_CONNECT);
if h_manager > 0 then begin
h_svc := OpenService(h_manager,PChar(sService),SERVICE_QUERY_STATUS);
if h_svc > 0 then begin
if(QueryServiceStatus(h_svc, service_status)) then
hStat := service_status.dwCurrentState;
CloseServiceHandle(h_svc);
end;
CloseServiceHandle(h_manager);
end;
Result := hStat;
end;
function ServiceGetStatusName(CONST sMachine, sService: string): string; { From BlackBox.pas }
var Cmd : string;
Status : DWord;
begin
Status := ServiceGetStatus(sMachine,sService);
case Status of
SERVICE_STOPPED : Cmd := 'STOPPED';
SERVICE_RUNNING : Cmd := 'RUNNING';
SERVICE_PAUSED : Cmd := 'PAUSED';
SERVICE_START_PENDING : Cmd := 'STARTING';
SERVICE_STOP_PENDING : Cmd := 'STOPPING';
SERVICE_CONTINUE_PENDING: Cmd := 'RESUMING';
SERVICE_PAUSE_PENDING : Cmd := 'PAUSING';
else
Cmd := 'UNKNOWN STATE';
end;
Result := Cmd;
end;
end.