//
//  Browse.C
//
//  Copyright (C) Microsoft, 1994,1995 All Rights Reserved.
//
//  History:
//  ral 5/23/94 - First pass
//  3/20/95  [stevecat] - NT port & real clean up, unicode, etc.
//
//
#include "priv.h"
#include "appwiz.h"
#include "util.h"
#ifdef WINNT
#include <uastrfnc.h>
#endif

#ifdef WINNT
#ifndef DOWNLEVEL_PLATFORM
#include <tsappcmp.h>       // for TermsrvAppInstallMode
#endif // DOWNLEVEL_PLATFORM
#endif // WINNT

// Copied from shelldll\ole2dup.h
#define GUIDSTR_MAX (1+ 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12 + 1 + 1)

//
//  Initialize the browse property sheet.  Limit the size of the edit control.
//

void BrowseInitPropSheet(HWND hDlg, LPARAM lParam)
{
    LPWIZDATA lpwd = InitWizSheet(hDlg, lParam, 0);

    Edit_LimitText(GetDlgItem(hDlg, IDC_COMMAND), ARRAYSIZE(lpwd->szExeName)-1);

#ifndef DOWNLEVEL_PLATFORM
    if (FAILED(SHAutoComplete(GetDlgItem(hDlg, IDC_COMMAND), 0)))
    {
        TraceMsg(TF_WARNING, "%s", "WARNING: Create Shortcut wizard won't AutoComplete because: 1) bad registry, 2) bad OleInit, or 3) Out of memory.");
    }
#endif //DOWNLEVEL_PLATFORM
}

//
//  Sets the appropriate wizard buttons.  If there's any text in the
//  edit control then Next is enabled.  Otherwise, Next and Back are both
//  grey.
//
void SetBrowseButtons(LPWIZDATA lpwd)
{
    BOOL fIsText = GetWindowTextLength(GetDlgItem(lpwd->hwnd, IDC_COMMAND)) > 0;
    BOOL fIsSetup = (lpwd->dwFlags & WDFLAG_SETUPWIZ);
    int iBtns = fIsSetup ? PSWIZB_BACK : 0;

    if (fIsSetup)
    {
#ifndef DOWNLEVEL_PLATFORM
#ifdef WINNT       
        // Are we running Terminal Service? Is this user an Admin?
        if (IsTerminalServicesRunning() && IsUserAnAdmin())
        {
            lpwd->bTermSrvAndAdmin = TRUE;
            iBtns |= fIsText ? PSWIZB_NEXT : PSWIZB_DISABLEDFINISH;
        }
        else
#endif // WINNT
#endif // DOWNLEVEL_PLATFORM
            iBtns |= fIsText ? PSWIZB_FINISH : PSWIZB_DISABLEDFINISH;
    }
    else
    {
        if (fIsText)
        {
            iBtns |= PSWIZB_NEXT;
        }
    }
    PropSheet_SetWizButtons(GetParent(lpwd->hwnd), iBtns);
}


//
//  NOTES: 1) This function assumes that lpwd->hwnd has already been set to
//           the dialogs hwnd.  2) This function is called from NextPushed
//           if the application specified can not be found.
//
//  BrowseSetActive enables the next button and sets the focus to the edit
//  control by posting a POKEFOCUS message.
//

void BrowseSetActive(LPWIZDATA lpwd)
{
    //
    // NOTE: We re-use the szProgDesc string since it will always be reset
    //       when this page is activated.  Use it to construct a command line.
    //

    #define   szCmdLine lpwd->szProgDesc

    lstrcpy(szCmdLine, lpwd->szExeName);

    PathQuoteSpaces(szCmdLine);

    if (lpwd->szParams[0] != 0)
    {
        lstrcat(szCmdLine, TEXT(" "));
        lstrcat(szCmdLine, lpwd->szParams);
    }

    Edit_SetText(GetDlgItem(lpwd->hwnd, IDC_COMMAND), szCmdLine);

    if (lpwd->dwFlags & WDFLAG_SETUPWIZ)
    {
        int   iHaveHeader = IsTerminalServicesRunning() ? IDS_TSHAVESETUPPRG : IDS_HAVESETUPPRG;
        int   iHeader = szCmdLine[0] != 0 ? iHaveHeader : IDS_NOSETUPPRG;
        TCHAR szInstruct[MAX_PATH];

        LoadString(g_hinst, iHeader, szInstruct, ARRAYSIZE(szInstruct));

        Static_SetText(GetDlgItem(lpwd->hwnd, IDC_SETUPMSG), szInstruct);
    }

    SetBrowseButtons(lpwd);

    PostMessage(lpwd->hwnd, WMPRIV_POKEFOCUS, 0, 0);

    szCmdLine[0] = 0;            // Reset progdesc to empty string
    #undef szCmdLine
}


//
//  Returns TRUE if able to get properties for szExeName from PifMgr.  The
//  program properties will be read into lpwd->PropPrg.
//

BOOL ReadPifProps(LPWIZDATA lpwd)
{
    HANDLE hPifProp;
    LPTSTR lpszName = (lpwd->dwFlags & WDFLAG_EXPSZ) ? lpwd->szExpExeName : lpwd->szExeName;

    hPifProp = PifMgr_OpenProperties(lpszName, NULL, 0, OPENPROPS_INHIBITPIF);
    if (!hPifProp)
    {
        TraceMsg(TF_ERROR, "%s", "Unable to open properties for DOS exe.");
    }

    if (hPifProp == 0)
        return(FALSE);

    PifMgr_GetProperties(hPifProp, (LPSTR)GROUP_PRG, &(lpwd->PropPrg),
                         sizeof(lpwd->PropPrg), GETPROPS_NONE);

    PifMgr_CloseProperties(hPifProp, CLOSEPROPS_DISCARD);

    return(TRUE);
}


//
//  Returns TRUE if lpwd->szExeName points to a valid exe type.  It also sets
//  the appropriate flags, such as APPKNOWN and DOSAPP in the wizdata structure
//  if the exe is valid.
//

