1 /*
2 * PROJECT: ReactOS system libraries
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Functions to read RunOnceEx registry.
5 * COPYRIGHT: Copyright 2021 He Yang <1160386205@qq.com>
6 */
7
8 #include "iernonce.h"
9
10 extern RUNONCEEX_CALLBACK g_Callback;
11
EnumValueName(_In_ DWORD iIndex,_Out_ LPTSTR pszName,_Inout_ LPDWORD pnNameLength)12 LONG CRegKeyEx::EnumValueName(
13 _In_ DWORD iIndex,
14 _Out_ LPTSTR pszName,
15 _Inout_ LPDWORD pnNameLength)
16 {
17 return RegEnumValueW(m_hKey, iIndex, pszName, pnNameLength,
18 NULL, NULL, NULL, NULL);
19 }
20
RunOnceExEntry(_In_ const ATL::CStringW & Name,_In_ const ATL::CStringW & Value)21 RunOnceExEntry::RunOnceExEntry(
22 _In_ const ATL::CStringW &Name,
23 _In_ const ATL::CStringW &Value) :
24 m_Value(Value), m_Name(Name)
25 { ; }
26
Delete(_In_ CRegKeyEx & hParentKey)27 BOOL RunOnceExEntry::Delete(
28 _In_ CRegKeyEx &hParentKey)
29 {
30 return hParentKey.DeleteValue(m_Name) == ERROR_SUCCESS;
31 }
32
Exec() const33 BOOL RunOnceExEntry::Exec() const
34 {
35 CStringW CommandLine;
36 if (wcsncmp(m_Value, L"||", 2) == 0)
37 {
38 // Remove the prefix.
39 CommandLine = (LPCWSTR)m_Value + 2;
40 }
41 else
42 {
43 CommandLine = m_Value;
44 }
45
46 // FIXME: SHEvaluateSystemCommandTemplate is not implemented
47 // using PathGetArgsW, PathRemoveArgsW as a workaround.
48 LPWSTR szCommandLine = CommandLine.GetBuffer();
49 LPCWSTR szParam = PathGetArgsW(szCommandLine);
50 PathRemoveArgsW(szCommandLine);
51
52 SHELLEXECUTEINFOW Info = { 0 };
53 Info.cbSize = sizeof(Info);
54 Info.fMask = SEE_MASK_NOCLOSEPROCESS;
55 Info.lpFile = szCommandLine;
56 Info.lpParameters = szParam;
57 Info.nShow = SW_SHOWNORMAL;
58
59 BOOL bSuccess = ShellExecuteExW(&Info);
60
61 CommandLine.ReleaseBuffer();
62
63 if (!bSuccess)
64 {
65 return FALSE;
66 }
67
68 if (Info.hProcess)
69 {
70 WaitForSingleObject(Info.hProcess, INFINITE);
71 CloseHandle(Info.hProcess);
72 }
73
74 return TRUE;
75 }
76
RunOnceExEntryCmp(_In_ const void * a,_In_ const void * b)77 int RunOnceExEntryCmp(
78 _In_ const void *a,
79 _In_ const void *b)
80 {
81 return lstrcmpW(((RunOnceExEntry *)a)->m_Name,
82 ((RunOnceExEntry *)b)->m_Name);
83 }
84
HandleValue(_In_ CRegKeyEx & hKey,_In_ const CStringW & ValueName)85 BOOL RunOnceExSection::HandleValue(
86 _In_ CRegKeyEx &hKey,
87 _In_ const CStringW &ValueName)
88 {
89 DWORD dwType;
90 DWORD cbData;
91
92 // Query data size
93 if (hKey.QueryValue(ValueName, &dwType, NULL, &cbData) != ERROR_SUCCESS)
94 return FALSE;
95
96 // Validate its format and size.
97 if (dwType != REG_SZ)
98 return TRUE;
99
100 if (cbData % sizeof(WCHAR) != 0)
101 return FALSE;
102
103 CStringW Buffer;
104 LPWSTR szBuffer = Buffer.GetBuffer((cbData / sizeof(WCHAR)) + 1);
105
106 if (hKey.QueryValue(ValueName, &dwType, szBuffer, &cbData) != ERROR_SUCCESS)
107 {
108 Buffer.ReleaseBuffer();
109 return FALSE;
110 }
111 szBuffer[cbData / sizeof(WCHAR)] = L'\0';
112 Buffer.ReleaseBuffer();
113
114 CStringW ExpandStr;
115 DWORD dwcchExpand = ExpandEnvironmentStringsW(Buffer, NULL, 0);
116 ExpandEnvironmentStringsW(Buffer, ExpandStr.GetBuffer(dwcchExpand + 1), dwcchExpand);
117 ExpandStr.ReleaseBuffer();
118
119 if (ValueName.IsEmpty())
120 {
121 // The default value specifies the section title.
122 m_SectionTitle = Buffer;
123 }
124 else
125 {
126 m_EntryList.Add(RunOnceExEntry(ValueName, ExpandStr));
127 }
128
129 return TRUE;
130 }
131
RunOnceExSection(_In_ CRegKeyEx & hParentKey,_In_ const CStringW & lpSubKeyName)132 RunOnceExSection::RunOnceExSection(
133 _In_ CRegKeyEx &hParentKey,
134 _In_ const CStringW &lpSubKeyName) :
135 m_SectionName(lpSubKeyName)
136 {
137 m_bSuccess = FALSE;
138 DWORD dwValueNum;
139 DWORD dwMaxValueNameLen;
140 LSTATUS Error;
141 CStringW ValueName;
142
143 if (m_RegKey.Open(hParentKey, lpSubKeyName) != ERROR_SUCCESS)
144 return;
145
146 Error = RegQueryInfoKeyW(m_RegKey, NULL, 0, NULL, NULL, NULL, NULL,
147 &dwValueNum, &dwMaxValueNameLen,
148 NULL, NULL, NULL);
149 if (Error != ERROR_SUCCESS)
150 return;
151
152 for (DWORD i = 0; i < dwValueNum; i++)
153 {
154 LPWSTR szValueName;
155 DWORD dwcchName = dwMaxValueNameLen + 1;
156
157 szValueName = ValueName.GetBuffer(dwMaxValueNameLen + 1);
158 Error = m_RegKey.EnumValueName(i, szValueName, &dwcchName);
159 ValueName.ReleaseBuffer();
160
161 if (Error != ERROR_SUCCESS)
162 {
163 // TODO: error handling
164 return;
165 }
166
167 if (!HandleValue(m_RegKey, ValueName))
168 return;
169 }
170
171 // Sort entries by name in string order.
172 qsort(m_EntryList.GetData(), m_EntryList.GetSize(),
173 sizeof(RunOnceExEntry), RunOnceExEntryCmp);
174
175 m_bSuccess = TRUE;
176 return;
177 }
178
179 // Copy constructor, CSimpleArray needs it.
RunOnceExSection(_In_ const RunOnceExSection & Section)180 RunOnceExSection::RunOnceExSection(_In_ const RunOnceExSection& Section) :
181 m_SectionName(Section.m_SectionName),
182 m_bSuccess(Section.m_bSuccess),
183 m_SectionTitle(Section.m_SectionTitle),
184 m_EntryList(Section.m_EntryList)
185 {
186 m_RegKey.Attach(Section.m_RegKey);
187 }
188
CloseAndDelete(_In_ CRegKeyEx & hParentKey)189 BOOL RunOnceExSection::CloseAndDelete(
190 _In_ CRegKeyEx &hParentKey)
191 {
192 m_RegKey.Close();
193 return hParentKey.RecurseDeleteKey(m_SectionName) == ERROR_SUCCESS;
194 }
195
GetEntryCnt() const196 UINT RunOnceExSection::GetEntryCnt() const
197 {
198 return m_EntryList.GetSize();
199 }
200
Exec(_Inout_ UINT & iCompleteCnt,_In_ const UINT iTotalCnt)201 BOOL RunOnceExSection::Exec(
202 _Inout_ UINT& iCompleteCnt,
203 _In_ const UINT iTotalCnt)
204 {
205 BOOL bSuccess = TRUE;
206
207 for (int i = 0; i < m_EntryList.GetSize(); i++)
208 {
209 m_EntryList[i].Delete(m_RegKey);
210 bSuccess &= m_EntryList[i].Exec();
211 iCompleteCnt++;
212 // TODO: the meaning of the third param is still unknown, seems it's always 0.
213 if (g_Callback)
214 g_Callback(iCompleteCnt, iTotalCnt, NULL);
215 }
216 return bSuccess;
217 }
218
RunOnceExSectionCmp(_In_ const void * a,_In_ const void * b)219 int RunOnceExSectionCmp(
220 _In_ const void *a,
221 _In_ const void *b)
222 {
223 return lstrcmpW(((RunOnceExSection *)a)->m_SectionName,
224 ((RunOnceExSection *)b)->m_SectionName);
225 }
226
RunOnceExInstance(_In_ HKEY BaseKey)227 RunOnceExInstance::RunOnceExInstance(_In_ HKEY BaseKey)
228 {
229 m_bSuccess = FALSE;
230 DWORD dwSubKeyNum;
231 DWORD dwMaxSubKeyNameLen;
232 LSTATUS Error;
233 CStringW SubKeyName;
234
235 Error = m_RegKey.Open(BaseKey,
236 L"Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx\\");
237 if (Error != ERROR_SUCCESS)
238 {
239 return;
240 }
241
242 ULONG cchTitle;
243 Error = m_RegKey.QueryStringValue(L"Title", NULL, &cchTitle);
244 if (Error == ERROR_SUCCESS)
245 {
246 Error = m_RegKey.QueryStringValue(L"Title", m_Title.GetBuffer(cchTitle + 1), &cchTitle);
247 m_Title.ReleaseBuffer();
248 if (Error != ERROR_SUCCESS)
249 return;
250 }
251
252 Error = m_RegKey.QueryDWORDValue(L"Flags", m_dwFlags);
253 if (Error != ERROR_SUCCESS)
254 {
255 m_dwFlags = 0;
256 }
257
258 Error = RegQueryInfoKeyW(m_RegKey, NULL, 0, NULL,
259 &dwSubKeyNum, &dwMaxSubKeyNameLen,
260 NULL, NULL, NULL, NULL, NULL, NULL);
261 if (Error != ERROR_SUCCESS)
262 return;
263
264 m_bShowDialog = FALSE;
265
266 for (DWORD i = 0; i < dwSubKeyNum; i++)
267 {
268 LPWSTR szSubKeyName;
269 DWORD dwcchName = dwMaxSubKeyNameLen + 1;
270
271 szSubKeyName = SubKeyName.GetBuffer(dwMaxSubKeyNameLen + 1);
272 Error = m_RegKey.EnumKey(i, szSubKeyName, &dwcchName);
273 SubKeyName.ReleaseBuffer();
274
275 if (Error != ERROR_SUCCESS)
276 {
277 // TODO: error handling
278 return;
279 }
280
281 if (!HandleSubKey(m_RegKey, SubKeyName))
282 return;
283 }
284
285 // Sort sections by name in string order.
286 qsort(m_SectionList.GetData(), m_SectionList.GetSize(),
287 sizeof(RunOnceExSection), RunOnceExSectionCmp);
288
289 m_bSuccess = TRUE;
290 return;
291 }
292
Exec(_In_opt_ HWND hwnd)293 BOOL RunOnceExInstance::Exec(_In_opt_ HWND hwnd)
294 {
295 BOOL bSuccess = TRUE;
296
297 UINT TotalCnt = 0;
298 UINT CompleteCnt = 0;
299 for (int i = 0; i < m_SectionList.GetSize(); i++)
300 {
301 TotalCnt += m_SectionList[i].GetEntryCnt();
302 }
303
304 // Execute items from registry one by one, and remove them.
305 for (int i = 0; i < m_SectionList.GetSize(); i++)
306 {
307 if (hwnd)
308 SendMessageW(hwnd, WM_SETINDEX, i, 0);
309
310 bSuccess &= m_SectionList[i].Exec(CompleteCnt, TotalCnt);
311 m_SectionList[i].CloseAndDelete(m_RegKey);
312 }
313
314 if (m_RegKey)
315 {
316 m_RegKey.DeleteValue(L"Title");
317 m_RegKey.DeleteValue(L"Flags");
318 }
319
320 // Notify the dialog all sections are handled.
321 if (hwnd)
322 SendMessageW(hwnd, WM_SETINDEX, m_SectionList.GetSize(), bSuccess);
323 return bSuccess;
324 }
325
Run(_In_ BOOL bSilence)326 BOOL RunOnceExInstance::Run(_In_ BOOL bSilence)
327 {
328 if (bSilence ||
329 (m_dwFlags & FLAGS_NO_STAT_DIALOG) ||
330 !m_bShowDialog)
331 {
332 return Exec(NULL);
333 }
334 else
335 {
336 // The dialog is responsible to create a thread and execute.
337 ProgressDlg dlg(*this);
338 return dlg.RunDialogBox();
339 }
340 }
341
HandleSubKey(_In_ CRegKeyEx & hKey,_In_ const CStringW & SubKeyName)342 BOOL RunOnceExInstance::HandleSubKey(
343 _In_ CRegKeyEx &hKey,
344 _In_ const CStringW& SubKeyName)
345 {
346 RunOnceExSection Section(hKey, SubKeyName);
347 if (!Section.m_bSuccess)
348 {
349 return FALSE;
350 }
351
352 if (!Section.m_SectionTitle.IsEmpty())
353 {
354 m_bShowDialog = TRUE;
355 }
356 m_SectionList.Add(Section);
357
358 // The copy constructor of RunOnceExSection didn't detach
359 // the m_RegKey while it's attached to the one in the array.
360 // So we have to detach it manually.
361 Section.m_RegKey.Detach();
362 return TRUE;
363 }
364