2 * PROJECT: ReactOS Windows-Compatible Session Manager
3 * LICENSE: BSD 2-Clause License
4 * FILE: base/system/smss/smsubsys.c
5 * PURPOSE: Main SMSS Code
6 * PROGRAMMERS: Alex Ionescu
9 /* INCLUDES *******************************************************************/
16 /* GLOBALS ********************************************************************/
18 RTL_CRITICAL_SECTION SmpKnownSubSysLock
;
19 LIST_ENTRY SmpKnownSubSysHead
;
20 HANDLE SmpWindowsSubSysProcess
;
21 HANDLE SmpWindowsSubSysProcessId
;
22 BOOLEAN RegPosixSingleInstance
;
23 WCHAR InitialCommandBuffer
[256];
25 /* FUNCTIONS ******************************************************************/
29 SmpCallCsrCreateProcess(IN PSB_API_MSG SbApiMsg
,
30 IN USHORT MessageLength
,
35 /* Initialize the header and send the message to CSRSS */
36 SbApiMsg
->h
.u2
.ZeroInit
= 0;
37 SbApiMsg
->h
.u1
.s1
.DataLength
= MessageLength
+ 8;
38 SbApiMsg
->h
.u1
.s1
.TotalLength
= sizeof(SB_API_MSG
);
39 SbApiMsg
->ApiNumber
= SbpCreateProcess
;
40 Status
= NtRequestWaitReplyPort(PortHandle
, &SbApiMsg
->h
, &SbApiMsg
->h
);
41 if (NT_SUCCESS(Status
)) Status
= SbApiMsg
->ReturnValue
;
47 SmpDereferenceSubsystem(IN PSMP_SUBSYSTEM SubSystem
)
49 /* Acquire the database lock while we (potentially) destroy this subsystem */
50 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
52 /* Drop the reference and see if it's terminating */
53 if (!(--SubSystem
->ReferenceCount
) && (SubSystem
->Terminating
))
55 /* Close all handles and free it */
56 if (SubSystem
->Event
) NtClose(SubSystem
->Event
);
57 if (SubSystem
->ProcessHandle
) NtClose(SubSystem
->ProcessHandle
);
58 if (SubSystem
->SbApiPort
) NtClose(SubSystem
->SbApiPort
);
59 RtlFreeHeap(SmpHeap
, 0, SubSystem
);
62 /* Release the database lock */
63 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
68 SmpLocateKnownSubSysByCid(IN PCLIENT_ID ClientId
)
70 PSMP_SUBSYSTEM Subsystem
= NULL
;
71 PLIST_ENTRY NextEntry
;
73 /* Lock the subsystem database */
74 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
76 /* Loop each subsystem in the database */
77 NextEntry
= SmpKnownSubSysHead
.Flink
;
78 while (NextEntry
!= &SmpKnownSubSysHead
)
80 /* Check if this one matches the client ID and is still valid */
81 Subsystem
= CONTAINING_RECORD(NextEntry
, SMP_SUBSYSTEM
, Entry
);
82 if ((*(PULONGLONG
)&Subsystem
->ClientId
== *(PULONGLONG
)ClientId
) &&
83 !(Subsystem
->Terminating
))
85 /* Add a reference and return it */
86 Subsystem
->ReferenceCount
++;
90 /* Reset the current pointer and keep earching */
92 NextEntry
= NextEntry
->Flink
;
95 /* Release the lock and return the subsystem we found */
96 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
102 SmpLocateKnownSubSysByType(IN ULONG MuSessionId
,
105 PSMP_SUBSYSTEM Subsystem
= NULL
;
106 PLIST_ENTRY NextEntry
;
108 /* Lock the subsystem database */
109 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
111 /* Loop each subsystem in the database */
112 NextEntry
= SmpKnownSubSysHead
.Flink
;
113 while (NextEntry
!= &SmpKnownSubSysHead
)
115 /* Check if this one matches the image and uID, and is still valid */
116 Subsystem
= CONTAINING_RECORD(NextEntry
, SMP_SUBSYSTEM
, Entry
);
117 if ((Subsystem
->ImageType
== ImageType
) &&
118 !(Subsystem
->Terminating
) &&
119 (Subsystem
->MuSessionId
== MuSessionId
))
121 /* Return it referenced for the caller */
122 Subsystem
->ReferenceCount
++;
126 /* Reset the current pointer and keep earching */
128 NextEntry
= NextEntry
->Flink
;
131 /* Release the lock and return the subsystem we found */
132 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
138 SmpLoadSubSystem(IN PUNICODE_STRING FileName
,
139 IN PUNICODE_STRING Directory
,
140 IN PUNICODE_STRING CommandLine
,
141 IN ULONG MuSessionId
,
142 OUT PHANDLE ProcessId
,
145 PSMP_SUBSYSTEM Subsystem
, NewSubsystem
, KnownSubsystem
= NULL
;
146 HANDLE SubSysProcessId
;
147 NTSTATUS Status
= STATUS_SUCCESS
;
148 SB_API_MSG SbApiMsg
, SbApiMsg2
;
149 RTL_USER_PROCESS_INFORMATION ProcessInformation
;
150 LARGE_INTEGER Timeout
;
152 PSB_CREATE_PROCESS_MSG CreateProcess
= &SbApiMsg
.CreateProcess
;
153 PSB_CREATE_SESSION_MSG CreateSession
= &SbApiMsg
.CreateSession
;
155 /* Make sure this is a found subsystem */
156 if (Flags
& SMP_INVALID_PATH
)
158 DPRINT1("SMSS: Unable to find subsystem - %wZ\n", FileName
);
159 return STATUS_OBJECT_NAME_NOT_FOUND
;
162 /* Don't use a session if the flag is set */
163 if (Flags
& 0x80) MuSessionId
= 0;
165 /* Lock the subsystems while we do a look up */
166 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
169 /* Check if we found a subsystem not yet fully initialized */
170 Subsystem
= SmpLocateKnownSubSysByType(MuSessionId
, -1);
171 if (!Subsystem
) break;
172 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
174 /* Wait on it to initialize */
175 NtWaitForSingleObject(Subsystem
->Event
, FALSE
, NULL
);
177 /* Dereference it and try the next one */
178 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
179 SmpDereferenceSubsystem(Subsystem
);
182 /* Check if this is a POSIX subsystem */
183 if (Flags
& SMP_POSIX_FLAG
)
185 /* Do we already have it? */
186 Subsystem
= SmpLocateKnownSubSysByType(MuSessionId
, IMAGE_SUBSYSTEM_POSIX_CUI
);
188 else if (Flags
& SMP_OS2_FLAG
)
190 /* This is an OS/2 subsystem, do we we already have it? */
191 Subsystem
= SmpLocateKnownSubSysByType(MuSessionId
, IMAGE_SUBSYSTEM_OS2_CUI
);
194 /* Check if we already have one of the optional subsystems for the session */
197 /* Dereference and return, no work to do */
198 SmpDereferenceSubsystem(Subsystem
);
199 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
200 return STATUS_SUCCESS
;
203 /* Allocate a new subsystem! */
204 NewSubsystem
= RtlAllocateHeap(SmpHeap
, SmBaseTag
, sizeof(SMP_SUBSYSTEM
));
207 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
208 return STATUS_NO_MEMORY
;
211 /* Initialize its header and reference count */
212 NewSubsystem
->ReferenceCount
= 1;
213 NewSubsystem
->MuSessionId
= MuSessionId
;
214 NewSubsystem
->ImageType
= -1;
216 /* Clear out all the other data for now */
217 NewSubsystem
->Terminating
= FALSE
;
218 NewSubsystem
->ProcessHandle
= NULL
;
219 NewSubsystem
->Event
= NULL
;
220 NewSubsystem
->PortHandle
= NULL
;
221 NewSubsystem
->SbApiPort
= NULL
;
223 /* Create the event we'll be waiting on for initialization */
224 Status
= NtCreateEvent(&NewSubsystem
->Event
,
229 if (!NT_SUCCESS(Status
))
231 /* This failed, bail out */
232 RtlFreeHeap(SmpHeap
, 0, NewSubsystem
);
233 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
234 return STATUS_NO_MEMORY
;
237 /* Insert the subsystem and release the lock. It can now be found */
238 InsertTailList(&SmpKnownSubSysHead
, &NewSubsystem
->Entry
);
239 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
241 /* The OS/2 and POSIX subsystems are actually Windows applications! */
242 if (Flags
& (SMP_POSIX_FLAG
| SMP_OS2_FLAG
))
244 /* Locate the Windows subsystem for this session */
245 KnownSubsystem
= SmpLocateKnownSubSysByType(MuSessionId
,
246 IMAGE_SUBSYSTEM_WINDOWS_GUI
);
249 DPRINT1("SMSS: SmpLoadSubSystem - SmpLocateKnownSubSysByType Failed\n");
253 /* Fill out all the process details and call CSRSS to launch it */
254 CreateProcess
->In
.ImageName
= FileName
;
255 CreateProcess
->In
.CurrentDirectory
= Directory
;
256 CreateProcess
->In
.CommandLine
= CommandLine
;
257 CreateProcess
->In
.DllPath
= SmpDefaultLibPath
.Length
?
258 &SmpDefaultLibPath
: NULL
;
259 CreateProcess
->In
.Flags
= Flags
| SMP_DEFERRED_FLAG
;
260 CreateProcess
->In
.DebugFlags
= SmpDebug
;
261 Status
= SmpCallCsrCreateProcess(&SbApiMsg
,
262 sizeof(*CreateProcess
),
263 KnownSubsystem
->SbApiPort
);
264 if (!NT_SUCCESS(Status
))
266 /* Handle failures */
267 DPRINT1("SMSS: SmpLoadSubSystem - SmpCallCsrCreateProcess Failed with Status %lx\n",
272 /* Save the process information we'll need for the create session */
273 ProcessInformation
.ProcessHandle
= CreateProcess
->Out
.ProcessHandle
;
274 ProcessInformation
.ThreadHandle
= CreateProcess
->Out
.ThreadHandle
;
275 ProcessInformation
.ClientId
= CreateProcess
->Out
.ClientId
;
276 ProcessInformation
.ImageInformation
.SubSystemType
= CreateProcess
->Out
.SubsystemType
;
280 /* This must be CSRSS itself, so just launch it and that's it */
281 Status
= SmpExecuteImage(FileName
,
285 Flags
| SMP_DEFERRED_FLAG
,
286 &ProcessInformation
);
287 if (!NT_SUCCESS(Status
))
289 /* Handle failures */
290 DPRINT1("SMSS: SmpLoadSubSystem - SmpExecuteImage Failed with Status %lx\n",
296 /* Fill out the handle and client ID in the subsystem structure now */
297 NewSubsystem
->ProcessHandle
= ProcessInformation
.ProcessHandle
;
298 NewSubsystem
->ClientId
= ProcessInformation
.ClientId
;
300 /* Check if we launched a native image or a subsystem-backed image */
301 if (ProcessInformation
.ImageInformation
.SubSystemType
== IMAGE_SUBSYSTEM_NATIVE
)
303 /* This must be CSRSS itself, since it's a native subsystem image */
304 SubSysProcessId
= ProcessInformation
.ClientId
.UniqueProcess
;
305 if ((ProcessId
) && !(*ProcessId
)) *ProcessId
= SubSysProcessId
;
307 /* Was this the initial CSRSS on Session 0? */
310 /* Then save it in the global variables */
311 SmpWindowsSubSysProcessId
= SubSysProcessId
;
312 SmpWindowsSubSysProcess
= ProcessInformation
.ProcessHandle
;
314 ASSERT(NT_SUCCESS(Status
));
318 /* This is the POSIX or OS/2 subsystem process, copy its information */
319 RtlCopyMemory(&CreateSession
->ProcessInfo
,
321 sizeof(CreateSession
->ProcessInfo
));
323 /* Not sure these field mean what I think they do -- but clear them */
324 *(PULONGLONG
)&CreateSession
->ClientId
= 0;
325 CreateSession
->MuSessionId
= 0;
327 /* This should find CSRSS because they are POSIX or OS/2 subsystems */
328 Subsystem
= SmpLocateKnownSubSysByType(MuSessionId
,
329 ProcessInformation
.ImageInformation
.SubSystemType
);
332 /* Odd failure -- but handle it anyway */
333 Status
= STATUS_NO_SUCH_PACKAGE
;
334 DPRINT1("SMSS: SmpLoadSubSystem - SmpLocateKnownSubSysByType Failed with Status %lx for sessionid %lu\n",
340 /* Duplicate the parent process handle for the subsystem to have */
341 Status
= NtDuplicateObject(NtCurrentProcess(),
342 ProcessInformation
.ProcessHandle
,
343 Subsystem
->ProcessHandle
,
344 &CreateSession
->ProcessInfo
.ProcessHandle
,
348 if (!NT_SUCCESS(Status
))
350 /* Fail since this is critical */
351 DPRINT1("SMSS: SmpLoadSubSystem - NtDuplicateObject Failed with Status %lx for sessionid %lu\n",
357 /* Duplicate the initial thread handle for the subsystem to have */
358 Status
= NtDuplicateObject(NtCurrentProcess(),
359 ProcessInformation
.ThreadHandle
,
360 Subsystem
->ProcessHandle
,
361 &CreateSession
->ProcessInfo
.ThreadHandle
,
365 if (!NT_SUCCESS(Status
))
367 /* Fail since this is critical */
368 DPRINT1("SMSS: SmpLoadSubSystem - NtDuplicateObject Failed with Status %lx for sessionid %lu\n",
374 /* Allocate an internal Session ID for this subsystem */
375 MuSessionId
= SmpAllocateSessionId(Subsystem
, 0);
376 CreateSession
->SessionId
= MuSessionId
;
378 /* Send the create session message to the subsystem */
379 SbApiMsg2
.ReturnValue
= STATUS_SUCCESS
;
380 SbApiMsg2
.h
.u2
.ZeroInit
= 0;
381 SbApiMsg2
.h
.u1
.s1
.DataLength
= sizeof(SB_CREATE_SESSION_MSG
) + 8;
382 SbApiMsg2
.h
.u1
.s1
.TotalLength
= sizeof(SB_API_MSG
);
383 Status
= NtRequestWaitReplyPort(Subsystem
->SbApiPort
,
386 if (NT_SUCCESS(Status
)) Status
= SbApiMsg2
.ReturnValue
;
387 if (!NT_SUCCESS(Status
))
389 /* Delete the session and handle failure if the LPC call failed */
390 SmpDeleteSession(CreateSession
->SessionId
);
391 DPRINT1("SMSS: SmpLoadSubSystem - NtRequestWaitReplyPort Failed with Status %lx for sessionid %lu\n",
393 CreateSession
->SessionId
);
398 /* Okay, everything looks good to go, initialize this subsystem now! */
399 Status
= NtResumeThread(ProcessInformation
.ThreadHandle
, NULL
);
400 if (!NT_SUCCESS(Status
))
402 /* That didn't work -- back out of everything */
403 DPRINT1("SMSS: SmpLoadSubSystem - NtResumeThread failed Status %lx\n", Status
);
407 /* Check if this was the subsystem for a different session */
410 /* Wait up to 60 seconds for it to initialize */
411 Timeout
.QuadPart
= -600000000;
412 Status
= NtWaitForSingleObject(NewSubsystem
->Event
, FALSE
, &Timeout
);
414 /* Timeout is done -- does this session still exist? */
415 if (!SmpCheckDuplicateMuSessionId(MuSessionId
))
417 /* Nope, it died. Cleanup should've ocurred in a different path. */
418 DPRINT1("SMSS: SmpLoadSubSystem - session deleted\n");
419 return STATUS_DELETE_PENDING
;
422 /* Check if we timed our or there was another error with the wait */
423 if (Status
!= STATUS_WAIT_0
)
425 /* Something is wrong with the subsystem, so back out of everything */
426 DPRINT1("SMSS: SmpLoadSubSystem - Timeout waiting for subsystem connect with Status %lx for sessionid %lu\n",
434 /* This a session 0 subsystem, just wait for it to initialize */
435 NtWaitForSingleObject(NewSubsystem
->Event
, FALSE
, NULL
);
438 /* Subsystem is created, resumed, and initialized. Close handles and exit */
439 NtClose(ProcessInformation
.ThreadHandle
);
440 Status
= STATUS_SUCCESS
;
444 /* This is the failure path. First check if we need to detach from session */
445 if ((AttachedSessionId
== -1) || (Flags
& (SMP_POSIX_FLAG
| SMP_OS2_FLAG
)))
447 /* We were not attached, or did not launch subsystems that required it */
448 DPRINT1("SMSS: Did not detach from Session Space: SessionId=%x Flags=%x Status=%x\n",
450 Flags
| SMP_DEFERRED_FLAG
,
455 /* Get the privilege we need for detachment */
456 Status
= SmpAcquirePrivilege(SE_LOAD_DRIVER_PRIVILEGE
, &State
);
457 if (!NT_SUCCESS(Status
))
459 /* We can't detach without it */
460 DPRINT1("SMSS: Did not detach from Session Space: SessionId=%x Flags=%x Status=%x\n",
462 Flags
| SMP_DEFERRED_FLAG
,
467 /* Now detach from the session */
468 Status
= NtSetSystemInformation(SystemSessionDetach
,
470 sizeof(AttachedSessionId
));
471 if (!NT_SUCCESS(Status
))
473 /* Failed to detach. Note the DPRINT1 has a typo in Windows */
474 DPRINT1("SMSS: SmpStartCsr, Couldn't Detach from Session Space. Status=%x\n", Status
);
475 ASSERT(NT_SUCCESS(Status
));
479 /* Detachment worked, reset our attached session ID */
480 AttachedSessionId
= -1;
483 /* And release the privilege we acquired */
484 SmpReleasePrivilege(State
);
488 /* Since this is the failure path, terminate the subsystem process */
489 NtTerminateProcess(ProcessInformation
.ProcessHandle
, Status
);
490 NtClose(ProcessInformation
.ThreadHandle
);
493 /* This is the cleanup path -- first dereference our subsystems */
494 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
495 if (Subsystem
) SmpDereferenceSubsystem(Subsystem
);
496 if (KnownSubsystem
) SmpDereferenceSubsystem(KnownSubsystem
);
498 /* In the failure case, destroy the new subsystem we just created */
499 if (!NT_SUCCESS(Status
))
501 RemoveEntryList(&NewSubsystem
->Entry
);
502 NtSetEvent(NewSubsystem
->Event
, 0);
503 SmpDereferenceSubsystem(NewSubsystem
);
506 /* Finally, we're all done! */
507 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
513 SmpLoadSubSystemsForMuSession(IN PULONG MuSessionId
,
514 OUT PHANDLE ProcessId
,
515 IN PUNICODE_STRING InitialCommand
)
517 NTSTATUS Status
= STATUS_SUCCESS
, Status2
;
518 PSMP_REGISTRY_VALUE RegEntry
;
519 UNICODE_STRING DestinationString
, NtPath
;
520 PLIST_ENTRY NextEntry
;
521 LARGE_INTEGER Timeout
;
524 /* Write a few last registry keys with the boot partition information */
525 SmpTranslateSystemPartitionInformation();
527 /* Process "SetupExecute" values */
528 NextEntry
= SmpSetupExecuteList
.Flink
;
529 while (NextEntry
!= &SmpSetupExecuteList
)
531 /* Execute each one and move on */
532 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
533 SmpExecuteCommand(&RegEntry
->Name
, 0, NULL
, 0);
534 NextEntry
= NextEntry
->Flink
;
537 /* Now process the subsystems */
538 NextEntry
= SmpSubSystemList
.Flink
;
539 while (NextEntry
!= &SmpSubSystemList
)
541 /* Get the entry and check if this is the special Win32k entry */
542 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
543 if (_wcsicmp(RegEntry
->Name
.Buffer
, L
"Kmode") == 0)
546 if (!RtlDosPathNameToNtPathName_U(RegEntry
->Value
.Buffer
,
551 Status
= STATUS_OBJECT_PATH_SYNTAX_BAD
;
552 DPRINT1("Failed: %lx\n", Status
);
556 /* Get the driver privilege */
557 Status
= SmpAcquirePrivilege(SE_LOAD_DRIVER_PRIVILEGE
, &State
);
558 if (NT_SUCCESS(Status
))
560 /* Create the new session */
561 ASSERT(AttachedSessionId
== -1);
562 Status
= NtSetSystemInformation(SystemSessionCreate
,
564 sizeof(*MuSessionId
));
565 if (!NT_SUCCESS(Status
))
567 DPRINT1("SMSS: Session space creation failed\n");
568 SmpReleasePrivilege(State
);
569 RtlFreeHeap(RtlGetProcessHeap(), 0, NtPath
.Buffer
);
572 AttachedSessionId
= *MuSessionId
;
575 * Start Win32k.sys on this session. Use a hardcoded value
576 * instead of the Kmode one...
578 RtlInitUnicodeString(&DestinationString
,
579 L
"\\SystemRoot\\System32\\win32k.sys");
580 Status
= NtSetSystemInformation(SystemExtendServiceTableInformation
,
582 sizeof(DestinationString
));
583 RtlFreeHeap(RtlGetProcessHeap(), 0, NtPath
.Buffer
);
584 SmpReleasePrivilege(State
);
585 if (!NT_SUCCESS(Status
))
587 DPRINT1("SMSS: Load of WIN32K failed.\n");
595 NextEntry
= NextEntry
->Flink
;
598 /* Now parse the required subsystem list */
599 NextEntry
= SmpSubSystemsToLoad
.Flink
;
600 while (NextEntry
!= &SmpSubSystemsToLoad
)
602 /* Get each entry and check if it's the internal debug or not */
603 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
604 if (_wcsicmp(RegEntry
->Name
.Buffer
, L
"Debug") == 0)
606 /* Load the internal debug system */
607 Status
= SmpExecuteCommand(&RegEntry
->Value
,
610 SMP_DEBUG_FLAG
| SMP_SUBSYSTEM_FLAG
);
614 /* Load the required subsystem */
615 Status
= SmpExecuteCommand(&RegEntry
->Value
,
620 if (!NT_SUCCESS(Status
))
622 DbgPrint("SMSS: Subsystem execute failed (%wZ)\n", &RegEntry
->Value
);
626 /* Move to the next entry */
627 NextEntry
= NextEntry
->Flink
;
630 /* Process the "Execute" list now */
631 NextEntry
= SmpExecuteList
.Blink
;
632 if (NextEntry
!= &SmpExecuteList
)
634 /* Get the custom initial command */
635 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
637 /* Write the initial command and wait for 5 seconds (why??!) */
638 *InitialCommand
= RegEntry
->Name
;
639 Timeout
.QuadPart
= -50000000;
640 NtDelayExecution(FALSE
, &Timeout
);
644 /* Use the default Winlogon initial command */
645 RtlInitUnicodeString(InitialCommand
, L
"winlogon.exe");
646 InitialCommandBuffer
[0] = UNICODE_NULL
;
648 /* Check if there's a debugger for Winlogon */
649 Status2
= LdrQueryImageFileExecutionOptions(InitialCommand
,
652 InitialCommandBuffer
,
653 sizeof(InitialCommandBuffer
) -
654 InitialCommand
->Length
,
656 if ((NT_SUCCESS(Status2
)) && (InitialCommandBuffer
[0]))
658 /* Put the debugger string with the Winlogon string */
659 wcscat(InitialCommandBuffer
, L
" ");
660 wcscat(InitialCommandBuffer
, InitialCommand
->Buffer
);
661 RtlInitUnicodeString(InitialCommand
, InitialCommandBuffer
);
665 /* Finally check if there was a custom initial command */
666 NextEntry
= SmpExecuteList
.Flink
;
667 while (NextEntry
!= &SmpExecuteList
)
669 /* Execute each one */
670 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
671 SmpExecuteCommand(&RegEntry
->Name
, *MuSessionId
, NULL
, 0);
672 NextEntry
= NextEntry
->Flink
;