void DetermineExeType(LPWIZDATA lpwd)
{

    DWORD   dwExeType;
    LPTSTR  lpszName = (lpwd->dwFlags & WDFLAG_EXPSZ) ? lpwd->szExpExeName : lpwd->szExeName;

    lpwd->dwFlags &= ~(WDFLAG_APPKNOWN | WDFLAG_DOSAPP | WDFLAG_SINGLEAPP);

    dwExeType = (DWORD)SHGetFileInfo(lpszName, 0, NULL, 0, SHGFI_EXETYPE);

    if (LOWORD(dwExeType) != ('M' | ('Z' << 8)))
    {
        lpwd->dwFlags |= WDFLAG_APPKNOWN;

        if (lstrcmpi(PathFindExtension(lpszName), c_szPIF) == 0)
        {
            lpwd->dwFlags |= WDFLAG_DOSAPP;
        }
    }
    else
    {
        lpwd->dwFlags |= WDFLAG_DOSAPP;

        if (ReadPifProps(lpwd))
        {
            if ((lpwd->PropPrg.flPrgInit & PRGINIT_INFSETTINGS) ||
                ((lpwd->PropPrg.flPrgInit &
                     (PRGINIT_NOPIF | PRGINIT_DEFAULTPIF)) == 0))
            {
                lpwd->dwFlags |= WDFLAG_APPKNOWN;

                if (lpwd->PropPrg.flPrgInit & PRGINIT_REALMODE)
                {
                    lpwd->dwFlags |= WDFLAG_SINGLEAPP;
                }
            }
        }
    }
}


//
//  Removes the filename extension (if any) from the string.
//

void StripExt(LPTSTR lpsz)
{
    if(lpsz)
    {
        LPTSTR pExt = PathFindExtension(lpsz);

        if (*pExt)
            *pExt = 0;    // null out the "."
    }
}



//
//  Sets the working directory as appropriate for the file type.
//

void FindWorkingDir(LPWIZDATA lpwd)
{
    LPTSTR lpszName = (lpwd->dwFlags & WDFLAG_EXPSZ) ? lpwd->szExpExeName : lpwd->szExeName;
#ifdef WINNT
    TCHAR szWindir[ MAX_PATH ];
    DWORD dwLen;
#endif

    if (PathIsUNC(lpszName) || PathIsDirectory(lpszName))
    {
        lpwd->szWorkingDir[0] = 0;
    }
    else
    {
        lstrcpy(lpwd->szWorkingDir, lpszName);
        PathRemoveFileSpec(lpwd->szWorkingDir);
    }

#ifdef WINNT
    //
    // Okay, at this point we should have the absolute path for the
    // working directory of the link.  On NT, if the working dir happens to be for
    // something in the %Windir% directory (or a subdir of %windir%),
    // then store the path as %windir%\blah\blah\blah instead of as an
    // absolute path.  This will help with interoperability of shortcuts
    // across different machines, etc.  But only do this for shortcuts that
    // are already marked as having expandable env strings...
    //

    if (lpwd->dwFlags & WDFLAG_EXPSZ)
    {
        dwLen = ExpandEnvironmentStrings( TEXT("%windir%"),
                                          szWindir,
                                          ARRAYSIZE(szWindir)
                                         );
        if (dwLen &&
            dwLen < ARRAYSIZE(szWindir) &&
            lstrlen(szWindir) <= lstrlen(lpwd->szWorkingDir)
           )
        {
            //
            // we use dwLen-1 because dwLen includes the '\0' character
            //
            if (CompareString( LOCALE_SYSTEM_DEFAULT,
                               NORM_IGNORECASE,
                               szWindir, dwLen-1 ,
                               lpwd->szWorkingDir, dwLen-1
                              ) == 2)
            {
                TCHAR szWorkingDir[ MAX_PATH ];
                //
                // We should substitute the env variable for the
                // actual string here...
                //
                ualstrcpy( szWorkingDir, lpwd->szWorkingDir );
                ualstrcpy( lpwd->szWorkingDir, TEXT("%windir%") );

                // 8 == lstrlen("%windir%")
                ualstrcpy( lpwd->szWorkingDir + 12, szWorkingDir+dwLen-1 );

            }
        }
    }
#endif // winnt
}


#ifndef NO_NEW_SHORTCUT_HOOK

//
// Returns:
//    Hook result or error.
//
// S_OK:
//    *pnshhk is the INewShortcutHook of the object to use to save the new Shortcut.
//    szProgDesc[] and szExt[] are filled in.
//    szExeName[] may be translated.
// otherwise:
//    *pnshhk is NULL.
//    szProgDesc[] and szExt[] are empty strings.
//

