1 /*
2  * Unit test of the Program Manager DDE Interfaces
3  *
4  * Copyright 2009 Mikey Alexander
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 /* DDE Program Manager Tests
22  * - Covers basic CreateGroup, ShowGroup, DeleteGroup, AddItem, and DeleteItem
23  *   functionality
24  * - Todo: Handle CommonGroupFlag
25  *         Better AddItem Tests (Lots of parameters to test)
26  *         Tests for Invalid Characters in Names / Invalid Parameters
27  */
28 
29 #include "precomp.h"
30 
31 /* Timeout on DdeClientTransaction Call */
32 #define MS_TIMEOUT_VAL 1000
33 /* # of times to poll for window creation */
34 #define PDDE_POLL_NUM 150
35 /* time to sleep between polls */
36 #define PDDE_POLL_TIME 300
37 
38 /* Call Info */
39 #define DDE_TEST_MISC            0x00010000
40 #define DDE_TEST_CREATEGROUP     0x00020000
41 #define DDE_TEST_DELETEGROUP     0x00030000
42 #define DDE_TEST_SHOWGROUP       0x00040000
43 #define DDE_TEST_ADDITEM         0x00050000
44 #define DDE_TEST_DELETEITEM      0x00060000
45 #define DDE_TEST_COMPOUND        0x00070000
46 #define DDE_TEST_CALLMASK        0x00ff0000
47 
48 #define DDE_TEST_NUMMASK           0x0000ffff
49 
50 static HRESULT (WINAPI *pSHGetLocalizedName)(LPCWSTR, LPWSTR, UINT, int *);
51 static BOOL (WINAPI *pSHGetSpecialFolderPathA)(HWND, LPSTR, int, BOOL);
52 static BOOL (WINAPI *pReadCabinetState)(CABINETSTATE *, int);
53 
54 static void init_function_pointers(void)
55 {
56     HMODULE hmod;
57 
58     hmod = GetModuleHandleA("shell32.dll");
59     pSHGetLocalizedName = (void*)GetProcAddress(hmod, "SHGetLocalizedName");
60     pSHGetSpecialFolderPathA = (void*)GetProcAddress(hmod, "SHGetSpecialFolderPathA");
61     pReadCabinetState = (void*)GetProcAddress(hmod, "ReadCabinetState");
62     if (!pReadCabinetState)
63         pReadCabinetState = (void*)GetProcAddress(hmod, (LPSTR)651);
64 }
65 
66 static BOOL use_common(void)
67 {
68     HMODULE hmod;
69     static BOOL (WINAPI *pIsNTAdmin)(DWORD, LPDWORD);
70 
71     /* IsNTAdmin() is available on all platforms. */
72     hmod = LoadLibraryA("advpack.dll");
73     pIsNTAdmin = (void*)GetProcAddress(hmod, "IsNTAdmin");
74 
75     if (!pIsNTAdmin(0, NULL))
76     {
77         /* We are definitely not an administrator */
78         FreeLibrary(hmod);
79         return FALSE;
80     }
81     FreeLibrary(hmod);
82 
83     /* If we end up here we are on NT4+ as Win9x and WinMe don't have the
84      * notion of administrators (as we need it).
85      */
86 
87     /* As of Vista  we should always use the users directory. Tests with the
88      * real Administrator account on Windows 7 proved this.
89      *
90      * FIXME: We need a better way of identifying Vista+ as currently this check
91      * also covers Wine and we don't know yet which behavior we want to follow.
92      */
93     if (pSHGetLocalizedName)
94         return FALSE;
95 
96     return TRUE;
97 }
98 
99 static BOOL full_title(void)
100 {
101     CABINETSTATE cs;
102 
103     memset(&cs, 0, sizeof(cs));
104     if (pReadCabinetState)
105     {
106         pReadCabinetState(&cs, sizeof(cs));
107     }
108     else
109     {
110         HKEY key;
111         DWORD size;
112 
113         win_skip("ReadCabinetState is not available, reading registry directly\n");
114         RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CabinetState", &key);
115         size = sizeof(cs);
116         RegQueryValueExA(key, "Settings", NULL, NULL, (LPBYTE)&cs, &size);
117         RegCloseKey(key);
118     }
119 
120     return (cs.fFullPathTitle == -1);
121 }
122 
123 static char ProgramsDir[MAX_PATH];
124 
125 static char Group1Title[MAX_PATH]  = "Group1";
126 static char Group2Title[MAX_PATH]  = "Group2";
127 static char Group3Title[MAX_PATH]  = "Group3";
128 static char StartupTitle[MAX_PATH] = "Startup";
129 
130 static void init_strings(void)
131 {
132     char startup[MAX_PATH];
133     char commonprograms[MAX_PATH];
134     char programs[MAX_PATH];
135 
136     if (pSHGetSpecialFolderPathA)
137     {
138         pSHGetSpecialFolderPathA(NULL, programs, CSIDL_PROGRAMS, FALSE);
139         pSHGetSpecialFolderPathA(NULL, commonprograms, CSIDL_COMMON_PROGRAMS, FALSE);
140         pSHGetSpecialFolderPathA(NULL, startup, CSIDL_STARTUP, FALSE);
141     }
142     else
143     {
144         HKEY key;
145         DWORD size;
146 
147         /* Older Win9x and NT4 */
148 
149         RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", &key);
150         size = sizeof(programs);
151         RegQueryValueExA(key, "Programs", NULL, NULL, (LPBYTE)&programs, &size);
152         size = sizeof(startup);
153         RegQueryValueExA(key, "Startup", NULL, NULL, (LPBYTE)&startup, &size);
154         RegCloseKey(key);
155 
156         RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", &key);
157         size = sizeof(commonprograms);
158         RegQueryValueExA(key, "Common Programs", NULL, NULL, (LPBYTE)&commonprograms, &size);
159         RegCloseKey(key);
160     }
161 
162     /* ProgramsDir on Vista+ is always the users one (CSIDL_PROGRAMS). Before Vista
163      * it depends on whether the user is an administrator (CSIDL_COMMON_PROGRAMS) or
164      * not (CSIDL_PROGRAMS).
165      */
166     if (use_common())
167         lstrcpyA(ProgramsDir, commonprograms);
168     else
169         lstrcpyA(ProgramsDir, programs);
170 
171     if (full_title())
172     {
173         lstrcpyA(Group1Title, ProgramsDir);
174         lstrcatA(Group1Title, "\\Group1");
175         lstrcpyA(Group2Title, ProgramsDir);
176         lstrcatA(Group2Title, "\\Group2");
177         lstrcpyA(Group3Title, ProgramsDir);
178         lstrcatA(Group3Title, "\\Group3");
179 
180         lstrcpyA(StartupTitle, startup);
181     }
182     else
183     {
184         /* Vista has the nice habit of displaying the full path in English
185          * and the short one localized. CSIDL_STARTUP on Vista gives us the
186          * English version so we have to 'translate' this one.
187          *
188          * MSDN claims it should be used for files not folders but this one
189          * suits our purposes just fine.
190          */
191         if (pSHGetLocalizedName)
192         {
193             WCHAR startupW[MAX_PATH];
194             WCHAR module[MAX_PATH];
195             WCHAR module_expanded[MAX_PATH];
196             WCHAR localized[MAX_PATH];
197             HRESULT hr;
198             int id;
199 
200             MultiByteToWideChar(CP_ACP, 0, startup, -1, startupW, sizeof(startupW)/sizeof(WCHAR));
201             hr = pSHGetLocalizedName(startupW, module, MAX_PATH, &id);
202             todo_wine ok(hr == S_OK, "got 0x%08x\n", hr);
203             /* check to be removed when SHGetLocalizedName is implemented */
204             if (hr == S_OK)
205             {
206                 ExpandEnvironmentStringsW(module, module_expanded, MAX_PATH);
207                 LoadStringW(GetModuleHandleW(module_expanded), id, localized, MAX_PATH);
208 
209                 WideCharToMultiByte(CP_ACP, 0, localized, -1, StartupTitle, sizeof(StartupTitle), NULL, NULL);
210             }
211             else
212                 lstrcpyA(StartupTitle, (strrchr(startup, '\\') + 1));
213         }
214         else
215         {
216             lstrcpyA(StartupTitle, (strrchr(startup, '\\') + 1));
217         }
218     }
219 }
220 
221 static HDDEDATA CALLBACK DdeCallback(UINT type, UINT format, HCONV hConv, HSZ hsz1, HSZ hsz2,
222                                      HDDEDATA hDDEData, ULONG_PTR data1, ULONG_PTR data2)
223 {
224     trace("Callback: type=%i, format=%i\n", type, format);
225     return NULL;
226 }
227 
228 /*
229  * Encoded String for Error Messages so that inner failures can determine
230  * what test is failing.  Format is: [Code:TestNum]
231  */
232 static const char * GetStringFromTestParams(int testParams)
233 {
234     int testNum;
235     static char testParamString[64];
236     const char *callId;
237 
238     testNum = testParams & DDE_TEST_NUMMASK;
239     switch (testParams & DDE_TEST_CALLMASK)
240     {
241     default:
242     case DDE_TEST_MISC:
243         callId = "MISC";
244         break;
245     case DDE_TEST_CREATEGROUP:
246         callId = "C_G";
247         break;
248     case DDE_TEST_DELETEGROUP:
249         callId = "D_G";
250         break;
251     case DDE_TEST_SHOWGROUP:
252         callId = "S_G";
253         break;
254     case DDE_TEST_ADDITEM:
255         callId = "A_I";
256         break;
257     case DDE_TEST_DELETEITEM:
258         callId = "D_I";
259         break;
260     case DDE_TEST_COMPOUND:
261         callId = "CPD";
262         break;
263     }
264 
265     sprintf(testParamString, "  [%s:%i]", callId, testNum);
266     return testParamString;
267 }
268 
269 /* Transfer DMLERR's into text readable strings for Error Messages */
270 #define DMLERR_TO_STR(x) case x: return#x;
271 static const char * GetStringFromError(UINT err)
272 {
273     switch (err)
274     {
275     DMLERR_TO_STR(DMLERR_NO_ERROR);
276     DMLERR_TO_STR(DMLERR_ADVACKTIMEOUT);
277     DMLERR_TO_STR(DMLERR_BUSY);
278     DMLERR_TO_STR(DMLERR_DATAACKTIMEOUT);
279     DMLERR_TO_STR(DMLERR_DLL_NOT_INITIALIZED);
280     DMLERR_TO_STR(DMLERR_DLL_USAGE);
281     DMLERR_TO_STR(DMLERR_EXECACKTIMEOUT);
282     DMLERR_TO_STR(DMLERR_INVALIDPARAMETER);
283     DMLERR_TO_STR(DMLERR_LOW_MEMORY);
284     DMLERR_TO_STR(DMLERR_MEMORY_ERROR);
285     DMLERR_TO_STR(DMLERR_NOTPROCESSED);
286     DMLERR_TO_STR(DMLERR_NO_CONV_ESTABLISHED);
287     DMLERR_TO_STR(DMLERR_POKEACKTIMEOUT);
288     DMLERR_TO_STR(DMLERR_POSTMSG_FAILED);
289     DMLERR_TO_STR(DMLERR_REENTRANCY);
290     DMLERR_TO_STR(DMLERR_SERVER_DIED);
291     DMLERR_TO_STR(DMLERR_SYS_ERROR);
292     DMLERR_TO_STR(DMLERR_UNADVACKTIMEOUT);
293     DMLERR_TO_STR(DMLERR_UNFOUND_QUEUE_ID);
294     default:
295         return "Unknown DML Error";
296     }
297 }
298 
299 /* Helper Function to Transfer DdeGetLastError into a String */
300 static const char * GetDdeLastErrorStr(DWORD instance)
301 {
302     UINT err = DdeGetLastError(instance);
303 
304     return GetStringFromError(err);
305 }
306 
307 /* Execute a Dde Command and return the error & result */
308 /* Note: Progman DDE always returns a pointer to 0x00000001 on a successful result */
309 static void DdeExecuteCommand(DWORD instance, HCONV hConv, const char *strCmd, HDDEDATA *hData, UINT *err, int testParams)
310 {
311     HDDEDATA command;
312 
313     command = DdeCreateDataHandle(instance, (LPBYTE) strCmd, strlen(strCmd)+1, 0, 0L, 0, 0);
314     ok (command != NULL, "DdeCreateDataHandle Error %s.%s\n",
315         GetDdeLastErrorStr(instance), GetStringFromTestParams(testParams));
316     *hData = DdeClientTransaction((void *) command,
317                                   -1,
318                                   hConv,
319                                   0,
320                                   0,
321                                   XTYP_EXECUTE,
322                                   MS_TIMEOUT_VAL,
323                                   NULL);
324 
325     /* hData is technically a pointer, but for Program Manager,
326      * it is NULL (error) or 1 (success)
327      * TODO: Check other versions of Windows to verify 1 is returned.
328      * While it is unlikely that anyone is actually testing that the result is 1
329      * if all versions of windows return 1, Wine should also.
330      */
331     if (*hData == NULL)
332     {
333         *err = DdeGetLastError(instance);
334     }
335     else
336     {
337         *err = DMLERR_NO_ERROR;
338         todo_wine
339         {
340             ok(*hData == (HDDEDATA) 1, "Expected HDDEDATA Handle == 1, actually %p.%s\n",
341                *hData, GetStringFromTestParams(testParams));
342         }
343     }
344     DdeFreeDataHandle(command);
345 }
346 
347 /*
348  * Check if Window is onscreen with the appropriate name.
349  *
350  * Windows are not created synchronously.  So we do not know
351  * when and if the window will be created/shown on screen.
352  * This function implements a polling mechanism to determine
353  * creation.
354  * A more complicated method would be to use SetWindowsHookEx.
355  * Since polling worked fine in my testing, no reason to implement
356  * the other.  Comments about other methods of determining when
357  * window creation happened were not encouraging (not including
358  * SetWindowsHookEx).
359  */
360 static HWND CheckWindowCreated(const char *winName, BOOL closeWindow, int testParams)
361 {
362     HWND window = NULL;
363     int i;
364 
365     /* Poll for Window Creation */
366     for (i = 0; window == NULL && i < PDDE_POLL_NUM; i++)
367     {
368         Sleep(PDDE_POLL_TIME);
369         /* Specify the window class name to make sure what we find is really an
370          * Explorer window. Explorer used two different window classes so try
371          * both.
372          */
373         window = FindWindowA("ExplorerWClass", winName);
374         if (!window)
375             window = FindWindowA("CabinetWClass", winName);
376     }
377     ok (window != NULL, "Window \"%s\" was not created in %i seconds - assumed failure.%s\n",
378         winName, PDDE_POLL_NUM*PDDE_POLL_TIME/1000, GetStringFromTestParams(testParams));
379 
380     /* Close Window as desired. */
381     if (window != NULL && closeWindow)
382     {
383         SendMessageA(window, WM_SYSCOMMAND, SC_CLOSE, 0);
384         window = NULL;
385     }
386     return window;
387 }
388 
389 /* Check for Existence (or non-existence) of a file or group
390  *   When testing for existence of a group, groupName is not needed
391  */
392 static void CheckFileExistsInProgramGroups(const char *nameToCheck, BOOL shouldExist, BOOL isGroup,
393                                            const char *groupName, int testParams)
394 {
395     char path[MAX_PATH];
396     DWORD attributes;
397     int len;
398 
399     lstrcpyA(path, ProgramsDir);
400 
401     len = strlen(path) + strlen(nameToCheck)+1;
402     if (groupName != NULL)
403     {
404         len += strlen(groupName)+1;
405     }
406     ok (len <= MAX_PATH, "Path Too Long.%s\n", GetStringFromTestParams(testParams));
407     if (len <= MAX_PATH)
408     {
409         if (groupName != NULL)
410         {
411             strcat(path, "\\");
412             strcat(path, groupName);
413         }
414         strcat(path, "\\");
415         strcat(path, nameToCheck);
416         attributes = GetFileAttributesA(path);
417         if (!shouldExist)
418         {
419             ok (attributes == INVALID_FILE_ATTRIBUTES , "File exists and shouldn't %s.%s\n",
420                 path, GetStringFromTestParams(testParams));
421         } else {
422             if (attributes == INVALID_FILE_ATTRIBUTES)
423             {
424                 ok (FALSE, "Created File %s doesn't exist.%s\n", path, GetStringFromTestParams(testParams));
425             } else if (isGroup) {
426                 ok (attributes & FILE_ATTRIBUTE_DIRECTORY, "%s is not a folder (attr=%x).%s\n",
427                     path, attributes, GetStringFromTestParams(testParams));
428             } else {
429                 ok (attributes & FILE_ATTRIBUTE_ARCHIVE, "Created File %s has wrong attributes (%x).%s\n",
430                     path, attributes, GetStringFromTestParams(testParams));
431             }
432         }
433     }
434 }
435 
436 /* Create Group Test.
437  *   command and expected_result.
438  *   if expected_result is DMLERR_NO_ERROR, test
439  *        1. group was created
440  *        2. window is open
441  */
442 static void CreateGroupTest(DWORD instance, HCONV hConv, const char *command, UINT expected_result,
443                             const char *groupName, const char *windowTitle, int testParams)
444 {
445     HDDEDATA hData;
446     UINT error;
447 
448     /* Execute Command & Check Result */
449     DdeExecuteCommand(instance, hConv, command, &hData, &error, testParams);
450     todo_wine
451     {
452         ok (expected_result == error, "CreateGroup %s: Expected Error %s, received %s.%s\n",
453             groupName, GetStringFromError(expected_result), GetStringFromError(error),
454             GetStringFromTestParams(testParams));
455     }
456 
457     /* No Error */
458     if (error == DMLERR_NO_ERROR)
459     {
460 
461         /* Check if Group Now Exists */
462         CheckFileExistsInProgramGroups(groupName, TRUE, TRUE, NULL, testParams);
463         /* Check if Window is Open (polling) */
464         CheckWindowCreated(windowTitle, TRUE, testParams);
465     }
466 }
467 
468 /* Show Group Test.
469  *   DDE command, expected_result, and the group name to check for existence
470  *   if expected_result is DMLERR_NO_ERROR, test
471  *        1. window is open
472  */
473 static HWND ShowGroupTest(DWORD instance, HCONV hConv, const char *command, UINT expected_result,
474                           const char *groupName, const char *windowTitle, BOOL closeAfterShowing, int testParams)
475 {
476     HDDEDATA hData;
477     UINT error;
478     HWND hwnd = 0;
479 
480     DdeExecuteCommand(instance, hConv, command, &hData, &error, testParams);
481 /* todo_wine...  Is expected to fail, wine stubbed functions DO fail */
482 /* TODO REMOVE THIS CODE!!! */
483     todo_wine_if (expected_result != DMLERR_NOTPROCESSED)
484         ok (expected_result == error, "ShowGroup %s: Expected Error %s, received %s.%s\n",
485             groupName, GetStringFromError(expected_result), GetStringFromError(error),
486             GetStringFromTestParams(testParams));
487 
488     if (error == DMLERR_NO_ERROR)
489     {
490         /* Check if Window is Open (polling) */
491         hwnd = CheckWindowCreated(windowTitle, closeAfterShowing, testParams);
492     }
493     return hwnd;
494 }
495 
496 /* Delete Group Test.
497  *   DDE command, expected_result, and the group name to check for existence
498  *   if expected_result is DMLERR_NO_ERROR, test
499  *        1. group does not exist
500  */
501 static void DeleteGroupTest(DWORD instance, HCONV hConv, const char *command, UINT expected_result,
502                             const char *groupName, int testParams)
503 {
504     HDDEDATA hData;
505     UINT error;
506 
507     DdeExecuteCommand(instance, hConv, command, &hData, &error, testParams);
508     todo_wine
509     {
510         ok (expected_result == error, "DeleteGroup %s: Expected Error %s, received %s.%s\n",
511             groupName, GetStringFromError(expected_result), GetStringFromError(error),
512             GetStringFromTestParams(testParams));
513     }
514 
515     if (error == DMLERR_NO_ERROR)
516     {
517         /* Check that Group does not exist */
518         CheckFileExistsInProgramGroups(groupName, FALSE, TRUE, NULL, testParams);
519     }
520 }
521 
522 /* Add Item Test
523  *   DDE command, expected result, and group and file name where it should exist.
524  *   checks to make sure error code matches expected error code
525  *   checks to make sure item exists if successful
526  */
527 static void AddItemTest(DWORD instance, HCONV hConv, const char *command, UINT expected_result,
528                         const char *fileName, const char *groupName, int testParams)
529 {
530     HDDEDATA hData;
531     UINT error;
532 
533     DdeExecuteCommand(instance, hConv, command, &hData, &error, testParams);
534     todo_wine
535     {
536         ok (expected_result == error, "AddItem %s: Expected Error %s, received %s.%s\n",
537             fileName, GetStringFromError(expected_result), GetStringFromError(error),
538             GetStringFromTestParams(testParams));
539     }
540 
541     if (error == DMLERR_NO_ERROR)
542     {
543         /* Check that File exists */
544         CheckFileExistsInProgramGroups(fileName, TRUE, FALSE, groupName, testParams);
545     }
546 }
547 
548 /* Delete Item Test.
549  *   DDE command, expected result, and group and file name where it should exist.
550  *   checks to make sure error code matches expected error code
551  *   checks to make sure item does not exist if successful
552  */
553 static void DeleteItemTest(DWORD instance, HCONV hConv, const char *command, UINT expected_result,
554                            const char *fileName, const char *groupName, int testParams)
555 {
556     HDDEDATA hData;
557     UINT error;
558 
559     DdeExecuteCommand(instance, hConv, command, &hData, &error, testParams);
560     todo_wine
561     {
562         ok (expected_result == error, "DeleteItem %s: Expected Error %s, received %s.%s\n",
563             fileName, GetStringFromError(expected_result), GetStringFromError(error),
564             GetStringFromTestParams(testParams));
565     }
566 
567     if (error == DMLERR_NO_ERROR)
568     {
569         /* Check that File does not exist */
570         CheckFileExistsInProgramGroups(fileName, FALSE, FALSE, groupName, testParams);
571     }
572 }
573 
574 /* Compound Command Test.
575  *   not really generic, assumes command of the form:
576  *          [CreateGroup ...][AddItem ...][AddItem ...]
577  *   All samples I've seen using Compound were of this form (CreateGroup,
578  *   AddItems) so this covers minimum expected functionality.
579  */
580 static HWND CompoundCommandTest(DWORD instance, HCONV hConv, const char *command, UINT expected_result,
581                                 const char *groupName, const char *windowTitle, const char *fileName1,
582                                 const char *fileName2, int testParams)
583 {
584     HDDEDATA hData;
585     UINT error;
586     HWND hwnd = 0;
587 
588     DdeExecuteCommand(instance, hConv, command, &hData, &error, testParams);
589     todo_wine
590     {
591         ok (expected_result == error, "Compound String %s: Expected Error %s, received %s.%s\n",
592             command, GetStringFromError(expected_result), GetStringFromError(error),
593             GetStringFromTestParams(testParams));
594     }
595 
596     if (error == DMLERR_NO_ERROR)
597     {
598         /* Check that File exists */
599         CheckFileExistsInProgramGroups(groupName, TRUE, TRUE, NULL, testParams);
600         hwnd = CheckWindowCreated(windowTitle, FALSE, testParams);
601         CheckFileExistsInProgramGroups(fileName1, TRUE, FALSE, groupName, testParams);
602         CheckFileExistsInProgramGroups(fileName2, TRUE, FALSE, groupName, testParams);
603     }
604     return hwnd;
605 }
606 
607 static void CreateAddItemText(char *itemtext, const char *cmdline, const char *name)
608 {
609     lstrcpyA(itemtext, "[AddItem(");
610     lstrcatA(itemtext, cmdline);
611     lstrcatA(itemtext, ",");
612     lstrcatA(itemtext, name);
613     lstrcatA(itemtext, ")]");
614 }
615 
616 /* 1st set of tests */
617 static int DdeTestProgman(DWORD instance, HCONV hConv)
618 {
619     HDDEDATA hData;
620     UINT error;
621     int testnum;
622     char temppath[MAX_PATH];
623     char f1g1[MAX_PATH], f2g1[MAX_PATH], f3g1[MAX_PATH], f1g3[MAX_PATH], f2g3[MAX_PATH];
624     char itemtext[MAX_PATH + 20];
625     char comptext[2 * (MAX_PATH + 20) + 21];
626     HWND hwnd;
627 
628     testnum = 1;
629     /* Invalid Command */
630     DdeExecuteCommand(instance, hConv, "[InvalidCommand()]", &hData, &error, DDE_TEST_MISC|testnum++);
631     ok (error == DMLERR_NOTPROCESSED, "InvalidCommand(), expected error %s, received %s.\n",
632         GetStringFromError(DMLERR_NOTPROCESSED), GetStringFromError(error));
633 
634     /* On Vista+ the files have to exist when adding a link */
635     GetTempPathA(MAX_PATH, temppath);
636     GetTempFileNameA(temppath, "dde", 0, f1g1);
637     GetTempFileNameA(temppath, "dde", 0, f2g1);
638     GetTempFileNameA(temppath, "dde", 0, f3g1);
639     GetTempFileNameA(temppath, "dde", 0, f1g3);
640     GetTempFileNameA(temppath, "dde", 0, f2g3);
641 
642     /* CreateGroup Tests (including AddItem, DeleteItem) */
643     CreateGroupTest(instance, hConv, "[CreateGroup(Group1)]", DMLERR_NO_ERROR, "Group1", Group1Title, DDE_TEST_CREATEGROUP|testnum++);
644     CreateAddItemText(itemtext, f1g1, "f1g1Name");
645     AddItemTest(instance, hConv, itemtext, DMLERR_NO_ERROR, "f1g1Name.lnk", "Group1", DDE_TEST_ADDITEM|testnum++);
646     CreateAddItemText(itemtext, f2g1, "f2g1Name");
647     AddItemTest(instance, hConv, itemtext, DMLERR_NO_ERROR, "f2g1Name.lnk", "Group1", DDE_TEST_ADDITEM|testnum++);
648     DeleteItemTest(instance, hConv, "[DeleteItem(f2g1Name)]", DMLERR_NO_ERROR, "f2g1Name.lnk", "Group1", DDE_TEST_DELETEITEM|testnum++);
649     CreateAddItemText(itemtext, f3g1, "f3g1Name");
650     AddItemTest(instance, hConv, itemtext, DMLERR_NO_ERROR, "f3g1Name.lnk", "Group1", DDE_TEST_ADDITEM|testnum++);
651     CreateGroupTest(instance, hConv, "[CreateGroup(Group2)]", DMLERR_NO_ERROR, "Group2", Group2Title, DDE_TEST_CREATEGROUP|testnum++);
652     /* Create Group that already exists - same instance */
653     CreateGroupTest(instance, hConv, "[CreateGroup(Group1)]", DMLERR_NO_ERROR, "Group1", Group1Title, DDE_TEST_CREATEGROUP|testnum++);
654 
655     /* ShowGroup Tests */
656     ShowGroupTest(instance, hConv, "[ShowGroup(Group1)]", DMLERR_NOTPROCESSED, "Group1", Group1Title, TRUE, DDE_TEST_SHOWGROUP|testnum++);
657     DeleteItemTest(instance, hConv, "[DeleteItem(f3g1Name)]", DMLERR_NO_ERROR, "f3g1Name.lnk", "Group1", DDE_TEST_DELETEITEM|testnum++);
658     ShowGroupTest(instance, hConv, "[ShowGroup(Startup,0)]", DMLERR_NO_ERROR, "Startup", StartupTitle, TRUE, DDE_TEST_SHOWGROUP|testnum++);
659     hwnd = ShowGroupTest(instance, hConv, "[ShowGroup(Group1,0)]", DMLERR_NO_ERROR, "Group1", Group1Title, FALSE, DDE_TEST_SHOWGROUP|testnum++);
660 
661     /* DeleteGroup Test - Note that Window is Open for this test */
662     DeleteGroupTest(instance, hConv, "[DeleteGroup(Group1)]", DMLERR_NO_ERROR, "Group1", DDE_TEST_DELETEGROUP|testnum++);
663     if (hwnd) SendMessageA(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
664 
665     /* Compound Execute String Command */
666     lstrcpyA(comptext, "[CreateGroup(Group3)]");
667     CreateAddItemText(itemtext, f1g3, "f1g3Name");
668     lstrcatA(comptext, itemtext);
669     CreateAddItemText(itemtext, f2g3, "f2g3Name");
670     lstrcatA(comptext, itemtext);
671     hwnd = CompoundCommandTest(instance, hConv, comptext, DMLERR_NO_ERROR, "Group3", Group3Title, "f1g3Name.lnk", "f2g3Name.lnk", DDE_TEST_COMPOUND|testnum++);
672 
673     DeleteGroupTest(instance, hConv, "[DeleteGroup(Group3)]", DMLERR_NO_ERROR, "Group3", DDE_TEST_DELETEGROUP|testnum++);
674     if (hwnd) SendMessageA(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
675 
676     /* Full Parameters of Add Item */
677     /* AddItem(CmdLine[,Name[,IconPath[,IconIndex[,xPos,yPos[,DefDir[,HotKey[,fMinimize[fSeparateSpace]]]]]]]) */
678 
679     DeleteFileA(f1g1);
680     DeleteFileA(f2g1);
681     DeleteFileA(f3g1);
682     DeleteFileA(f1g3);
683     DeleteFileA(f2g3);
684 
685     return testnum;
686 }
687 
688 /* 2nd set of tests - 2nd connection */
689 static void DdeTestProgman2(DWORD instance, HCONV hConv, int testnum)
690 {
691     /* Create Group that already exists on a separate connection */
692     CreateGroupTest(instance, hConv, "[CreateGroup(Group2)]", DMLERR_NO_ERROR, "Group2", Group2Title, DDE_TEST_CREATEGROUP|testnum++);
693     DeleteGroupTest(instance, hConv, "[DeleteGroup(Group2)]", DMLERR_NO_ERROR, "Group2", DDE_TEST_DELETEGROUP|testnum++);
694 }
695 
696 START_TEST(progman_dde)
697 {
698     DWORD instance = 0;
699     UINT err;
700     HSZ hszProgman;
701     HCONV hConv;
702     int testnum;
703 
704     init_function_pointers();
705     init_strings();
706 
707     /* Initialize DDE Instance */
708     err = DdeInitializeA(&instance, DdeCallback, APPCMD_CLIENTONLY, 0);
709     ok (err == DMLERR_NO_ERROR, "DdeInitialize Error %s\n", GetStringFromError(err));
710 
711     /* Create Connection */
712     hszProgman = DdeCreateStringHandleA(instance, "PROGMAN", CP_WINANSI);
713     ok (hszProgman != NULL, "DdeCreateStringHandle Error %s\n", GetDdeLastErrorStr(instance));
714     hConv = DdeConnect(instance, hszProgman, hszProgman, NULL);
715     ok (DdeFreeStringHandle(instance, hszProgman), "DdeFreeStringHandle failure\n");
716     /* Seeing failures on early versions of Windows Connecting to progman, exit if connection fails */
717     if (hConv == NULL)
718     {
719         ok (DdeUninitialize(instance), "DdeUninitialize failed\n");
720         return;
721     }
722 
723     /* Run Tests */
724     testnum = DdeTestProgman(instance, hConv);
725 
726     /* Cleanup & Exit */
727     ok (DdeDisconnect(hConv), "DdeDisonnect Error %s\n", GetDdeLastErrorStr(instance));
728     ok (DdeUninitialize(instance), "DdeUninitialize failed\n");
729 
730     /* 2nd Instance (Followup Tests) */
731     /* Initialize DDE Instance */
732     instance = 0;
733     err = DdeInitializeA(&instance, DdeCallback, APPCMD_CLIENTONLY, 0);
734     ok (err == DMLERR_NO_ERROR, "DdeInitialize Error %s\n", GetStringFromError(err));
735 
736     /* Create Connection */
737     hszProgman = DdeCreateStringHandleA(instance, "PROGMAN", CP_WINANSI);
738     ok (hszProgman != NULL, "DdeCreateStringHandle Error %s\n", GetDdeLastErrorStr(instance));
739     hConv = DdeConnect(instance, hszProgman, hszProgman, NULL);
740     ok (hConv != NULL, "DdeConnect Error %s\n", GetDdeLastErrorStr(instance));
741     ok (DdeFreeStringHandle(instance, hszProgman), "DdeFreeStringHandle failure\n");
742 
743     /* Run Tests */
744     DdeTestProgman2(instance, hConv, testnum);
745 
746     /* Cleanup & Exit */
747     ok (DdeDisconnect(hConv), "DdeDisonnect Error %s\n", GetDdeLastErrorStr(instance));
748     ok (DdeUninitialize(instance), "DdeUninitialize failed\n");
749 }
750