1 /*
2  * PROJECT:     ReactOS Applications Manager
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Functions to parse command-line flags and process them
5  * COPYRIGHT:   Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org)
6  *              Copyright 2020 He Yang (1160386205@qq.com)
7  */
8 
9 #include "gui.h"
10 #include "unattended.h"
11 #include "configparser.h"
12 #include <setupapi.h>
13 #include <conutils.h>
14 
15 static BOOL
MatchCmdOption(LPWSTR argvOption,LPCWSTR szOptToMacth)16 MatchCmdOption(LPWSTR argvOption, LPCWSTR szOptToMacth)
17 {
18     WCHAR FirstCharList[] = {L'-', L'/'};
19 
20     for (UINT i = 0; i < _countof(FirstCharList); i++)
21     {
22         if (argvOption[0] == FirstCharList[i])
23         {
24             return StrCmpIW(argvOption + 1, szOptToMacth) == 0;
25         }
26     }
27     return FALSE;
28 }
29 
30 static void
InitRappsConsole()31 InitRappsConsole()
32 {
33     // First, try to attach to our parent's console
34     if (!AttachConsole(ATTACH_PARENT_PROCESS))
35     {
36         // Did we already have a console?
37         if (GetLastError() != ERROR_ACCESS_DENIED)
38         {
39             // No, try to open a new one
40             AllocConsole();
41         }
42     }
43     ConInitStdStreams(); // Initialize the Console Standard Streams
44 }
45 
46 static CAppInfo *
SearchForAppWithDisplayName(CAppDB & db,AppsCategories Type,LPCWSTR Name)47 SearchForAppWithDisplayName(CAppDB &db, AppsCategories Type, LPCWSTR Name)
48 {
49     CAtlList<CAppInfo *> List;
50     db.GetApps(List, Type);
51     for (POSITION it = List.GetHeadPosition(); it;)
52     {
53         CAppInfo *Info = List.GetNext(it);
54         if (SearchPatternMatch(Info->szDisplayName, Name))
55         {
56             return Info;
57         }
58     }
59     return NULL;
60 }
61 
62 static BOOL
HandleInstallCommand(CAppDB * db,LPWSTR szCommand,int argcLeft,LPWSTR * argvLeft)63 HandleInstallCommand(CAppDB *db, LPWSTR szCommand, int argcLeft, LPWSTR *argvLeft)
64 {
65     if (argcLeft < 1)
66     {
67         InitRappsConsole();
68         ConResMsgPrintf(StdOut, NULL, IDS_CMD_NEED_PACKAGE_NAME, szCommand);
69         return FALSE;
70     }
71 
72     CAtlList<CAppInfo *> Applications;
73     for (int i = 0; i < argcLeft; i++)
74     {
75         LPCWSTR PackageName = argvLeft[i];
76         CAppInfo *AppInfo = db->FindByPackageName(PackageName);
77         if (AppInfo)
78         {
79             Applications.AddTail(AppInfo);
80         }
81     }
82 
83     return DownloadListOfApplications(Applications, TRUE);
84 }
85 
86 static BOOL
HandleSetupCommand(CAppDB * db,LPWSTR szCommand,int argcLeft,LPWSTR * argvLeft)87 HandleSetupCommand(CAppDB *db, LPWSTR szCommand, int argcLeft, LPWSTR *argvLeft)
88 {
89     if (argcLeft != 1)
90     {
91         InitRappsConsole();
92         ConResMsgPrintf(StdOut, NULL, IDS_CMD_NEED_FILE_NAME, szCommand);
93         return FALSE;
94     }
95 
96     CAtlList<CAppInfo *> Applications;
97     HINF InfHandle = SetupOpenInfFileW(argvLeft[0], NULL, INF_STYLE_WIN4, NULL);
98     if (InfHandle == INVALID_HANDLE_VALUE)
99     {
100         return FALSE;
101     }
102 
103     INFCONTEXT Context;
104     if (SetupFindFirstLineW(InfHandle, L"RAPPS", L"Install", &Context))
105     {
106         WCHAR szPkgName[MAX_PATH];
107         do
108         {
109             if (SetupGetStringFieldW(&Context, 1, szPkgName, _countof(szPkgName), NULL))
110             {
111                 CAppInfo *AppInfo = db->FindByPackageName(szPkgName);
112                 if (AppInfo)
113                 {
114                     Applications.AddTail(AppInfo);
115                 }
116             }
117         } while (SetupFindNextLine(&Context, &Context));
118     }
119     SetupCloseInfFile(InfHandle);
120 
121     return DownloadListOfApplications(Applications, TRUE);
122 }
123 
124 static BOOL
HandleUninstallCommand(CAppDB & db,UINT argcLeft,LPWSTR * argvLeft)125 HandleUninstallCommand(CAppDB &db, UINT argcLeft, LPWSTR *argvLeft)
126 {
127     UINT argi = 0, silent = FALSE, byregkeyname = FALSE;
128     for (; argcLeft; ++argi, --argcLeft)
129     {
130         if (!StrCmpIW(argvLeft[argi], L"/S"))
131             ++silent;
132         else if (!StrCmpIW(argvLeft[argi], L"/K"))
133             ++byregkeyname;
134         else
135             break;
136     }
137     if (argcLeft != 1)
138         return FALSE;
139 
140     CStringW buf;
141     LPCWSTR name = argvLeft[argi];
142     BOOL retval = FALSE;
143     CAppInfo *pInfo = NULL, *pDelete = NULL;
144 
145     if (!byregkeyname)
146     {
147         for (UINT i = ENUM_INSTALLED_MIN; !pInfo && i <= ENUM_INSTALLED_MAX; ++i)
148         {
149             pInfo = SearchForAppWithDisplayName(db, AppsCategories(i), name);
150         }
151 
152         if (!pInfo)
153         {
154             CAvailableApplicationInfo *p = db.FindAvailableByPackageName(name);
155             if (p)
156             {
157                 CConfigParser *cp = p->GetConfigParser();
158                 if (cp && cp->GetString(DB_REGNAME, buf) && !buf.IsEmpty())
159                 {
160                     name = buf.GetString();
161                     byregkeyname = TRUE;
162                 }
163             }
164         }
165     }
166 
167     if (byregkeyname)
168     {
169         // Force a specific key type if requested (<M|U>[32|64]<\\KeyName>)
170         if (name[0])
171         {
172             REGSAM wow = 0;
173             UINT i = 1;
174             if (name[i] == '3' && name[i + 1])
175                 wow = KEY_WOW64_32KEY, i += 2;
176             else if (name[i] == '6' && name[i + 1])
177                 wow = KEY_WOW64_64KEY, i += 2;
178 
179             if (name[i++] == '\\')
180             {
181                 pInfo = CAppDB::CreateInstalledAppInstance(name + i, name[0] == 'U', wow);
182             }
183         }
184 
185         if (!pInfo)
186         {
187             pInfo = CAppDB::CreateInstalledAppByRegistryKey(name);
188         }
189         pDelete = pInfo;
190     }
191 
192     if (pInfo)
193     {
194         retval = pInfo->UninstallApplication(silent ? UCF_SILENT : UCF_NONE);
195     }
196     delete pDelete;
197     return retval;
198 }
199 
200 static BOOL
HandleGenerateInstallerCommand(CAppDB & db,UINT argcLeft,LPWSTR * argvLeft)201 HandleGenerateInstallerCommand(CAppDB &db, UINT argcLeft, LPWSTR *argvLeft)
202 {
203     if (argcLeft != 2)
204         return FALSE;
205 
206     CAvailableApplicationInfo *pAI = db.FindAvailableByPackageName(argvLeft[0]);
207     if (!pAI)
208         return FALSE;
209 
210     return ExtractAndRunGeneratedInstaller(*pAI, argvLeft[1]);
211 }
212 
213 static BOOL
HandleFindCommand(CAppDB * db,LPWSTR szCommand,int argcLeft,LPWSTR * argvLeft)214 HandleFindCommand(CAppDB *db, LPWSTR szCommand, int argcLeft, LPWSTR *argvLeft)
215 {
216     if (argcLeft < 1)
217     {
218         ConResMsgPrintf(StdOut, NULL, IDS_CMD_NEED_PARAMS, szCommand);
219         return FALSE;
220     }
221 
222     CAtlList<CAppInfo *> List;
223     db->GetApps(List, ENUM_ALL_AVAILABLE);
224 
225     for (int i = 0; i < argcLeft; i++)
226     {
227         LPCWSTR lpszSearch = argvLeft[i];
228         ConResMsgPrintf(StdOut, NULL, IDS_CMD_FIND_RESULT_FOR, lpszSearch);
229 
230         POSITION CurrentListPosition = List.GetHeadPosition();
231         while (CurrentListPosition)
232         {
233             CAppInfo *Info = List.GetNext(CurrentListPosition);
234 
235             if (SearchPatternMatch(Info->szDisplayName, lpszSearch) || SearchPatternMatch(Info->szComments, lpszSearch))
236             {
237                 ConPrintf(StdOut, L"%s (%s)\n", Info->szDisplayName.GetString(), Info->szIdentifier.GetString());
238             }
239         }
240 
241         ConPrintf(StdOut, L"\n");
242     }
243 
244     return TRUE;
245 }
246 
247 static BOOL
HandleInfoCommand(CAppDB * db,LPWSTR szCommand,int argcLeft,LPWSTR * argvLeft)248 HandleInfoCommand(CAppDB *db, LPWSTR szCommand, int argcLeft, LPWSTR *argvLeft)
249 {
250     if (argcLeft < 1)
251     {
252         ConResMsgPrintf(StdOut, NULL, IDS_CMD_NEED_PARAMS, szCommand);
253         return FALSE;
254     }
255 
256     for (int i = 0; i < argcLeft; i++)
257     {
258         LPCWSTR PackageName = argvLeft[i];
259         CAppInfo *AppInfo = db->FindByPackageName(PackageName);
260         if (!AppInfo)
261         {
262             ConResMsgPrintf(StdOut, NULL, IDS_CMD_PACKAGE_NOT_FOUND, PackageName);
263         }
264         else
265         {
266             ConResMsgPrintf(StdOut, NULL, IDS_CMD_PACKAGE_INFO, PackageName);
267 
268             ConPuts(StdOut, AppInfo->szDisplayName);
269 
270             if (!AppInfo->szDisplayVersion.IsEmpty())
271             {
272                 ConResPrintf(StdOut, IDS_AINFO_VERSION);
273                 ConPuts(StdOut, AppInfo->szDisplayVersion);
274             }
275 
276             CStringW License, Size, UrlSite, UrlDownload;
277             AppInfo->GetDisplayInfo(License, Size, UrlSite, UrlDownload);
278 
279             if (!License.IsEmpty())
280             {
281                 ConResPrintf(StdOut, IDS_AINFO_LICENSE);
282                 ConPuts(StdOut, License);
283             }
284 
285             if (!Size.IsEmpty())
286             {
287                 ConResPrintf(StdOut, IDS_AINFO_SIZE);
288                 ConPuts(StdOut, Size);
289             }
290 
291             if (!UrlSite.IsEmpty())
292             {
293                 ConResPrintf(StdOut, IDS_AINFO_URLSITE);
294                 ConPuts(StdOut, UrlSite);
295             }
296 
297             if (AppInfo->szComments)
298             {
299                 ConResPrintf(StdOut, IDS_AINFO_DESCRIPTION);
300                 ConPuts(StdOut, AppInfo->szComments);
301             }
302 
303             if (!UrlDownload.IsEmpty())
304             {
305                 ConResPrintf(StdOut, IDS_AINFO_URLDOWNLOAD);
306                 ConPuts(StdOut, UrlDownload);
307             }
308             ConPuts(StdOut, L"\n");
309         }
310         ConPuts(StdOut, L"\n");
311     }
312     return TRUE;
313 }
314 
315 static VOID
PrintHelpCommand()316 PrintHelpCommand()
317 {
318     ConPrintf(StdOut, L"\n");
319     ConResPuts(StdOut, IDS_APPTITLE);
320     ConPrintf(StdOut, L"\n\n");
321 
322     ConResPuts(StdOut, IDS_CMD_USAGE);
323     ConPrintf(StdOut, L"%ls\n", UsageString);
324 }
325 
326 BOOL
ParseCmdAndExecute(LPWSTR lpCmdLine,BOOL bIsFirstLaunch,int nCmdShow)327 ParseCmdAndExecute(LPWSTR lpCmdLine, BOOL bIsFirstLaunch, int nCmdShow)
328 {
329     INT argc;
330     LPWSTR *argv = CommandLineToArgvW(lpCmdLine, &argc);
331     if (!argv)
332         return FALSE;
333 
334     CStringW Directory;
335     GetStorageDirectory(Directory);
336     CAppDB db(Directory);
337 
338     BOOL bAppwizMode = (argc > 1 && MatchCmdOption(argv[1], CMD_KEY_APPWIZ));
339     if (!bAppwizMode)
340     {
341         CUpdateDatabaseMutex lock;
342         if (SettingsInfo.bUpdateAtStart || bIsFirstLaunch)
343             db.RemoveCached();
344 
345         db.UpdateAvailable();
346     }
347 
348     db.UpdateInstalled();
349 
350     if (argc == 1 || bAppwizMode) // RAPPS is launched without options or APPWIZ mode is requested
351     {
352         // Check whether the RAPPS MainWindow is already launched in another process
353         CStringW szWindowText(MAKEINTRESOURCEW(bAppwizMode ? IDS_APPWIZ_TITLE : IDS_APPTITLE));
354         LPCWSTR pszMutex = bAppwizMode ? L"RAPPWIZ" : MAINWINDOWMUTEX;
355 
356         HANDLE hMutex = CreateMutexW(NULL, FALSE, pszMutex);
357         if ((!hMutex) || (GetLastError() == ERROR_ALREADY_EXISTS))
358         {
359             /* If already started, find its window */
360             HWND hWindow;
361             for (int wait = 2500, inter = 250; wait > 0; wait -= inter)
362             {
363                 if ((hWindow = FindWindowW(szWindowClass, szWindowText)) != NULL)
364                     break;
365                 Sleep(inter);
366             }
367 
368             if (hWindow)
369             {
370                 /* Activate the window in the other instance */
371                 ShowWindow(hWindow, SW_SHOW);
372                 SwitchToThisWindow(hWindow, TRUE);
373                 if (bAppwizMode)
374                     PostMessage(hWindow, WM_COMMAND, ID_ACTIVATE_APPWIZ, 0);
375 
376                 if (hMutex)
377                     CloseHandle(hMutex);
378 
379                 return FALSE;
380             }
381         }
382         szWindowText.Empty();
383 
384         CMainWindow wnd(&db, bAppwizMode);
385         MainWindowLoop(&wnd, nCmdShow);
386 
387         if (hMutex)
388             CloseHandle(hMutex);
389 
390         return TRUE;
391     }
392 
393     if (MatchCmdOption(argv[1], CMD_KEY_INSTALL))
394     {
395         return HandleInstallCommand(&db, argv[1], argc - 2, argv + 2);
396     }
397     else if (MatchCmdOption(argv[1], CMD_KEY_SETUP))
398     {
399         return HandleSetupCommand(&db, argv[1], argc - 2, argv + 2);
400     }
401     else if (MatchCmdOption(argv[1], CMD_KEY_UNINSTALL))
402     {
403         return HandleUninstallCommand(db, argc - 2, argv + 2);
404     }
405     else if (MatchCmdOption(argv[1], CMD_KEY_GENINST))
406     {
407         return HandleGenerateInstallerCommand(db, argc - 2, argv + 2);
408     }
409 
410     InitRappsConsole();
411 
412     if (MatchCmdOption(argv[1], CMD_KEY_FIND))
413     {
414         return HandleFindCommand(&db, argv[1], argc - 2, argv + 2);
415     }
416     else if (MatchCmdOption(argv[1], CMD_KEY_INFO))
417     {
418         return HandleInfoCommand(&db, argv[1], argc - 2, argv + 2);
419     }
420     else if (MatchCmdOption(argv[1], CMD_KEY_HELP) || MatchCmdOption(argv[1], CMD_KEY_HELP_ALT))
421     {
422         PrintHelpCommand();
423         return TRUE;
424     }
425     else
426     {
427         // unrecognized/invalid options
428         ConResPuts(StdOut, IDS_CMD_INVALID_OPTION);
429         PrintHelpCommand();
430         return FALSE;
431     }
432 }
433