HRESULT QueryNewLinkHandler(LPWIZDATA lpwd, LPCLSID pclsidHook)
{
   HRESULT   hr;
   IUnknown *punk;
   LPTSTR lpszName = (lpwd->dwFlags & WDFLAG_EXPSZ) ? lpwd->szExpExeName : lpwd->szExeName;

   lpwd->pnshhk = NULL;
#ifdef UNICODE
   lpwd->pnshhkA = NULL;
#endif

   *(lpwd->szProgDesc) = TEXT('\0');
   *(lpwd->szExt) = TEXT('\0');

   hr = CoCreateInstance(pclsidHook, NULL, CLSCTX_INPROC_SERVER, &IID_IUnknown, &punk);

   if (hr == S_OK)
   {
      INewShortcutHook *pnshhk;

      hr = punk->lpVtbl->QueryInterface(punk, &IID_INewShortcutHook, &pnshhk);

      if (hr == S_OK)
      {
         hr = pnshhk->lpVtbl->SetReferent(pnshhk, lpszName, lpwd->hwnd);

         if (hr == S_OK)
         {
            hr = pnshhk->lpVtbl->SetFolder(pnshhk, lpwd->lpszFolder);

            if (hr == S_OK)
            {
               hr = pnshhk->lpVtbl->GetName(pnshhk, lpwd->szProgDesc,
                                            ARRAYSIZE(lpwd->szProgDesc));

               if (hr == S_OK)
               {
                  hr = pnshhk->lpVtbl->GetExtension(pnshhk, lpwd->szExt,
                                                    ARRAYSIZE(lpwd->szExt));

                  if (hr == S_OK)
                     hr = pnshhk->lpVtbl->GetReferent(pnshhk, lpszName,
                                                      ARRAYSIZE(lpwd->szExeName));
               }
            }
         }

         if (hr == S_OK)
            lpwd->pnshhk = pnshhk;
         else
            pnshhk->lpVtbl->Release(pnshhk);
      }
#ifdef UNICODE
      else
      {
          INewShortcutHookA *pnshhkA;
          hr = punk->lpVtbl->QueryInterface(punk, &IID_INewShortcutHookA, &pnshhkA);

          if (hr == S_OK)
          {
             UINT   cFolderA = WideCharToMultiByte(CP_ACP,0,lpwd->lpszFolder,-1,NULL,0,0,0)+1;
             LPSTR  lpszFolderA = (LPSTR)LocalAlloc(LPTR,cFolderA*SIZEOF(CHAR));

             if (NULL == lpszFolderA)
             {
                 hr = E_OUTOFMEMORY;
             }
             else
             {
                 CHAR   szNameA[MAX_PATH];
                 CHAR   szProgDescA[MAX_PATH];
                 CHAR   szExtA[MAX_PATH];

                 WideCharToMultiByte(CP_ACP, 0,
                                     lpszName, -1,
                                     szNameA, ARRAYSIZE(szNameA),
                                     0, 0);

                 WideCharToMultiByte(CP_ACP, 0,
                                     lpwd->lpszFolder, -1,
                                     lpszFolderA, cFolderA,
                                     0, 0);

                 hr = pnshhkA->lpVtbl->SetReferent(pnshhkA, szNameA, lpwd->hwnd);

                 if (hr == S_OK)
                 {
                    hr = pnshhkA->lpVtbl->SetFolder(pnshhkA, lpszFolderA);

                    if (hr == S_OK)
                    {
                       hr = pnshhkA->lpVtbl->GetName(pnshhkA, szProgDescA,
                                                    ARRAYSIZE(szProgDescA));

                       if (hr == S_OK)
                       {
                          MultiByteToWideChar(CP_ACP, 0,
                                              szProgDescA, -1,
                                              lpwd->szProgDesc, ARRAYSIZE(lpwd->szProgDesc));

                          hr = pnshhkA->lpVtbl->GetExtension(pnshhkA, szExtA,
                                                            ARRAYSIZE(szExtA));

                          if (hr == S_OK)
                          {
                             MultiByteToWideChar(CP_ACP, 0,
                                                 szExtA, -1,
                                                 lpwd->szExt, ARRAYSIZE(lpwd->szExt));

                             hr = pnshhkA->lpVtbl->GetReferent(pnshhkA, szNameA,
                                                              ARRAYSIZE(szNameA));

                             MultiByteToWideChar(CP_ACP, 0,
                                                 szExtA, -1,
                                                 lpszName, ARRAYSIZE(lpwd->szExeName));
                          }
                       }
                    }
                 }

                 if (hr == S_OK)
                    lpwd->pnshhkA = pnshhkA;
                 else
                    pnshhkA->lpVtbl->Release(pnshhkA);

                 LocalFree(lpszFolderA);
             }
          }
      }
#endif
      punk->lpVtbl->Release(punk);
   }

   return(hr);
}


const TCHAR c_szNewLinkHandlers[] = REGSTR_PATH_EXPLORER TEXT("\\NewShortcutHandlers");


//
// Sets lpwd->pnshhk to NULL for CLSID_ShellLink (default) or to the
// INewShortcutHook of the object to be used.
//
// If lpwd->pnshhk is returned non-NULL, szProgDesc[] and szExt[] are also
// filled in.
//

void DetermineLinkHandler(LPWIZDATA lpwd)
{
   HKEY hkeyHooks;

   // Lose any previously saved external new Shortcut handler.

   if (lpwd->pnshhk)
   {
      lpwd->pnshhk->lpVtbl->Release(lpwd->pnshhk);
      lpwd->pnshhk = NULL;
   }
#ifdef UNICODE
   if (lpwd->pnshhkA)
   {
      lpwd->pnshhkA->lpVtbl->Release(lpwd->pnshhkA);
      lpwd->pnshhkA = NULL;
   }
#endif

   //
   // Enumerate the list of new link handlers.  Each new link handler is
   // registered as a GUID value under c_szNewLinkHandlers.
   //

   if (RegOpenKey(HKEY_LOCAL_MACHINE, c_szNewLinkHandlers, &hkeyHooks)
       == ERROR_SUCCESS)
   {
      DWORD dwiValue;
      TCHAR szCLSID[GUIDSTR_MAX];
      DWORD dwcbCLSIDLen;

      //
      // Invoke each hook.  A hook returns S_FALSE if it does not wish to
      // handle the new link.  Stop if a hook returns S_OK.
      //

      for (dwcbCLSIDLen = ARRAYSIZE(szCLSID), dwiValue = 0;
           RegEnumValue(hkeyHooks, dwiValue, szCLSID, &dwcbCLSIDLen, NULL,
                        NULL, NULL, NULL) == ERROR_SUCCESS;
           dwcbCLSIDLen = ARRAYSIZE(szCLSID), dwiValue++)
      {
         CLSID clsidHook;

         if (SHCLSIDFromString(szCLSID, &clsidHook) == S_OK &&
             QueryNewLinkHandler(lpwd, &clsidHook) == S_OK)
            break;
      }

      RegCloseKey(hkeyHooks);
   }

   return;
}

#endif


//
//  Returns TRUE if it's OK to go to the next wizard dialog.
//

