xref: /reactos/dll/cpl/appwiz/createlink.c (revision cce399e7)
1 /*
2  * PROJECT:         ReactOS Software Control Panel
3  * FILE:            dll/cpl/appwiz/createlink.c
4  * PURPOSE:         ReactOS Software Control Panel
5  * PROGRAMMER:      Gero Kuehn (reactos.filter@gkware.com)
6  *                  Dmitry Chapyshev (lentind@yandex.ru)
7  *                  Johannes Anderwald
8  *                  Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
9  * UPDATE HISTORY:
10  *      06-17-2004  Created
11  */
12 
13 #include "appwiz.h"
14 #include <commctrl.h>
15 #include <shellapi.h>
16 #include <strsafe.h>
17 #include <shlwapi_undoc.h> // for PathFindOnPathExW
18 
19 BOOL
20 IsShortcut(HKEY hKey)
21 {
22     WCHAR Value[10];
23     DWORD Size;
24     DWORD Type;
25 
26     Size = sizeof(Value);
27     if (RegQueryValueExW(hKey, L"IsShortcut", NULL, &Type, (LPBYTE)Value, &Size) != ERROR_SUCCESS)
28         return FALSE;
29 
30     if (Type != REG_SZ)
31         return FALSE;
32 
33     return (wcsicmp(Value, L"yes") == 0);
34 }
35 
36 BOOL
37 IsExtensionAShortcut(LPWSTR lpExtension)
38 {
39     HKEY hKey;
40     WCHAR Buffer[100];
41     DWORD Size;
42     DWORD Type;
43 
44     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, lpExtension, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
45         return FALSE;
46 
47     if (IsShortcut(hKey))
48     {
49         RegCloseKey(hKey);
50         return TRUE;
51     }
52 
53     Size = sizeof(Buffer);
54     if (RegQueryValueEx(hKey, NULL, NULL, &Type, (LPBYTE)Buffer, &Size) != ERROR_SUCCESS || Type != REG_SZ)
55     {
56         RegCloseKey(hKey);
57         return FALSE;
58     }
59 
60     RegCloseKey(hKey);
61 
62     if (RegOpenKeyExW(HKEY_CLASSES_ROOT, Buffer, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
63         return FALSE;
64 
65     if (IsShortcut(hKey))
66     {
67         RegCloseKey(hKey);
68         return TRUE;
69     }
70 
71     RegCloseKey(hKey);
72     return FALSE;
73 }
74 
75 BOOL
76 CreateShortcut(PCREATE_LINK_CONTEXT pContext)
77 {
78     IShellLinkW *pShellLink, *pSourceShellLink;
79     IPersistFile *pPersistFile;
80     HRESULT hr;
81     WCHAR Path[MAX_PATH];
82     LPWSTR lpExtension;
83 
84     /* get the extension */
85     lpExtension = PathFindExtensionW(pContext->szTarget);
86 
87     if (IsExtensionAShortcut(lpExtension))
88     {
89         hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_ALL, &IID_IShellLinkW, (void**)&pSourceShellLink);
90 
91         if (FAILED(hr))
92             return FALSE;
93 
94         hr = IUnknown_QueryInterface(pSourceShellLink, &IID_IPersistFile, (void**)&pPersistFile);
95         if (FAILED(hr))
96         {
97             IUnknown_Release(pSourceShellLink);
98             return FALSE;
99         }
100 
101         hr = pPersistFile->lpVtbl->Load(pPersistFile, (LPCOLESTR)pContext->szTarget, STGM_READ);
102         IUnknown_Release(pPersistFile);
103 
104         if (FAILED(hr))
105         {
106             IUnknown_Release(pSourceShellLink);
107             return FALSE;
108         }
109 
110         hr = IShellLinkW_GetPath(pSourceShellLink, Path, _countof(Path), NULL, 0);
111         IUnknown_Release(pSourceShellLink);
112 
113         if (FAILED(hr))
114         {
115             return FALSE;
116         }
117     }
118     else
119     {
120         StringCchCopyW(Path, _countof(Path), pContext->szTarget);
121     }
122 
123     hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_ALL,
124                           &IID_IShellLinkW, (void**)&pShellLink);
125 
126     if (hr != S_OK)
127         return FALSE;
128 
129     pShellLink->lpVtbl->SetPath(pShellLink, Path);
130     if (pContext->szArguments[0])
131         pShellLink->lpVtbl->SetArguments(pShellLink, pContext->szArguments);
132     pShellLink->lpVtbl->SetDescription(pShellLink, pContext->szDescription);
133     pShellLink->lpVtbl->SetWorkingDirectory(pShellLink, pContext->szWorkingDirectory);
134 
135     hr = IUnknown_QueryInterface(pShellLink, &IID_IPersistFile, (void**)&pPersistFile);
136     if (hr != S_OK)
137     {
138         IUnknown_Release(pShellLink);
139         return FALSE;
140     }
141 
142     hr = pPersistFile->lpVtbl->Save(pPersistFile, pContext->szLinkName, TRUE);
143     IUnknown_Release(pPersistFile);
144     IUnknown_Release(pShellLink);
145     return (hr == S_OK);
146 }
147 
148 BOOL
149 CreateInternetShortcut(PCREATE_LINK_CONTEXT pContext)
150 {
151     IUniformResourceLocatorW *pURL = NULL;
152     IPersistFile *pPersistFile = NULL;
153     HRESULT hr;
154     WCHAR szPath[MAX_PATH];
155     GetFullPathNameW(pContext->szLinkName, _countof(szPath), szPath, NULL);
156 
157     hr = CoCreateInstance(&CLSID_InternetShortcut, NULL, CLSCTX_ALL,
158                           &IID_IUniformResourceLocatorW, (void **)&pURL);
159     if (FAILED(hr))
160         return FALSE;
161 
162     hr = IUnknown_QueryInterface(pURL, &IID_IPersistFile, (void **)&pPersistFile);
163     if (FAILED(hr))
164     {
165         IUnknown_Release(pURL);
166         return FALSE;
167     }
168 
169     pURL->lpVtbl->SetURL(pURL, pContext->szTarget, 0);
170 
171     hr = pPersistFile->lpVtbl->Save(pPersistFile, szPath, TRUE);
172 
173     IUnknown_Release(pPersistFile);
174     IUnknown_Release(pURL);
175 
176     return SUCCEEDED(hr);
177 }
178 
179 BOOL IsInternetLocation(LPCWSTR pszLocation)
180 {
181     return (PathIsURLW(pszLocation) || wcsstr(pszLocation, L"www.") == pszLocation);
182 }
183 
184 /* Remove all invalid characters from the name */
185 void
186 DoConvertNameForFileSystem(LPWSTR szName)
187 {
188     LPWSTR pch1, pch2;
189     for (pch1 = pch2 = szName; *pch1; ++pch1)
190     {
191         if (wcschr(L"\\/:*?\"<>|", *pch1) != NULL)
192         {
193             /* *pch1 is an invalid character */
194             continue;
195         }
196         *pch2 = *pch1;
197         ++pch2;
198     }
199     *pch2 = 0;
200 }
201 
202 BOOL
203 DoValidateShortcutName(PCREATE_LINK_CONTEXT pContext)
204 {
205     SIZE_T cch;
206     LPCWSTR pch, pszName = pContext->szDescription;
207 
208     if (!pszName || !pszName[0])
209         return FALSE;
210 
211     cch = wcslen(pContext->szOrigin) + wcslen(pszName) + 1;
212     if (cch >= MAX_PATH)
213         return FALSE;
214 
215     pch = pszName;
216     for (pch = pszName; *pch; ++pch)
217     {
218         if (wcschr(L"\\/:*?\"<>|", *pch) != NULL)
219         {
220             /* *pch is an invalid character */
221             return FALSE;
222         }
223     }
224 
225     return TRUE;
226 }
227 
228 INT_PTR
229 CALLBACK
230 WelcomeDlgProc(HWND hwndDlg,
231                UINT uMsg,
232                WPARAM wParam,
233                LPARAM lParam)
234 {
235     LPPROPSHEETPAGEW ppsp;
236     PCREATE_LINK_CONTEXT pContext;
237     LPPSHNOTIFY lppsn;
238     WCHAR szPath[MAX_PATH * 2];
239     WCHAR szDesc[100];
240     WCHAR szTitle[100];
241     BROWSEINFOW brws;
242     LPITEMIDLIST pidllist;
243     SHFILEINFOW FileInfo;
244 
245     switch(uMsg)
246     {
247         case WM_INITDIALOG:
248             ppsp = (LPPROPSHEETPAGEW)lParam;
249             pContext = (PCREATE_LINK_CONTEXT) ppsp->lParam;
250             SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pContext);
251             PropSheet_SetWizButtons(GetParent(hwndDlg), 0);
252             SHAutoComplete(GetDlgItem(hwndDlg, IDC_SHORTCUT_LOCATION), SHACF_DEFAULT);
253             break;
254         case WM_COMMAND:
255         {
256             switch(HIWORD(wParam))
257             {
258                 case EN_CHANGE:
259                     if (SendDlgItemMessage(hwndDlg, IDC_SHORTCUT_LOCATION, WM_GETTEXTLENGTH, 0, 0))
260                     {
261                         PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_NEXT);
262                     }
263                     else
264                     {
265                         PropSheet_SetWizButtons(GetParent(hwndDlg), 0);
266                     }
267                     break;
268             }
269             switch(LOWORD(wParam))
270             {
271                 case IDC_SHORTCUT_BROWSE:
272                     LoadStringW(hApplet, IDS_BROWSE_FOR_TARGET, szTitle, _countof(szTitle));
273                     ZeroMemory(&brws, sizeof(brws));
274                     brws.hwndOwner = hwndDlg;
275                     brws.pidlRoot = NULL;
276                     brws.pszDisplayName = szPath;
277                     brws.lpszTitle = szTitle;
278                     brws.ulFlags = BIF_BROWSEINCLUDEFILES | BIF_RETURNONLYFSDIRS |
279                                    BIF_NEWDIALOGSTYLE | BIF_SHAREABLE;
280                     brws.lpfn = NULL;
281                     pidllist = SHBrowseForFolderW(&brws);
282                     if (!pidllist)
283                         break;
284 
285                     if (SHGetPathFromIDListW(pidllist, szPath))
286                     {
287                         SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION, szPath);
288                         SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_LOCATION, WM_SETFOCUS, 0, 0);
289                         SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_LOCATION, EM_SETSEL, 0, -1);
290                     }
291                     else
292                     {
293                         SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION, NULL);
294                     }
295 
296                     /* Free memory, if possible */
297                     CoTaskMemFree(pidllist);
298                     break;
299             }
300             break;
301         case WM_NOTIFY:
302             lppsn  = (LPPSHNOTIFY) lParam;
303             pContext = (PCREATE_LINK_CONTEXT)GetWindowLongPtr(hwndDlg, DWLP_USER);
304             if (lppsn->hdr.code == PSN_SETACTIVE)
305             {
306                 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION, pContext->szTarget);
307             }
308             else if (lppsn->hdr.code == PSN_WIZNEXT)
309             {
310                 GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION, pContext->szTarget, _countof(pContext->szTarget));
311                 StrTrimW(pContext->szTarget, L" \t");
312                 ExpandEnvironmentStringsW(pContext->szTarget, szPath, _countof(szPath));
313 
314                 if (IsInternetLocation(szPath)) /* The internet location */
315                 {
316                     WCHAR szName[128];
317                     LoadStringW(hApplet, IDS_NEW_INTERNET_SHORTCUT, szName, _countof(szName));
318                     StringCchCopyW(pContext->szDescription, _countof(pContext->szDescription), szName);
319                     pContext->szWorkingDirectory[0] = 0;
320                     pContext->szArguments[0] = 0;
321                     return FALSE;
322                 }
323 
324                 /* Split and build args */
325                 LPWSTR pszArgs = PathGetArgsW(szPath);
326                 if (pszArgs && pszArgs > szPath)
327                 {
328                     PathRemoveArgsW(szPath);
329                     StringCchCopyW(pContext->szArguments, _countof(pContext->szArguments), pszArgs);
330                 }
331                 else
332                 {
333                     pContext->szArguments[0] = 0;
334                 }
335 
336                 /* Find the file */
337                 WCHAR szFound[MAX_PATH];
338                 StringCchCopyW(szFound, _countof(szFound), szPath);
339                 if (!PathFindOnPathExW(szFound, NULL, WHICH_DEFAULT) &&
340                     FindExecutableW(szPath, NULL, szFound) <= (HINSTANCE)(INT_PTR)32)
341                 {
342                     /* Not found */
343                     SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_LOCATION, EM_SETSEL, 0, -1);
344 
345                     LoadStringW(hApplet, IDS_CREATE_SHORTCUT, szDesc, _countof(szDesc));
346                     LoadStringW(hApplet, IDS_ERROR_NOT_FOUND, szPath, _countof(szPath));
347 
348                     WCHAR szError[MAX_PATH + 100];
349                     StringCchPrintfW(szError, _countof(szError), szPath, pContext->szTarget);
350                     MessageBoxW(hwndDlg, szError, szDesc, MB_ICONERROR);
351 
352                     /* Prevent the wizard to go next */
353                     SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, -1);
354                     return TRUE;
355                 }
356 
357                 /* Rebuild target */
358                 StringCchCopyW(pContext->szTarget, _countof(pContext->szTarget), szFound);
359 
360                 /* Get display name */
361                 FileInfo.szDisplayName[0] = 0;
362                 if (SHGetFileInfoW(szFound, 0, &FileInfo, sizeof(FileInfo), SHGFI_DISPLAYNAME))
363                     StringCchCopyW(pContext->szDescription, _countof(pContext->szDescription), FileInfo.szDisplayName);
364 
365                 /* Set working directory */
366                 StringCchCopyW(pContext->szWorkingDirectory, _countof(pContext->szWorkingDirectory), szFound);
367                 PathRemoveBackslashW(pContext->szWorkingDirectory);
368                 PathRemoveFileSpecW(pContext->szWorkingDirectory);
369                 PathRemoveBackslashW(pContext->szWorkingDirectory);
370 
371                 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
372             }
373             else if (lppsn->hdr.code == PSN_RESET && !lppsn->lParam)
374             {
375                 /* The user has clicked [Cancel] */
376                 DeleteFileW(pContext->szOldFile);
377                 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, pContext->szOldFile, NULL);
378             }
379             break;
380         }
381     }
382     return FALSE;
383 }
384 
385 INT_PTR
386 CALLBACK
387 FinishDlgProc(HWND hwndDlg,
388                UINT uMsg,
389                WPARAM wParam,
390                LPARAM lParam)
391 {
392     LPPROPSHEETPAGEW ppsp;
393     PCREATE_LINK_CONTEXT pContext;
394     LPPSHNOTIFY lppsn;
395     WCHAR szText[MAX_PATH];
396     WCHAR szMessage[128];
397 
398     switch(uMsg)
399     {
400         case WM_INITDIALOG:
401             ppsp = (LPPROPSHEETPAGEW)lParam;
402             pContext = (PCREATE_LINK_CONTEXT) ppsp->lParam;
403             SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pContext);
404             PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_BACK | PSWIZB_FINISH);
405             break;
406         case WM_COMMAND:
407             switch(HIWORD(wParam))
408             {
409                 case EN_CHANGE:
410                     if (SendDlgItemMessage(hwndDlg, IDC_SHORTCUT_NAME, WM_GETTEXTLENGTH, 0, 0))
411                     {
412                         GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_NAME, szText, _countof(szText));
413                         StrTrimW(szText, L" \t");
414                         if (szText[0])
415                             PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_BACK | PSWIZB_FINISH);
416                         else
417                             PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_BACK);
418                     }
419                     else
420                     {
421                         PropSheet_SetWizButtons(GetParent(hwndDlg), PSWIZB_BACK);
422                     }
423                     break;
424             }
425             break;
426         case WM_NOTIFY:
427             lppsn  = (LPPSHNOTIFY) lParam;
428             pContext = (PCREATE_LINK_CONTEXT) GetWindowLongPtr(hwndDlg, DWLP_USER);
429             if (lppsn->hdr.code == PSN_SETACTIVE)
430             {
431                 /* TODO: Use shell32!PathCleanupSpec instead of DoConvertNameForFileSystem */
432                 DoConvertNameForFileSystem(pContext->szDescription);
433                 SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_NAME, pContext->szDescription);
434                 SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_NAME, EM_SETSEL, 0, -1);
435                 SetFocus(GetDlgItem(hwndDlg, IDC_SHORTCUT_NAME));
436             }
437             else if (lppsn->hdr.code == PSN_WIZFINISH)
438             {
439                 GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_NAME, pContext->szDescription, _countof(pContext->szDescription));
440                 StrTrimW(pContext->szDescription, L" \t");
441 
442                 if (!DoValidateShortcutName(pContext))
443                 {
444                     SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_NAME, EM_SETSEL, 0, -1);
445 
446                     LoadStringW(hApplet, IDS_INVALID_NAME, szMessage, _countof(szMessage));
447                     MessageBoxW(hwndDlg, szMessage, NULL, MB_ICONERROR);
448 
449                     /* prevent the wizard to go next */
450                     SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, -1);
451                     return TRUE;
452                 }
453 
454                 /* if old shortcut file exists, then delete it now */
455                 DeleteFileW(pContext->szOldFile);
456                 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, pContext->szOldFile, NULL);
457 
458                 if (IsInternetLocation(pContext->szTarget))
459                 {
460                     /* internet */
461                     StringCchCopyW(pContext->szLinkName, _countof(pContext->szLinkName),
462                                    pContext->szOrigin);
463                     PathAppendW(pContext->szLinkName, pContext->szDescription);
464 
465                     /* change extension if any */
466                     PathRemoveExtensionW(pContext->szLinkName);
467                     StringCchCatW(pContext->szLinkName, _countof(pContext->szLinkName), L".url");
468 
469                     if (!CreateInternetShortcut(pContext))
470                     {
471                         LoadStringW(hApplet, IDS_CANTMAKEINETSHORTCUT, szMessage, _countof(szMessage));
472                         MessageBoxW(hwndDlg, szMessage, NULL, MB_ICONERROR);
473                     }
474                 }
475                 else
476                 {
477                     /* file */
478                     StringCchCopyW(pContext->szLinkName, _countof(pContext->szLinkName),
479                                    pContext->szOrigin);
480                     PathAppendW(pContext->szLinkName, pContext->szDescription);
481 
482                     /* change extension if any */
483                     PathRemoveExtensionW(pContext->szLinkName);
484                     StringCchCatW(pContext->szLinkName, _countof(pContext->szLinkName), L".lnk");
485 
486                     if (!CreateShortcut(pContext))
487                     {
488                         WCHAR szMessage[128];
489                         LoadStringW(hApplet, IDS_CANTMAKESHORTCUT, szMessage, _countof(szMessage));
490                         MessageBoxW(hwndDlg, szMessage, NULL, MB_ICONERROR);
491                     }
492                 }
493             }
494             else if (lppsn->hdr.code == PSN_RESET && !lppsn->lParam)
495             {
496                 /* The user has clicked [Cancel] */
497                 DeleteFileW(pContext->szOldFile);
498                 SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, pContext->szOldFile, NULL);
499             }
500             break;
501     }
502     return FALSE;
503 }
504 
505 static int CALLBACK
506 PropSheetProc(HWND hwndDlg, UINT uMsg, LPARAM lParam)
507 {
508     // NOTE: This callback is needed to set large icon correctly.
509     HICON hIcon;
510     switch (uMsg)
511     {
512         case PSCB_INITIALIZED:
513         {
514             hIcon = LoadIconW(hApplet, MAKEINTRESOURCEW(IDI_APPINETICO));
515             SendMessageW(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
516             break;
517         }
518     }
519     return 0;
520 }
521 
522 LONG CALLBACK
523 ShowCreateShortcutWizard(HWND hwndCPl, LPCWSTR szPath)
524 {
525     PROPSHEETHEADERW psh;
526     HPROPSHEETPAGE ahpsp[2];
527     PROPSHEETPAGE psp;
528     UINT nPages = 0;
529     UINT nLength;
530     PCREATE_LINK_CONTEXT pContext;
531     WCHAR szMessage[128];
532     LPWSTR pch;
533 
534     pContext = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pContext));
535     if (!pContext)
536     {
537         /* no memory */
538         LoadStringW(hApplet, IDS_NO_MEMORY, szMessage, _countof(szMessage));
539         MessageBoxW(hwndCPl, szMessage, NULL, MB_ICONERROR);
540         return FALSE;
541     }
542 
543     nLength = wcslen(szPath);
544     if (!nLength)
545     {
546         HeapFree(GetProcessHeap(), 0, pContext);
547 
548         /* no directory given */
549         LoadStringW(hApplet, IDS_NO_DIRECTORY, szMessage, _countof(szMessage));
550         MessageBoxW(hwndCPl, szMessage, NULL, MB_ICONERROR);
551         return FALSE;
552     }
553 
554     if (!PathFileExistsW(szPath))
555     {
556         HeapFree(GetProcessHeap(), 0, pContext);
557 
558         /* invalid path */
559         LoadStringW(hApplet, IDS_INVALID_PATH, szMessage, _countof(szMessage));
560         MessageBoxW(hwndCPl, szMessage, NULL, MB_ICONERROR);
561         return FALSE;
562     }
563 
564     /* build the pContext->szOrigin and pContext->szOldFile */
565     if (PathIsDirectoryW(szPath))
566     {
567         StringCchCopyW(pContext->szOrigin, _countof(pContext->szOrigin), szPath);
568         pContext->szOldFile[0] = 0;
569     }
570     else
571     {
572         StringCchCopyW(pContext->szOrigin, _countof(pContext->szOrigin), szPath);
573         pch = PathFindFileNameW(pContext->szOrigin);
574         if (pch && *pch)
575             *pch = 0;
576 
577         StringCchCopyW(pContext->szOldFile, _countof(pContext->szOldFile), szPath);
578 
579         pch = PathFindFileNameW(szPath);
580         if (pch && *pch)
581         {
582             /* build szDescription */
583             StringCchCopyW(pContext->szDescription, _countof(pContext->szDescription), pch);
584             *pch = 0;
585 
586             pch = PathFindExtensionW(pContext->szDescription);
587             *pch = 0;
588         }
589     }
590     PathAddBackslashW(pContext->szOrigin);
591 
592     /* Create the Welcome page */
593     psp.dwSize = sizeof(PROPSHEETPAGE);
594     psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
595     psp.hInstance = hApplet;
596     psp.pfnDlgProc = WelcomeDlgProc;
597     psp.pszTemplate = MAKEINTRESOURCEW(IDD_SHORTCUT_LOCATION);
598     psp.lParam = (LPARAM)pContext;
599     ahpsp[nPages++] = CreatePropertySheetPage(&psp);
600 
601     /* Create the Finish page */
602     psp.dwFlags = PSP_DEFAULT | PSP_HIDEHEADER;
603     psp.pfnDlgProc = FinishDlgProc;
604     psp.pszTemplate = MAKEINTRESOURCEW(IDD_SHORTCUT_FINISH);
605     ahpsp[nPages++] = CreatePropertySheetPage(&psp);
606 
607     /* Create the property sheet */
608     psh.dwSize = sizeof(PROPSHEETHEADER);
609     psh.dwFlags = PSH_WIZARD97 | PSH_WATERMARK | PSH_USEICONID | PSH_USECALLBACK;
610     psh.hInstance = hApplet;
611     psh.pszIcon = MAKEINTRESOURCEW(IDI_APPINETICO);
612     psh.hwndParent = NULL;
613     psh.nPages = nPages;
614     psh.nStartPage = 0;
615     psh.phpage = ahpsp;
616     psh.pszbmWatermark = MAKEINTRESOURCEW(IDB_SHORTCUT);
617     psh.pfnCallback = PropSheetProc;
618 
619     /* Display the wizard */
620     PropertySheet(&psh);
621     HeapFree(GetProcessHeap(), 0, pContext);
622     return TRUE;
623 }
624 
625 LONG
626 CALLBACK
627 NewLinkHereW(HWND hwndCPl, UINT uMsg, LPARAM lParam1, LPARAM lParam2)
628 {
629     InitCommonControls();
630     return ShowCreateShortcutWizard(hwndCPl, (LPWSTR)lParam1);
631 }
632 
633 LONG
634 CALLBACK
635 NewLinkHereA(HWND hwndCPl, UINT uMsg, LPARAM lParam1, LPARAM lParam2)
636 {
637     WCHAR szFile[MAX_PATH];
638 
639     if (MultiByteToWideChar(CP_ACP, 0, (LPSTR)lParam1, -1, szFile, _countof(szFile)))
640     {
641         InitCommonControls();
642         return ShowCreateShortcutWizard(hwndCPl, szFile);
643     }
644     return -1;
645 }
646