text: /*++
  
   Copyright (c) 2000 Microsoft Corporation
  
   Module Name:
  
      EmulateMissingEXE.cpp
  
   Abstract:
  
      Win9x had scandskw.exe and defrag.exe in %windir%, NT does not.
      Whistler has a hack in the shell32 for scandisk for app compatability
      purposes.  Whistler can also invoke defrag via 
      "%windir%\system32\mmc.exe %windir%\system32\dfrg.msc".
  
      This shim redirects CreateProcess and Winexec to execute these two
      substitutes, as well as FindFile to indicate their presence.
  
   Notes:
  
      This is a general purpose shim.
  
   History:
  
      01/02/2001  prashkud Created
      02/18/2001  prashkud Merged HandleStartKeyword SHIM with this.
      02/21/2001  prashkud Replaced most strings with CString class.                   
  
  --*/
  
  #include "precomp.h"
  
  IMPLEMENT_SHIM_BEGIN(EmulateMissingEXE)
  #include "ShimHookMacro.h"
  
  APIHOOK_ENUM_BEGIN
      APIHOOK_ENUM_ENTRY(CreateProcessA)
      APIHOOK_ENUM_ENTRY(CreateProcessW)
      APIHOOK_ENUM_ENTRY(WinExec)
      APIHOOK_ENUM_ENTRY(FindFirstFileA)
      APIHOOK_ENUM_ENTRY(FindFirstFileW)
      APIHOOK_ENUM_ENTRY_COMSERVER(SHELL32)
  APIHOOK_ENUM_END
  
  IMPLEMENT_COMSERVER_HOOK(SHELL32)
  
  // Type for the functions that builds the New EXES
  typedef BOOL (*_pfn_STUBFUNC)(CString&, CString&, BOOL);
  
  // Main Data structure to hold the New strings
  struct REPLACEENTRY {
      WCHAR *OrigExeName;                 // original EXE to be replaced
      _pfn_STUBFUNC pfnFuncName;          // function to call to correct the name
  };
  
  CRITICAL_SECTION g_CritSec;
  WCHAR *g_szSysDir = NULL;               // system directory for stubs to use
  
  BOOL StubScandisk(CString&, CString&, BOOL);
  BOOL StubDefrag(CString&, CString&, BOOL);
  BOOL StubStart(CString&, CString&, BOOL);
  BOOL StubControl(CString&, CString&, BOOL);
  BOOL StubDxDiag(CString&, CString&, BOOL);
  BOOL StubWinhlp(CString&, CString&, BOOL);
  BOOL StubRundll(CString&, CString&, BOOL);
  BOOL StubPbrush(CString&, CString&, BOOL);
  
  // Add variations of these missing Exes like in HandleStartKeyword                             
  // Start has been put at the top of the list as there seem to be more apps
  // that need the SHIM for this EXE than others. In fact there was a 
  // seperate SHIM HandleStartKeyword that was merged with this.
  REPLACEENTRY g_ReplList[] = {
      {L"start",        StubStart    },
      {L"start.exe",    StubStart    },    
      {L"scandskw",     StubScandisk },
      {L"scandskw.exe", StubScandisk },
      {L"defrag",       StubDefrag   },
      {L"defrag.exe",   StubDefrag   },
      {L"control",      StubControl  },
      {L"control.exe",  StubControl  },
      {L"dxdiag",       StubDxDiag   },
      {L"dxdiag.exe",   StubDxDiag   },
      {L"winhelp",      StubWinhlp   },
      {L"winhelp.exe",  StubWinhlp   },
      {L"rundll",       StubRundll   },
      {L"rundll.exe",   StubRundll   },
      {L"Pbrush",       StubPbrush   },    
      {L"Pbrush.exe",   StubPbrush   },    
      // Always the last one
      {L"",             NULL         }
  };
  
  // Added to merge HandleStartKeyword
  // Link list of shell link object this pointers.
  struct THISPOINTER
  {
      THISPOINTER *next;
      LPCVOID pThisPointer;
  };
  
  THISPOINTER *g_pThisPointerList;
  
  /*++
  
   Function Description:
  
      Add a this pointer to the linked list of pointers. Does not add if the
      pointer is NULL or a duplicate.
  
   Arguments:
  
      IN  pThisPointer - the pointer to add.
  
   Return Value:
  
      None
  
   History:
  
      12/14/2000 maonis Created
  
  --*/
  
  VOID 
  AddThisPointer(
      IN LPCVOID pThisPointer
      )
  {
      EnterCriticalSection(&g_CritSec);
  
      if (pThisPointer)
      {
          THISPOINTER *pPointer = g_pThisPointerList;
          while (pPointer)
          {
              if (pPointer->pThisPointer == pThisPointer)
              {
                  return;
              }
              pPointer = pPointer->next;
          }
  
          pPointer = (THISPOINTER *) malloc(sizeof THISPOINTER);
  
          if (pPointer)
          {
              pPointer->pThisPointer = pThisPointer;
              pPointer->next = g_pThisPointerList;
              g_pThisPointerList = pPointer;
          }      
      }
  
      LeaveCriticalSection(&g_CritSec);
  }
  
  /*++
  
   Function Description:
  
      Remove a this pointer if it can be found in the linked list of pointers. 
  
   Arguments:
  
      IN  pThisPointer - the pointer to remove.
  
   Return Value:
  
      TRUE if the pointer is found.
      FALSE if the pointer is not found.
  
   History:
  
      12/14/2000 maonis Created
  
  --*/
  
  BOOL 
  RemoveThisPointer(
      IN LPCVOID pThisPointer
      )
  {
      THISPOINTER *pPointer = g_pThisPointerList;
      THISPOINTER *last = NULL;
      BOOL lRet = FALSE;
      
      EnterCriticalSection(&g_CritSec);
  
      while (pPointer)
      {
          if (pPointer->pThisPointer == pThisPointer)
          {
              if (last)
              {
                  last->next = pPointer->next;
              }
              else
              {
                  g_pThisPointerList = pPointer->next;
              }
  
              free(pPointer);
              lRet = TRUE;    
              break;
          }
  
          last = pPointer;
          pPointer = pPointer->next;
      }
  
      LeaveCriticalSection(&g_CritSec);
      return lRet;
  }
  
  
  /*++
  
   We are here because the application name: scandskw.exe, matches the one in the 
   static array. Fill the News for scandskw.exe as: 
  
      rundll32.exe shell32.dll,AppCompat_RunDLL SCANDSKW
  
  --*/
  
  BOOL
  StubScandisk(
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL /*bExists*/
      )
  {
      csNewApplicationName = g_szSysDir;
      csNewApplicationName += L"\\rundll32.exe";
      csNewCommandLine     = L"shell32.dll,AppCompat_RunDLL SCANDSKW";
  
      return TRUE;
  
  }
  
  /*++
  
   We are here because the application name: defrag.exe, matches the one in the 
   static array. Fill the News for .exe as:
      
      %windir%\\system32\\mmc.exe %windir%\\system32\\dfrg.msc
  
  --*/
  
  BOOL
  StubDefrag(
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL /*bExists*/
      )
  {
      csNewApplicationName = g_szSysDir;
      csNewApplicationName += L"\\mmc.exe";
  
      csNewCommandLine =  g_szSysDir;
      csNewCommandLine += L"\\dfrg.msc";
      return TRUE;
  }
  
  /*++
  
   We are here because the application name: start.exe, matches the one in the 
   static array. Fill the News for .exe as: 
   
      %windir%\\system32\\cmd.exe" "/c start"
  
   Many applications have a "start.exe" in their current working directories 
   which needs to take precendence over any New we make.
  
  --*/
  
  BOOL
  StubStart(
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL bExists
      )
  {
      //
      // First check the current working directory for start.exe
      //
  
      if (bExists) {
          return FALSE;
      }
  
      // 
      // There is no start.exe in the current working directory
      //
      csNewApplicationName = g_szSysDir;
      csNewApplicationName += L"\\cmd.exe";
      csNewCommandLine     = L"/d /c start \"\"";
  
      return TRUE;
  }
  
  /*++
  
   We are here because the application name: control.exe, matches the one in the 
   static array. Fill the News for .exe as:
   
      %windir%\\system32\\control.exe
  
  --*/
  
  BOOL
  StubControl(
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL /*bExists*/
      )
  {
      csNewApplicationName = g_szSysDir;
      csNewApplicationName += L"\\control.exe";
      csNewCommandLine     = L"";        
  
      return TRUE;
  
  }
  
  /*++
  
   We are here because the application name: dxdiag.exe, matches the one in the 
   static array. Fill the News for .exe as:
   
      %windir%\system32\dxdiag.exe
  
  --*/
  
  BOOL
  StubDxDiag(
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL /*bExists*/
      )
  {
      csNewApplicationName = g_szSysDir;
      csNewApplicationName += L"\\dxdiag.exe";
      csNewCommandLine     = L"";
  
      return TRUE;
  }
  
  /*++
  
   We are here because the application name: Winhlp.exe, matches the one in the 
   static array. Fill the News for .exe as:
   
      %windir%\system32\winhlp32.exe
  
  --*/
  
  BOOL
  StubWinhlp(
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL /*bExists*/
      )
  {
      csNewApplicationName = g_szSysDir;
      csNewApplicationName += L"\\winhlp32.exe";
      // Winhlp32.exe needs the app name to be in the commandline.
      csNewCommandLine = csNewApplicationName;        
  
      return TRUE;
  }
  
  /*++
  
   We are here because the application name: rundll.exe matches the one in the 
   static array. Fill the News for .exe as:
   
      %windir%\system32\rundll32.exe
  
  --*/
  
  BOOL
  StubRundll(
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL /*bExists*/
      )
  {
      csNewApplicationName = g_szSysDir;
      csNewApplicationName += L"\\rundll32.exe";
      csNewCommandLine     = L"";
  
      return TRUE;
  }
  
  /*++
  
   We are here because the application name: Pbrush.exe matches the one in the 
   static array. Fill the New for .exe as:
   
      %windir%\system32\mspaint.exe
  
  --*/
  
  BOOL
  StubPbrush(
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL /*bExists*/
      )
  {
      csNewApplicationName = g_szSysDir;
      csNewApplicationName += L"\\mspaint.exe";
      csNewCommandLine     = L"";
  
      return TRUE;
  }
  
  /*++
  
   GetTitle takes the app path and returns just the EXE name.
  
  --*/
  
  VOID
  GetTitle(CString& csAppName,CString& csAppTitle)
  {
      csAppTitle = csAppName;
      int len = csAppName.ReverseFind(L'\\');
      if (len)
      {
          csAppTitle.Delete(0, len+1);
      }    
  }
  
  /*++
  
   This is the main function where the New logic happens. This function 
   goes through the static array and fills the suitable New appname and 
   the commandline.
  
  --*/
  
  BOOL
  Redirect(
      const CString& csApplicationName, 
      const CString& csCommandLine,
      CString& csNewApplicationName,
      CString& csNewCommandLine,
      BOOL  bJustCheckExePresence
      )
  {
      BOOL bRet = FALSE;
      CSTRING_TRY
      {    
  
          CString csOrigAppName;
          CString csOrigCommandLine;
          BOOL bExists = FALSE;
  
          AppAndCommandLine AppObj(csApplicationName, csCommandLine);
          csOrigAppName = AppObj.GetApplicationName();
          csOrigCommandLine = AppObj.GetCommandlineNoAppName();
  
          if (csOrigAppName.IsEmpty())
          {
              goto Exit;
          }
  
          //
          // Loop through the list of redirectors 
          //
      
          REPLACEENTRY *rEntry = &g_ReplList[0];
          CString csAppTitle;
          GetTitle(csOrigAppName, csAppTitle);    
  
          while (rEntry && rEntry->OrigExeName[0])
          {
              if (_wcsicmp(rEntry->OrigExeName, csAppTitle) == 0)
              {
                  //
                  // This final parameter has been added for the merger
                  // of HandleStartKeyword Shim. If this is TRUE, we don't
                  // go any further but just return.
                  //
                  if (bJustCheckExePresence)
                  {
                      bRet = TRUE;
                      goto Exit;
                  }
  
                  //
                  // Check if the current working directory contains the exe in question
                  //
                  WCHAR *szCurrentDirectory = NULL;
                  DWORD dwLen = GetCurrentDirectoryW(0, NULL);  // Just to get the length
                  // The dwLen got includes the terminating NULL too.
                  szCurrentDirectory = (WCHAR*)malloc(dwLen * sizeof(WCHAR));
  
                  if (szCurrentDirectory && 
                      GetCurrentDirectoryW(dwLen, szCurrentDirectory))
                  {
                      CString csFullAppName(szCurrentDirectory);
                      csFullAppName += L"\\";
                      csFullAppName += csAppTitle;
                      
                      // Check if the file exists and is not a directory
                      DWORD dwAttr = GetFileAttributesW(csFullAppName);
                      if ((dwAttr != INVALID_FILE_ATTRIBUTES) && 
                          !(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
                      {
                          DPFN( eDbgLevelInfo,
                              "[Redirect] %s found in current working directory");
                          bExists = TRUE;
                      }
                      free(szCurrentDirectory);
                  }
                  else if(szCurrentDirectory)
                  {
                      free(szCurrentDirectory);
                  }
             
                  //
                  // We have a match, so call the corresponding function
                  //            
  
                  bRet = (*(rEntry->pfnFuncName))(csNewApplicationName,
                          csNewCommandLine, bExists);
                  if (bRet) 
                  {                
                      //
                      // Append the original command line 
                      //
                      csNewCommandLine += L" ";
                      csNewCommandLine += csOrigCommandLine;                
                  }
  
                  // We matched an EXE, so we're done
                  break;            
              }
  
              rEntry++;
          }
  
          if (bRet) 
          {
              DPFN( eDbgLevelWarning, "Redirected:");
              DPFN( eDbgLevelWarning, "\tFrom: %S %S", csApplicationName, csCommandLine);
              DPFN( eDbgLevelWarning, "\tTo:   %S %S", csNewApplicationName, csNewCommandLine);
          }
      }
      CSTRING_CATCH
      {
          DPFN( eDbgLevelError, "Not Redirecting: Exception encountered");
          bRet = FALSE;     
      }
  
  Exit:
      return bRet;
  }
  
  /*++
  
   Hooks the CreateProcessA function to see if any News need to be 
   substituted. 
  
  --*/
  
  BOOL 
  APIHOOK(CreateProcessA)(
      LPCSTR lpApplicationName,
      LPSTR lpCommandLine,
      LPSECURITY_ATTRIBUTES lpProcessAttributes,
      LPSECURITY_ATTRIBUTES lpThreadAttributes,
      BOOL bInheritHandles,
      DWORD dwCreationFlags,
      LPVOID lpEnvironment,
      LPCSTR lpCurrentDirectory,
      LPSTARTUPINFOA lpStartupInfo,
      LPPROCESS_INFORMATION lpProcessInformation
      )
  {
      if ((NULL == lpApplicationName) &&
         (NULL == lpCommandLine))
      {
          // If both are NULL, return FALSE.
          SetLastError(ERROR_INVALID_PARAMETER);
          return FALSE;
      }
  
      CSTRING_TRY
      {    
          CString csNewApplicationName;
          CString csNewCommandLine;
          CString csPassedAppName(lpApplicationName);
          CString csPassedCommandLine(lpCommandLine);
          
          if ((csPassedAppName.IsEmpty()) &&
              (csPassedCommandLine.IsEmpty()))
          {
              goto exit;
          }
  
          // 
          // Run the list of New stubs: call to the main New routine
          //
          if (Redirect(csPassedAppName, csPassedCommandLine, csNewApplicationName, 
                  csNewCommandLine, FALSE))
          {
              LOGN(
                  eDbgLevelWarning,
                  "[CreateProcessA] \" %s %s \": changed to \" %s %s \"",
                  lpApplicationName, lpCommandLine, 
                  csNewApplicationName.GetAnsi(), csNewCommandLine.GetAnsi());
          }
          else
          {
              csNewApplicationName = lpApplicationName;
              csNewCommandLine = lpCommandLine;
          }
  
  
          // Convert back to ANSI using the GetAnsi() method exposed by the CString class.
          return ORIGINAL_API(CreateProcessA)(
              csNewApplicationName.IsEmpty() ? NULL : csNewApplicationName.GetAnsi(), 
              csNewCommandLine.IsEmpty() ? NULL : csNewCommandLine.GetAnsi(),  
              lpProcessAttributes, lpThreadAttributes, bInheritHandles, 
              dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo,             
              lpProcessInformation);
  
      }
      CSTRING_CATCH
      {
          DPFN( eDbgLevelError, "[CreateProcessA]:Original API called.Exception occured!");
          
      }
  
  exit:
      return ORIGINAL_API(CreateProcessA)(lpApplicationName, lpCommandLine,
                  lpProcessAttributes, lpThreadAttributes, bInheritHandles, 
                  dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo,             
                  lpProcessInformation);
  }
  
  /*++
  
   Hooks the CreateProcessW function to see if any News need to be 
   substituted. 
  
  --*/
  
  BOOL 
  APIHOOK(CreateProcessW)(
      LPCWSTR lpApplicationName,
      LPWSTR lpCommandLine,
      LPSECURITY_ATTRIBUTES lpProcessAttributes,
      LPSECURITY_ATTRIBUTES lpThreadAttributes,
      BOOL bInheritHandles,
      DWORD dwCreationFlags,
      LPVOID lpEnvironment,
      LPCWSTR lpCurrentDirectory,
      LPSTARTUPINFOW lpStartupInfo,
      LPPROCESS_INFORMATION lpProcessInformation
      )
  {
      if ((NULL == lpApplicationName) &&
         (NULL == lpCommandLine))
      {
          // If both are NULL, return FALSE.
          SetLastError(ERROR_INVALID_PARAMETER);
          return FALSE;        
      }
  
      CSTRING_TRY
      {    
          CString csNewApplicationName;
          CString csNewCommandLine;
          CString csApplicationName(lpApplicationName);
          CString csCommandLine(lpCommandLine);
  
          if ((csApplicationName.IsEmpty()) &&
              (csCommandLine.IsEmpty()))
          {
              goto exit;
          }
  
          // 
          // Run the list of New stubs
          //
  
          if (Redirect(csApplicationName, csCommandLine, csNewApplicationName, 
                  csNewCommandLine, FALSE)) 
          {    
              LOGN(
                  eDbgLevelWarning,
                  "[CreateProcessW] \" %S %S \": changed to \" %S %S \"",
                  lpApplicationName, lpCommandLine, csNewApplicationName, csNewCommandLine);            
          }
          else
          {
              csNewApplicationName = lpApplicationName;
              csNewCommandLine = lpCommandLine;
          }
  
  
          return ORIGINAL_API(CreateProcessW)(
              csNewApplicationName.IsEmpty() ? NULL : csNewApplicationName.Get(), 
              csNewCommandLine.IsEmpty() ? NULL : (LPWSTR)csNewCommandLine.Get(),  
              lpProcessAttributes, lpThreadAttributes, bInheritHandles, 
              dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo,
              lpProcessInformation);
      }
      CSTRING_CATCH
      {
          DPFN( eDbgLevelError, "[CreateProcessW] Original API called. Exception occured!");
      }
  
  exit:
      return ORIGINAL_API(CreateProcessW)(lpApplicationName, lpCommandLine, 
                  lpProcessAttributes, lpThreadAttributes, bInheritHandles, 
                  dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo,
                  lpProcessInformation);
  }
  
  /*++
  
   Hooks WinExec to redirect if necessary. 
  
  --*/
  
  UINT
  APIHOOK(WinExec)(
      LPCSTR lpCmdLine,
      UINT uCmdShow
      )
  {
      if (NULL == lpCmdLine)
      {
          SetLastError(ERROR_INVALID_PARAMETER);
          return ERROR_PATH_NOT_FOUND;
      }
  
      CSTRING_TRY
      {            
          CString csNewApplicationName;
          CString csNewCommandLine;
          CString csAppName;
          CString csNewCmdLine;
          CString csCommandLine(lpCmdLine);
          
          if (csCommandLine.IsEmpty())
          {
              goto exit;
          }
          // Check for redirection
          if (Redirect(csAppName, csCommandLine, csNewApplicationName,
                  csNewCommandLine, FALSE))
          {
              // Modification for the WinHlp32 strange behaviour
              if (csNewCommandLine.Find(csNewApplicationName.Get()) == -1)
              {
                  // If the new Command line does not contain the new application
                  // name as the substring, we are here.
                  csNewCmdLine = csNewApplicationName;                        
                  csNewCmdLine += L" ";
              }
              csNewCmdLine += csNewCommandLine;  
  
              // Assign to csCommandLine as this can be commonly used      
              csCommandLine = csNewCmdLine;
  
              LOGN(
                  eDbgLevelInfo,
                  "[WinExec] \" %s \": changed to \" %s \"",
                  lpCmdLine, csCommandLine.GetAnsi());       
          }
  
          return ORIGINAL_API(WinExec)(csCommandLine.GetAnsi(), uCmdShow);
  
      }
      CSTRING_CATCH
      {            
          DPFN( eDbgLevelError, "[WinExec]:Original API called.Exception occured!");        
      }
  
  exit:
      return ORIGINAL_API(WinExec)(lpCmdLine, uCmdShow);
  }
  
  /*++
  
   Hooks the FindFirstFileA function to see if any replacements need to be 
   substituted. This is a requirement for cmd.exe.
  
  --*/
  
  HANDLE
  APIHOOK(FindFirstFileA)(
      LPCSTR lpFileName,
      LPWIN32_FIND_DATAA lpFindFileData
      )
  {
      CSTRING_TRY
      {            
          CString csNewApplicationName;
          CString csNewCommandLine;
          CString csFileName(lpFileName);
          CString csAppName;
  
          // Call the main replacement routine.
          if (Redirect(csFileName, csAppName, csNewApplicationName, csNewCommandLine, FALSE)) 
          {     
              // Assign to csFileName
              csFileName = csNewApplicationName;
              LOGN(
                  eDbgLevelInfo,
                  "[FindFirstFileA] \" %s  \": changed to \" %s \"",
                  lpFileName, csFileName.GetAnsi());
          }
  
          return ORIGINAL_API(FindFirstFileA)(csFileName.GetAnsi(), lpFindFileData);
      }
      CSTRING_CATCH
      {
          DPFN( eDbgLevelError, "[FindFirstFileA]:Original API called.Exception occured!");        
          return ORIGINAL_API(FindFirstFileA)(lpFileName, lpFindFileData);
      }
  }
  
  /*++
  
   Hooks the FindFirstFileW function to see if any replacements need to be 
   substituted. This is a requirement for cmd.exe.
  
  --*/
  
  HANDLE
  APIHOOK(FindFirstFileW)(
      LPCWSTR lpFileName,
      LPWIN32_FIND_DATAW lpFindFileData
      )
  {
      CSTRING_TRY
      {    
          CString csNewApplicationName(lpFileName);
          CString csNewCommandLine;
          CString csFileName(lpFileName);
          CString csAppName;
      
          // Call the main replacement routine.
          if (Redirect(csFileName, csAppName, csNewApplicationName, 
                  csNewCommandLine, FALSE))
          {
              LOGN(
                  eDbgLevelInfo,
                  "[FindFirstFileW] \" %S \": changed to \" %S \"",
                  lpFileName, (const WCHAR*)csNewApplicationName);
          }
  
          return ORIGINAL_API(FindFirstFileW)(csNewApplicationName, lpFindFileData);
      }
      CSTRING_CATCH
      {
          DPFN( eDbgLevelError, "[FindFirstFileW]:Original API called.Exception occured!");
          return ORIGINAL_API(FindFirstFileW)(lpFileName, lpFindFileData);
      }
  }
  
  // Added for the merge of HandleStartKeyword
  
  /*++
  
   Hook IShellLinkA::SetPath - check if it's start, if so change it to cmd and add the 
   this pointer to the list.
  
  --*/
  
  HRESULT STDMETHODCALLTYPE
  COMHOOK(IShellLinkA, SetPath)(
      PVOID pThis,
      LPCSTR pszFile
      )
  {
      _pfn_IShellLinkA_SetPath pfnSetPath = ORIGINAL_COM( IShellLinkA, SetPath, pThis);
  
      CSTRING_TRY
      {   
          CString csExeName;
          CString csCmdLine;
          CString csNewAppName;
          CString csNewCmdLine;
          CString cscmdCommandLine(pszFile);
  
          // Assign the ANSI string to the WCHAR CString
          csExeName = pszFile;
          csExeName.TrimLeft();
          
          // Check to see whether the Filename conatains the "Start" keyword.
          // The last parameter to the Rediect function controls this.
          if (Redirect(csExeName, csCmdLine,  csNewAppName, csNewCmdLine, TRUE))
          {
              // Found a match. We add the this pointer to the list.
              AddThisPointer(pThis);
              DPFN( eDbgLevelInfo, "[SetPath] Changing start.exe to cmd.exe\n");
  
              // Prefix of new "start" command line, use full path to CMD.EXE                  
              // Append the WCHAR global system directory path to ANSI CString
              cscmdCommandLine = g_szSysDir;
              cscmdCommandLine += L"\\cmd.exe";                   
          }
  
          return (*pfnSetPath)(pThis, cscmdCommandLine.GetAnsi());
      }
      CSTRING_CATCH
      {
          DPFN( eDbgLevelError, "[SetPath] Original API called. Exception occured!");
          return (*pfnSetPath)(pThis, pszFile);
      }
  }
  
  /*++
  
   Hook IShellLinkA::SetArguments - if the this pointer can be found in the list, remove it
   from the list and add "/d /c start" in front of the original argument list.
  
  --*/
  
  HRESULT STDMETHODCALLTYPE
  COMHOOK(IShellLinkA, SetArguments)(
      PVOID pThis,
      LPCSTR pszFile 
      )
  {
      _pfn_IShellLinkA_SetArguments pfnSetArguments = ORIGINAL_COM(IShellLinkA, SetArguments, pThis);
  
      CSTRING_TRY
      {    
          CString csNewFile(pszFile);        
          if (RemoveThisPointer(pThis))
          {
              csNewFile = "/d /c start \"\" ";
              csNewFile += pszFile;
  
              DPFN( eDbgLevelInfo, "[SetArguments] Arg list is now %S", csNewFile);        
          }
  
          return (*pfnSetArguments)( pThis, csNewFile.GetAnsi());
      }
      CSTRING_CATCH
      {
          DPFN( eDbgLevelError, "[SetArguments]:Original API called.Exception occured!");
          return (*pfnSetArguments)( pThis, pszFile );
      }  
  }
  
  /*++
  
   Register hooked functions
  
  --*/
  
  BOOL
  NOTIFY_FUNCTION(
      DWORD fdwReason
      )
  {
      if (fdwReason == DLL_PROCESS_ATTACH) 
      {
          UINT uiLen = GetSystemDirectory(NULL, 0);
          g_szSysDir = (WCHAR*)malloc(uiLen * sizeof(WCHAR));  // This won't be deallocated..
          if (g_szSysDir && !GetSystemDirectory(g_szSysDir, uiLen))
          {
              DPFN( eDbgLevelError, "[Notify] GetSystemDirectory failed");
              return FALSE;
          }
  
          if (InitializeCriticalSectionAndSpinCount(&g_CritSec, 0x80000000) == FALSE)
          {
              DPFN( eDbgLevelError, "Failed to initialize critical section");
              return FALSE;            
          }
      }
      
      return TRUE;
  }
  
  HOOK_BEGIN
  
      CALL_NOTIFY_FUNCTION
  
      APIHOOK_ENTRY(KERNEL32.DLL, CreateProcessA)
      APIHOOK_ENTRY(KERNEL32.DLL, CreateProcessW)
      APIHOOK_ENTRY(KERNEL32.DLL, WinExec)
      APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileA)
      APIHOOK_ENTRY(KERNEL32.DLL, FindFirstFileW)
      APIHOOK_ENTRY_COMSERVER(SHELL32)
      COMHOOK_ENTRY(ShellLink, IShellLinkA, SetPath, 20)
      COMHOOK_ENTRY(ShellLink, IShellLinkA, SetArguments, 11)
  
  HOOK_END
  
  IMPLEMENT_SHIM_END
  
  
  