BOOL NextPushed(LPWIZDATA lpwd)
{
    GetDlgItemText(lpwd->hwnd, IDC_COMMAND, lpwd->szExeName, ARRAYSIZE(lpwd->szExeName));

    // Is the string a path with spaces, without arguments, but isn't correctly
    // quoted?  NT #d: >C:\Program Files\Windows NT\dialer.exe< is treated like
    // "C:\Program" with "Files\Windows NT\dialer.exe" as args.
    if (PathFileExists(lpwd->szExeName))
    {
        // Yes, so let's quote it so we don't treat the stuff after
        // the space like args.
        PathQuoteSpaces(lpwd->szExeName);
    }
    
    PathRemoveBlanks(lpwd->szExeName);

    if (lpwd->szExeName[0] != 0)
    {
        BOOL    bUNC;
        LPTSTR  lpszTarget = NULL;
        HCURSOR hcurOld  = SetCursor(LoadCursor(NULL, IDC_WAIT));
        LPTSTR  lpszArgs = PathGetArgs(lpwd->szExeName);

        lstrcpy(lpwd->szParams, lpszArgs);

        if (*lpszArgs)
        {
            *(lpszArgs - 1) = 0;   // clobber the ' ' in the exe name field
        }

        ExpandEnvironmentStrings( lpwd->szExeName,
                                  lpwd->szExpExeName,
                                  ARRAYSIZE(lpwd->szExpExeName)
                                 );
        lpwd->szExpExeName[ MAX_PATH-1 ] = TEXT('\0');
        if (lstrcmp(lpwd->szExeName, lpwd->szExpExeName))
            lpwd->dwFlags |= WDFLAG_EXPSZ;


        lpszTarget = (lpwd->dwFlags & WDFLAG_EXPSZ) ? lpwd->szExpExeName : lpwd->szExeName;


        PathUnquoteSpaces(lpszTarget);
        if (lpwd->dwFlags & WDFLAG_EXPSZ)
            PathUnquoteSpaces(lpwd->szExeName);

        lpwd->dwFlags &= ~WDFLAG_COPYLINK;

#ifndef NO_NEW_SHORTCUT_HOOK

        //
        // Figure out who wants to handle this string as a link referent.
        //

        DetermineLinkHandler(lpwd);

        if (lpwd->pnshhk)
        {
            //
            // We are using an external link handler.  Skip file system
            // validation.
            //

            lpwd->dwFlags |= WDFLAG_APPKNOWN;
            SetCursor(hcurOld);
            return(TRUE);
        }
#ifdef UNICODE
        if (lpwd->pnshhkA)
        {
            //
            // We are using an external link handler.  Skip file system
            // validation.
            //

            lpwd->dwFlags |= WDFLAG_APPKNOWN;
            SetCursor(hcurOld);
            return(TRUE);
        }
#endif

#endif

        bUNC = PathIsUNC(lpszTarget);

        if (bUNC && !SHValidateUNC(lpwd->hwnd, lpszTarget, FALSE))
            goto Done;

        //
        //  If the user tries to make a link to A:\ and there's no disk
        //  in the drive, PathResolve would fail.  So, for drive roots, we
        //  don't try to resolve it.
        //

        if ((PathIsRoot(lpszTarget) && !bUNC &&
             DriveType(DRIVEID(lpszTarget))) ||
             PathResolve(lpszTarget, NULL,
                         PRF_VERIFYEXISTS | PRF_TRYPROGRAMEXTENSIONS))
        {
            //
            // If we found a PIF file then we'll try to convert it to the
            // name of the file it points to.
            //

            if (lstrcmpi(PathFindExtension(lpszTarget), c_szPIF) == 0)
            {
                if (!ReadPifProps(lpwd))
                {
                    goto Done;
                }

#ifdef UNICODE
                MultiByteToWideChar(CP_ACP, 0, lpwd->PropPrg.achCmdLine, -1,
                                    lpszTarget, ARRAYSIZE(lpwd->szExeName));
#else
                lstrcpy(lpszTarget, lpwd->PropPrg.achCmdLine);
#endif // UNICODE

                PathRemoveArgs(lpszTarget);

                if (!PathResolve(lpszTarget, NULL,
                                 PRF_VERIFYEXISTS | PRF_TRYPROGRAMEXTENSIONS))
                {
                    goto Done;
                }
            }

#ifdef WINNT
            //
            // Okay, at this point we should have the absolute path for the
            // target of the link.  On NT, if the target happens to be for
            // something in the %Windir% directory (or a subdir of %Windir%),
            // AND the user didn't type in an expandable path already, then
            // store the path as %windir%\blah\blah\blah instead of as an
            // absolute path.  This will help with interoperability of shortcuts
            // across different machines, etc.
            //

            if (!(lpwd->dwFlags & WDFLAG_EXPSZ))
            {
                TCHAR szWindir[ MAX_PATH ];
                DWORD dwLen;

                //
                // What did the user type in?
                //
                GetDlgItemText(lpwd->hwnd, IDC_COMMAND, szWindir, ARRAYSIZE(szWindir));
                if (ualstrcmpi(szWindir, lpwd->szExeName)==0)
                {
                    //
                    // If we didn't change it, it means the user typed in an
                    // exact path.  In that case, don't try to map anyting.
                    //
                    goto LinkToALinkCase;
                }
                dwLen = ExpandEnvironmentStrings( TEXT("%windir%"),
                                                  szWindir,
                                                  ARRAYSIZE(szWindir)
                                                 );
                if (dwLen &&
                    dwLen < ARRAYSIZE(szWindir) &&
                    lstrlen(szWindir) <= lstrlen(lpszTarget)
                   )
                {
                    //
                    // we use dwLen-1 because dwLen includes the '\0' character
                    //
                    if (CompareString( LOCALE_SYSTEM_DEFAULT,
                                       NORM_IGNORECASE,
                                       szWindir, dwLen-1 ,
                                       lpszTarget, dwLen-1
                                      ) == 2)
                    {
                        //
                        // We should substitute the env variable for the
                        // actual string here...
                        //
                        lstrcpy( lpwd->szExpExeName, lpwd->szExeName );
                        lstrcpy( lpwd->szExeName, TEXT("%windir%") );

                        // 8 == lstrlen("%windir%")
                        ualstrcpy( lpwd->szExeName + 8, lpwd->szExpExeName+dwLen-1 );
                        lpwd->dwFlags |= WDFLAG_EXPSZ;
                        lpszTarget = lpwd->szExpExeName;

                    }
                }
            }
#endif // winnt

#ifdef WINNT
            //
            // Okay, at this point we should have the absolute path for the
            // target of the link.  On NT, if the target happens to be for
            // something in the %Windir% directory (or a subdir of %Windir%),
            // AND the user didn't type in an expandable path already, then
            // store the path as %windir%\blah\blah\blah instead of as an
            // absolute path.  This will help with interoperability of shortcuts
            // across different machines, etc.
            //

            if (!(lpwd->dwFlags & WDFLAG_EXPSZ))
            {
                TCHAR szWindir[ MAX_PATH ];
                DWORD dwLen;

                //
                // What did the user type in?
                //
                GetDlgItemText(lpwd->hwnd, IDC_COMMAND, szWindir, ARRAYSIZE(szWindir));
                if (ualstrcmpi(szWindir, lpwd->szExeName)==0)
                {
                    //
                    // If we didn't change it, it means the user typed in an
                    // exact path.  In that case, don't try to map anyting.
                    //
                    goto LinkToALinkCase;
                }
                dwLen = ExpandEnvironmentStrings( TEXT("%windir%"),
                                                  szWindir,
                                                  ARRAYSIZE(szWindir)
                                                 );
                if (dwLen &&
                    dwLen < ARRAYSIZE(szWindir) &&
                    lstrlen(szWindir) <= lstrlen(lpszTarget)
                   )
                {
                    //
                    // we use dwLen-1 because dwLen includes the '\0' character
                    //
                    if (CompareString( LOCALE_SYSTEM_DEFAULT,
                                       NORM_IGNORECASE,
                                       szWindir, dwLen-1 ,
                                       lpszTarget, dwLen-1
                                      ) == 2)
                    {
                        //
                        // We should substitute the env variable for the
                        // actual string here...
                        //
                        lstrcpy( lpwd->szExpExeName, lpwd->szExeName );
                        lstrcpy( lpwd->szExeName, TEXT("%windir%") );

                        // 8 == lstrlen("%windir%")
                        ualstrcpy( lpwd->szExeName + 8, lpwd->szExpExeName+dwLen-1 );
                        lpwd->dwFlags |= WDFLAG_EXPSZ;
                        lpszTarget = lpwd->szExpExeName;

                    }
                }
            }
LinkToALinkCase:

#endif // winnt

            //
            //  Really, really obscure case. The user creates "New Shortcut" and
            //  tries to point it to itself. Don't allow it.  We'd be confused
            //  later. Since it's so obscure, just give a generic error about
            //  "Can't find this file"
            //

            if (!(lpwd->lpszOriginalName &&
                  lstrcmpi(lpwd->lpszOriginalName, lpszTarget) == 0))
            {
                DetermineExeType(lpwd);
                FindWorkingDir(lpwd);

                lpwd->szProgDesc[0] = 0;  // Reset description
                                          // EVEN IF WE DON'T RECREATE IT HERE!

                if (lpwd->lpszFolder && lpwd->lpszFolder[0] != 0 &&
                    !DetermineDefaultTitle(lpwd))
                {
                    goto Done;
                }

                if (lpwd->dwFlags & WDFLAG_EXPSZ)
                {
                    LPTSTR lpszExt = PathFindExtension( lpwd->szExeName );

                    if (!(*lpszExt))
                    {
                        // do simple check to make sure there was a file name
                        // at the end of the original entry.  we assume that
                        // if we got this far, lpszExt points to the end of
                        // the string pointed to by lpwd->szExeName, and that
                        // lpwd->szExeName has at least one character in it.
                        if (lpwd->szExeName &&
                            (*lpwd->szExeName) &&
                            (*(lpszExt-1)!=TEXT('%'))
                            )
                        {
                            lstrcpy( lpszExt, PathFindExtension( lpszTarget ) );
                        }
                    }
                }

                SetCursor(hcurOld);
                return(TRUE);
            }

        }
Done:

        SetCursor(hcurOld);
        ShellMessageBox(g_hinst, lpwd->hwnd, MAKEINTRESOURCE(IDS_BADPATHMSG), 0, MB_OK | MB_ICONEXCLAMATION, lpwd->szExeName);
    }

    BrowseSetActive(lpwd);
    return(FALSE);
}


//
//  Returns TRUE if it's OK to run the setup program.
//

BOOL SetupCleanupExePath(LPWIZDATA lpwd)
{
    BOOL fValidPrg = FALSE;

    GetDlgItemText(lpwd->hwnd, IDC_COMMAND, lpwd->szExeName, ARRAYSIZE(lpwd->szExeName));

    // Is the string a path with spaces, without arguments, but isn't correctly
    // quoted?  NT #d: >C:\Program Files\Windows NT\dialer.exe< is treated like
    // "C:\Program" with "Files\Windows NT\dialer.exe" as args.
    if (PathFileExists(lpwd->szExeName))
    {
        // Yes, so let's quote it so we don't treat the stuff after
        // the space like args.
        PathQuoteSpaces(lpwd->szExeName);
    }

    PathRemoveBlanks(lpwd->szExeName);

    if (lpwd->szExeName[0] != 0)
    {
        LPTSTR lpszTarget = NULL;
        LPTSTR lpszArgs = NULL;
        HCURSOR hcurOld = SetCursor(LoadCursor(NULL, IDC_WAIT));


        ExpandEnvironmentStrings( lpwd->szExeName,
                                  lpwd->szExpExeName,
                                  ARRAYSIZE(lpwd->szExpExeName)
                                 );
        if (lstrcmp(lpwd->szExeName, lpwd->szExpExeName))
            lpwd->dwFlags |= WDFLAG_EXPSZ;

        lpszTarget = (lpwd->dwFlags & WDFLAG_EXPSZ) ? lpwd->szExpExeName : lpwd->szExeName;

        lpszArgs = PathGetArgs(lpszTarget);
        lstrcpy(lpwd->szParams, lpszArgs);
        if (*lpszArgs)
        {
            *(lpszArgs - 1) = 0;   // clobber the ' ' in the exe name field
        }
   
        PathUnquoteSpaces(lpszTarget);
        if (lpwd->dwFlags & WDFLAG_EXPSZ)
            PathUnquoteSpaces(lpwd->szExeName);

        if (PathResolve(lpszTarget, NULL,
                        PRF_VERIFYEXISTS | PRF_TRYPROGRAMEXTENSIONS))
        {
            LPTSTR lpszExt = PathFindExtension( lpszTarget );
            fValidPrg = TRUE;
            FindWorkingDir(lpwd);
            if (lpwd->dwFlags & WDFLAG_EXPSZ)
            {
                if (!(*lpszExt))
                {
                    lstrcpy( lpszExt, PathFindExtension( lpszTarget ) );
                }
            }
            
            if ((*lpszExt) && lpwd->bTermSrvAndAdmin && (!lstrcmpi(lpszExt, TEXT(".msi"))))
                lstrcat(lpwd->szParams, TEXT(" ALLUSERS=1"));

            PathQuoteSpaces( lpszTarget );
        }
        SetCursor(hcurOld);
    }


    if (!fValidPrg)
    {
        ShellMessageBox(g_hinst, lpwd->hwnd, MAKEINTRESOURCE(IDS_BADPATHMSG), 0, MB_OK | MB_ICONEXCLAMATION, lpwd->szExeName);
        BrowseSetActive(lpwd);
    }
    return(fValidPrg);
}


BOOL DetermineDefaultTitle(LPWIZDATA lpwd)
{
    TCHAR   szFullName[MAX_PATH];
    BOOL    fCopy;
    LPTSTR  lpszName;

    lpwd->dwFlags &= ~WDFLAG_COPYLINK;

    if (lpwd->dwFlags & WDFLAG_EXPSZ)
        lpszName = lpwd->szExpExeName;
    else
        lpszName = lpwd->szExeName;

    if (!SHGetNewLinkInfo(lpszName, lpwd->lpszFolder, szFullName,
                     &fCopy, 0))
    {
        //
        // failure...
        //

        return(FALSE);
    }

    lpszName = PathFindFileName(szFullName);

    StripExt(lpszName);

    lstrcpyn(lpwd->szProgDesc, lpszName, ARRAYSIZE(lpwd->szProgDesc));

    //
    // We will never copy PIF files since they often do not contain
    // the appropriate current directory.  This is becuase they are
    // automatically created when you run a DOS application from the
    // shell.
    //

    if ((lpwd->dwFlags & WDFLAG_DOSAPP) == 0)
    {
        if (fCopy)
        {
            lpwd->dwFlags |= WDFLAG_COPYLINK;
        }
#ifndef NO_NEW_SHORTCUT_HOOK
        lstrcpy(lpwd->szExt, c_szLNK);
    }
    else
    {
        lstrcpy(lpwd->szExt, c_szPIF);
#endif
    }

    return(TRUE);
}

//
// paranoia: evaluate each time in case it is installed after ARP was first open, but
//           before it is closed and re-opened
//
BOOL MSI_IsMSIAvailable()
{
    BOOL bAvailable = FALSE;

    HINSTANCE hinst = LoadLibraryA("MSI.DLL");
    
    if (hinst)
    {
        bAvailable = TRUE;

        FreeLibrary(hinst);
    }

    return bAvailable;
}

//
//  Call the common dialog code for File Open
//
BOOL BrowseForExe(HWND hwnd, LPTSTR pszName, DWORD cchName, LPCTSTR pszInitDir)
{
    TCHAR szExt[80];
    TCHAR szFilter[200];
    TCHAR szTitle[80];
    TCHAR szBootDir[64];

    //
    // Must pass the buffer size to GetBootDir because that is what
    // the RegQueryValueEx function expects - not count of chars.
    //

    if (!pszInitDir)
    {
        GetBootDir(szBootDir, ARRAYSIZE(szBootDir));
    }
    else
    {
        // we want to pass in an initial directory since GetFileNameFromBrowse
        // try to determine an initial directory by doing a PathRemoveFileSpec
        // on pszName.  If pszName is already a directory then the last directory
        // is removed (even though it's not a file).  E.g.: "c:\winnt" -> "c:\"
        lstrcpyn(szBootDir, pszInitDir, ARRAYSIZE(szBootDir));
    }

    if (MSI_IsMSIAvailable())
        LoadAndStrip(IDS_BROWSEFILTERMSI, szFilter, ARRAYSIZE(szFilter));
    else
        LoadAndStrip(IDS_BROWSEFILTER, szFilter, ARRAYSIZE(szFilter));

    LoadString(g_hinst, IDS_BROWSEEXT,    szExt,    ARRAYSIZE(szExt));
    LoadString(g_hinst, IDS_BROWSETITLE,  szTitle,  ARRAYSIZE(szTitle));

    // we need to set pszName to NULL or else GetFileNameFromBrowse will use it
    // to find the initial directory even though we explicitly pass in an initial
    // dir.
    *pszName = 0;

    return(GetFileNameFromBrowse(hwnd, pszName, cchName,
                                 szBootDir, szExt, szFilter, szTitle));
}

//
//  Use the common open dialog to browse for program. Used by SetupBrowseDlgProc
//

void BrowsePushed(LPWIZDATA lpwd)
{
    LPTSTR lpszName;
    DWORD cchName = 0;

    GetDlgItemText(lpwd->hwnd, IDC_COMMAND, lpwd->szExeName, ARRAYSIZE(lpwd->szExeName));
    ExpandEnvironmentStrings( lpwd->szExeName, lpwd->szExpExeName, ARRAYSIZE(lpwd->szExpExeName) );
    if (lstrcmp(lpwd->szExeName, lpwd->szExpExeName))
        lpwd->dwFlags |= WDFLAG_EXPSZ;

    if (lpwd->dwFlags & WDFLAG_EXPSZ)
    {
        lpszName = lpwd->szExpExeName;
        cchName = ARRAYSIZE(lpwd->szExpExeName);
    }
    else
    {
        lpszName = lpwd->szExeName;
        cchName = ARRAYSIZE(lpwd->szExeName);
    }

    if (BrowseForExe(lpwd->hwnd, lpszName, cchName, lpszName))
    {
        lpwd->szParams[0] = 0;
        BrowseSetActive(lpwd);
    }
}


int CALLBACK BrowseCallbackProc(
    HWND hwnd, 
    UINT uMsg, 
    LPARAM lParam, 
    LPARAM lpData
    )
{
    LPITEMIDLIST pidlNavigate;

    switch (uMsg)
    {
    case BFFM_INITIALIZED:
        // Check if we should navigate to a folder on initialize
        pidlNavigate = (LPITEMIDLIST) lpData;
        if (pidlNavigate != NULL)
        {
            // Yes! We have a folder to navigate to; send the message
            SendMessage(hwnd, BFFM_SETSELECTION, (WPARAM) FALSE, (LPARAM) pidlNavigate);
        }
        break;

    case BFFM_SELCHANGED:
        // Check if we should enable/disable the Ok button
        pidlNavigate = (LPITEMIDLIST)lParam;
        if (pidlNavigate != NULL)
        {
            DWORD dwFlags = SFGAO_FILESYSTEM;
            WCHAR szName[MAX_PATH] = L"";
            
            if (SUCCEEDED(SHGetNameAndFlags(pidlNavigate, SHGDN_NORMAL, szName, ARRAYSIZE(szName), &dwFlags)))
            {
                if ((dwFlags & SFGAO_FILESYSTEM) != 0 ||
                    !lstrncmp(szName, L"\\\\", 2))
                {
                    // Path is either a valid local path
                    // or a valid network path, enable the ok button
                    SendMessage(hwnd, BFFM_ENABLEOK, (WPARAM)0, (LPARAM)1);
                }
                else
                {
                    // Path does not exist, disable the ok button
                    // This could be My Computer or an empty floppy drive etc...
                    SendMessage(hwnd, BFFM_ENABLEOK, (WPARAM)0, (LPARAM)0);
                }
            }
        }
    }
     
    return 0;
}

// This implementation of 'Browse' uses SHBrowseForFolder to find a file or folder
// for the shortcut wizard - used by BrowseDlgProc
void BrowseForFileOrFolder(LPWIZDATA lpwd)
{
    TCHAR szBrowseTitle[256];
    TCHAR szName[MAX_PATH];
    BROWSEINFO bi = {0};
    LPITEMIDLIST pidlSelected;
    LPITEMIDLIST pidlStartBrowse;
    IShellFolder* pdesktop;

    // Try to start the browse at a location indicated by the typed-in command line,
    // if possible
    GetDlgItemText(lpwd->hwnd, IDC_COMMAND, lpwd->szExeName, ARRAYSIZE(lpwd->szExeName));

    // ..Get the desktop folder
    if (SUCCEEDED(SHGetDesktopFolder(&pdesktop)))
    {
        // ..Now try to parse the path the user entered into a pidl to start at
        ULONG chEaten;

#ifdef UNICODE
        if (FAILED(pdesktop->lpVtbl->ParseDisplayName(pdesktop, lpwd->hwnd, NULL,
            lpwd->szExeName, &chEaten, &pidlStartBrowse, NULL)))
#else
        WCHAR szTmp[MAX_PATH];
        
        SHAnsiToUnicode(lpwd->szExeName, szTmp, MAX_PATH);

        if (FAILED(pdesktop->lpVtbl->ParseDisplayName(pdesktop, lpwd->hwnd, NULL,
            szTmp, &chEaten, &pidlStartBrowse, NULL)))        
#endif
        {
            // The path the user entered didn't make any sense
            // pidlStartBrowse should already be NULL, but we want to make sure
            pidlStartBrowse = NULL;
        }

        // Now we can continue and display the browse window

        // Load the title string for the browse window
        LoadString(g_hinst, IDS_FILEFOLDERBROWSE_TITLE, szBrowseTitle, ARRAYSIZE(szBrowseTitle));

        // Note that bi = {0} for all other members except:
        bi.hwndOwner = lpwd->hwnd;
        bi.pszDisplayName = szName;
        bi.lpszTitle = szBrowseTitle;
        bi.ulFlags = BIF_BROWSEINCLUDEFILES | BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;

        // Ensure the pidl we want to start at is passed to the callback function
        bi.lpfn = BrowseCallbackProc;
        bi.lParam = (LPARAM) pidlStartBrowse;

        pidlSelected = SHBrowseForFolder(&bi);

        if (pidlSelected != NULL)
        {
            STRRET strret;
            if (SUCCEEDED(pdesktop->lpVtbl->GetDisplayNameOf(pdesktop, pidlSelected, SHGDN_NORMAL | SHGDN_FORPARSING, &strret)))
            {
                StrRetToBuf(&strret, pidlSelected, lpwd->szExeName, ARRAYSIZE(lpwd->szExeName));

                // Assume no parameters for this new file
                lpwd->szParams[0] = 0;
                
                // Populate the text box with the new file, etc.
                BrowseSetActive(lpwd);
            }
            // Free the pidl
            ILFree(pidlSelected);
        }

        if (pidlStartBrowse != NULL)
        {
            ILFree(pidlStartBrowse);
        }
            
        pdesktop->lpVtbl->Release(pdesktop);
    }
    else
    {
        // This really shouldn't happen; SHGetDesktopdesktop failed; out of memory?
    }
}

//
//  Main dialog procedure for first page of shortcut wizard.
//

//
//  Note that there are now two BrowseDlgProcs, the one below and
//  'SetupBrowseDlgProc'. This is because BrowseDlgProc now uses
//  a different method for implementing the 'Browse' button and I
//  wanted to do this without affecting the Setup Wizard which will
//  now use SetupBrowseDlgProc. - dsheldon 6/16/98
//

BOOL_PTR CALLBACK BrowseDlgProc(HWND hDlg, UINT message , WPARAM wParam, LPARAM lParam)
{
    NMHDR FAR *lpnm = NULL;
    LPPROPSHEETPAGE lpPropSheet = (LPPROPSHEETPAGE)(GetWindowLongPtr(hDlg, DWLP_USER));
    LPWIZDATA lpwd = NULL;

    if (lpPropSheet)
    {
        lpwd = (LPWIZDATA)lpPropSheet->lParam;
    }

    switch(message)
    {
        case WM_NOTIFY:
            lpnm = (NMHDR FAR *)lParam;
            if(lpnm)
            {
                switch(lpnm->code)
                {
                   case PSN_SETACTIVE:
                        if(lpwd)
                        {
                            lpwd->hwnd = hDlg;
                            if (lpwd->dwFlags & WDFLAG_NOBROWSEPAGE)
                            {
                                SetDlgMsgResult(hDlg, WM_NOTIFY, -1);
                            }
                            else
                            {
                                BrowseSetActive(lpwd);
                            }
                        }
                        break;

                   case PSN_WIZNEXT:
                        if(lpwd)
                        {
                            if (!NextPushed(lpwd) ||
                                ((lpwd->dwFlags & WDFLAG_SETUPWIZ) && !SetupCleanupExePath(lpwd)))
                            {
                                SetDlgMsgResult(hDlg, WM_NOTIFY, -1);
                            }
                        }
                        break;

                   case PSN_WIZFINISH:
                        if(lpwd)
                        {
                            BOOL ForceWx86;

#ifdef WX86
                            ForceWx86 = bWx86Enabled && bForceX86Env;
#else
                            ForceWx86 = FALSE;
#endif

                            if (!SetupCleanupExePath(lpwd) ||
                                !ExecSetupProg(lpwd, ForceWx86, TRUE))
                            {
                                BrowseSetActive(lpwd);
                                SetDlgMsgResult(hDlg, WM_NOTIFY, -1);
                            }
                        }
                        break;

                   case PSN_RESET:
                        if(lpwd)
                        {
                            CleanUpWizData(lpwd);
                        }
                        break;

                   default:
                      return FALSE;
                }
            }
            break;

        case WM_INITDIALOG:
            BrowseInitPropSheet(hDlg, lParam);
            break;

        case WMPRIV_POKEFOCUS:
            {
            HWND hCmd = GetDlgItem(hDlg, IDC_COMMAND);

            SetFocus(hCmd);

            Edit_SetSel(hCmd, 0, -1);

            break;
            }

        case WM_DESTROY:
        case WM_HELP:
        case WM_CONTEXTMENU:
            break;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case IDHELP:
                    break;

                case IDC_COMMAND:
                    switch (GET_WM_COMMAND_CMD(wParam, lParam))
                    {
                        case EN_CHANGE:
                            if(lpwd)
                            {
                                SetBrowseButtons(lpwd);
                            }
                            break;
                    }
                    break;

                case IDC_BROWSE:
                    if(lpwd)
                    {
                        BrowseForFileOrFolder(lpwd);
                    }
                    break;

            } // end of switch on WM_COMMAND
            break;

        default:
            return FALSE;

    } // end of switch on message

    return TRUE;
}  // BrowseDlgProc


BOOL_PTR CALLBACK SetupBrowseDlgProc(HWND hDlg, UINT message , WPARAM wParam, LPARAM lParam)
{
    NMHDR FAR *lpnm = NULL;
    LPPROPSHEETPAGE lpPropSheet = (LPPROPSHEETPAGE)(GetWindowLongPtr(hDlg, DWLP_USER));
    LPWIZDATA lpwd = NULL;

    if (lpPropSheet)
    {
        lpwd = (LPWIZDATA)lpPropSheet->lParam;
    }

    switch(message)
    {
        case WM_NOTIFY:
            lpnm = (NMHDR FAR *)lParam;
            if(lpnm)
            {
                switch(lpnm->code)
                {
                   BOOL bForceWx86;
                   case PSN_SETACTIVE:
                        if(lpwd)
                        {
                            lpwd->hwnd = hDlg;
                            if (lpwd->dwFlags & WDFLAG_NOBROWSEPAGE)
                            {
                                SetDlgMsgResult(hDlg, WM_NOTIFY, -1);
                            }
                            else
                            {
                                BrowseSetActive(lpwd);
                            }
                        }
                        break;

                   case PSN_WIZNEXT:
                        if(lpwd)
                        {
                            // Remember the previous "InstallMode"
                            lpwd->bPrevMode = TermsrvAppInstallMode();

                            // Set the "InstallMode"
                            SetTermsrvAppInstallMode(TRUE);

#ifdef WX86
                            bForceWx86 = bWx86Enabled && bForceX86Env;
#else
                            bForceWx86 = FALSE;
#endif
                            if (!NextPushed(lpwd) || !SetupCleanupExePath(lpwd) ||
                                  !ExecSetupProg(lpwd, bForceWx86, FALSE))
                            {
                                SetDlgMsgResult(hDlg, WM_NOTIFY, -1);
                            }
                        }
                        break;

                   case PSN_WIZFINISH:
                       if(lpwd)
                       {
#ifdef WX86
                           bForceWx86 = bWx86Enabled && bForceX86Env;
#else
                           bForceWx86 = FALSE;
#endif

                           if (!SetupCleanupExePath(lpwd) ||
                               !ExecSetupProg(lpwd, bForceWx86, TRUE))
                           {
                               BrowseSetActive(lpwd);
                               SetDlgMsgResult(hDlg, WM_NOTIFY, -1);
                           }
                       }
                       break;

                   case PSN_RESET:
                       if(lpwd)
                       {
                           CleanUpWizData(lpwd);
                       }
                       break;

                   default:
                      return FALSE;
                }
            }
            break;

        case WM_INITDIALOG:
            BrowseInitPropSheet(hDlg, lParam);
            break;

        case WMPRIV_POKEFOCUS:
            {
            HWND hCmd = GetDlgItem(hDlg, IDC_COMMAND);

            SetFocus(hCmd);

            Edit_SetSel(hCmd, 0, -1);

            break;
            }

        case WM_DESTROY:
        case WM_HELP:
        case WM_CONTEXTMENU:
            break;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case IDHELP:
                        break;

                case IDC_COMMAND:
                    switch (GET_WM_COMMAND_CMD(wParam, lParam))
                    {
                        case EN_CHANGE:
                            if(lpwd)
                            {
                                SetBrowseButtons(lpwd);
                            }
                            break;
                    }
                    break;

                case IDC_BROWSE:
                    if(lpwd)
                    {
                        BrowsePushed(lpwd);
                    }
                    break;

            } // end of switch on WM_COMMAND
            break;

        default:
            return FALSE;

    } // end of switch on message

    return TRUE;
}  // SetupBrowseDlgProc




