xref: /trunk/main/desktop/win32/source/setup/setup.cpp (revision 91144cd0085a7583d2099b982122deb2184ab956)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_desktop.hxx"
24 
25 #define  UNICODE    1
26 #define _UNICODE    1
27 
28 #define WIN // scope W32 API
29 
30 #if defined _MSC_VER
31 #pragma warning(push, 1)
32 #endif
33 #include <windows.h>
34 #if defined _MSC_VER
35 #pragma warning(pop)
36 #endif
37 #include <tchar.h>
38 #include <assert.h>
39 #include <shlwapi.h>
40 #include <new>
41 #include <time.h>
42 #include <mbctype.h>
43 #include <locale.h>
44 #include <Msiquery.h>
45 #include <MsiDefs.h>
46 #include "strsafe.h"
47 
48 #include "setup.hxx"
49 #include "aoo_msi.hxx"
50 
51 #include "resource.h"
52 
53 //--------------------------------------------------------------------------
54 
55 #define MAX_STR_LENGTH     32000
56 #define MAX_TEXT_LENGTH     1024
57 #define MAX_LANGUAGE_LEN      80
58 #define MAX_STR_CAPTION      256
59 #define VERSION_SIZE          80
60 #define SECTION_SETUP       TEXT( "Setup" )
61 #define SECTION_LANGUAGE    TEXT( "Languages" )
62 #define PRODUCT_NAME_VAR    TEXT( "%PRODUCTNAME" )
63 #define PRODUCT_VERSION     TEXT( "ProductVersion" )
64 #define ERROR_SHOW_USAGE      -2
65 #define ERROR_SETUP_TO_OLD    -3
66 #define ERROR_SETUP_NOT_FOUND -4
67 #define ERROR_OS_TO_OLD       -5
68 #define ERROR_RUNTIME_FAILED  -6
69 
70 // Lowest Windows this office can run on.  Not an arbitrary policy: the VC v14
71 // redistributable that supplies the runtime the binaries need installs only on
72 // Windows 10/11 and Server 2016 and later, so the floor comes with the toolset.
73 #define REQUIRED_WINDOWS_MAJOR  10
74 
75 #define PARAM_SETUP_USED    TEXT( " SETUP_USED=1 " )
76 #define PARAM_PACKAGE       TEXT( "/I " )
77 #define PARAM_MINOR_UPGRADE TEXT( "/FVOMUS " )
78 #define PARAM_ADMIN         TEXT( "/A " )
79 #define PARAM_TRANSFORM     TEXT( " TRANSFORMS=" )
80 #define PARAM_REBOOT        TEXT( " REBOOT=Force" )
81 #define PARAM_PATCH         TEXT( " /update " )
82 #define PARAM_REG_ALL_MSO_TYPES TEXT( "REGISTER_ALL_MSO_TYPES=1 " )
83 #define PARAM_REG_NO_MSO_TYPES  TEXT( "REGISTER_NO_MSO_TYPES=1 " )
84 // The VC v14 redistributable is a Burn bundle, not the old MSI-style installer the
85 // VC++ 2008 package was.  It does not understand /Q; its silent switches are these.
86 // /norestart matters -- without it the bundle may reboot the machine mid-install.
87 #define PARAM_SILENTINSTALL     TEXT( " /install /quiet /norestart" )
88 
89 // Burn exit codes that mean "the runtime is now present".  1638 is
90 // ERROR_PRODUCT_VERSION: a NEWER runtime is already installed, which is success for
91 // our purposes -- it is also the common case on any developer machine.
92 #define RUNTIME_INSTALL_OK          0L
93 #define RUNTIME_INSTALL_NEWER       1638L
94 #define RUNTIME_INSTALL_REBOOT_REQ  3010L
95 
96 #define PARAM_RUNNING           TEXT( "ignore_running" )
97 #define CMDLN_REG_ALL_MSO_TYPES TEXT( "msoreg=1" )
98 #define CMDLN_REG_NO_MSO_TYPES  TEXT( "msoreg=0" )
99 
100 #define ADVAPI32_DLL        TEXT( "advapi32.dll" )
101 #define PROFILE_NAME        TEXT( "setup.ini" )
102 
103 #define RUNTIME_X64_NAME    TEXT( "redist\\vcredist_x64.exe" )
104 #define RUNTIME_X86_NAME    TEXT( "redist\\vcredist_x86.exe" )
105 
106 // There is deliberately no ProductCode here any more.  The old code gated on
107 // MsiQueryProductState() against a hardcoded VC++ 2008 GUID, which cannot work for the
108 // v14 runtime: Microsoft reissues that redistributable with a NEW ProductCode on every
109 // servicing revision, so an exact-GUID test misreports every machine that has anything
110 // other than the one pinned build.  We test for the runtime functionally instead --
111 // see RuntimeAlreadyPresent().
112 
113 #define ADVAPI32API_CheckTokenMembership "CheckTokenMembership"
114 
115 typedef BOOL (WINAPI* PFnCheckTokenMembership)(HANDLE TokenHandle, PSID SidToCheck, PBOOL IsMember);
116 
117 #ifdef DEBUG
OutputDebugStringFormat(LPCTSTR pFormat,...)118 inline void OutputDebugStringFormat( LPCTSTR pFormat, ... )
119 {
120     TCHAR    buffer[1024];
121     va_list  args;
122 
123     va_start( args, pFormat );
124     StringCchVPrintf( buffer, sizeof(buffer), pFormat, args );
125     OutputDebugString( buffer );
126 }
127 #else
OutputDebugStringFormat(LPCTSTR,...)128 static inline void OutputDebugStringFormat( LPCTSTR, ... )
129 {
130 }
131 #endif
132 
133 //--------------------------------------------------------------------------
134 
135 const TCHAR sMsiExe[]        = TEXT( "\\msiexec.exe" );
136 const TCHAR sDelayReboot[]   = TEXT( " /c:\"msiinst /delayreboot\"" );
137 const TCHAR sMsiQuiet[]      = TEXT( " /q" );
138 const TCHAR sMemMapName[]    = TEXT( "Global\\MsiErrorObject" );
139 
140 //--------------------------------------------------------------------------
SetupApp()141 SetupApp::SetupApp()
142 {
143     m_uiRet         = ERROR_SUCCESS;
144 
145     // Get OS version
146     OSVERSIONINFO sInfoOS;
147 
148     ZeroMemory( &sInfoOS, sizeof(OSVERSIONINFO) );
149     sInfoOS.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
150 
151     GetVersionEx( &sInfoOS );
152 
153     m_nOSVersion    = sInfoOS.dwMajorVersion;
154     m_nMinorVersion = sInfoOS.dwMinorVersion;
155     m_bIsWin9x      = ( VER_PLATFORM_WIN32_NT != sInfoOS.dwPlatformId );
156     m_bNeedReboot   = false;
157     m_bAdministrative = false;
158 
159     m_hInst     = NULL;
160     m_hMapFile  = NULL;
161     m_pAppTitle = NULL;
162     m_pCmdLine  = NULL;
163 
164     m_pDatabase = NULL;
165     m_pReqVersion   = NULL;
166     m_pProductName  = NULL;
167     m_pAdvertise    = NULL;
168     m_pTmpName      = NULL;
169     m_pLogFile      = NULL;
170     m_pModuleFile   = NULL;
171     m_pPatchFiles   = NULL;
172     m_pMSIErrorCode = NULL;
173     m_pUpgradeKey   = NULL;
174     m_pProductVersion = NULL;
175 
176     m_pErrorText    = new TCHAR[ MAX_TEXT_LENGTH ];
177     m_pErrorText[0] = '\0';
178 
179     m_nLanguageID     = 0;
180     m_nLanguageCount  = 0;
181     m_ppLanguageList  = NULL;
182 
183     m_bQuiet          = false;
184     m_bRegNoMsoTypes  = false;
185     m_bRegAllMsoTypes = false;
186     m_bIsMinorUpgrade = false;
187     m_bSupportsPatch  = false;
188 
189     m_bIgnoreAlreadyRunning = false;
190 }
191 
192 //--------------------------------------------------------------------------
~SetupApp()193 SetupApp::~SetupApp()
194 {
195     if ( m_ppLanguageList )
196     {
197         for ( int i = 0; i < m_nLanguageCount; i++ )
198             if ( m_ppLanguageList[i] )
199                 delete m_ppLanguageList[ i ];
200         delete [] m_ppLanguageList;
201     }
202 
203     time_t aTime;
204     time( &aTime );
205     tm *pTime = localtime( &aTime );   // Convert time to struct tm form
206 
207     Log( TEXT( "End: %s\n\r\n\r\n" ), _tasctime( pTime ) );
208 
209     if ( m_pLogFile ) fclose( m_pLogFile );
210 
211     if ( m_pTmpName )
212     {
213         _tremove( m_pTmpName );
214         free( m_pTmpName );
215     }
216 
217     if ( m_pMSIErrorCode ) UnmapViewOfFile( m_pMSIErrorCode );
218     if ( m_hMapFile ) CloseHandle( m_hMapFile );
219 
220     if ( m_pAppTitle ) delete [] m_pAppTitle;
221     if ( m_pDatabase ) delete [] m_pDatabase;
222     if ( m_pReqVersion ) delete [] m_pReqVersion;
223     if ( m_pProductName ) delete [] m_pProductName;
224     if ( m_pAdvertise )   delete [] m_pAdvertise;
225     if ( m_pLogFile )     delete [] m_pLogFile;
226     if ( m_pErrorText )   delete [] m_pErrorText;
227     if ( m_pModuleFile )  delete [] m_pModuleFile;
228     if ( m_pPatchFiles )  delete [] m_pPatchFiles;
229     if ( m_pUpgradeKey )  delete [] m_pUpgradeKey;
230     if ( m_pProductVersion ) delete [] m_pProductVersion;
231 }
232 
233 //--------------------------------------------------------------------------
Initialize(HINSTANCE hInst)234 boolean SetupApp::Initialize( HINSTANCE hInst )
235 {
236     m_pCmdLine  = WIN::GetCommandLine();
237     m_hInst     = hInst;
238 
239     // Load our AppTitle (caption)
240     m_pAppTitle     = new TCHAR[ MAX_STR_CAPTION ];
241     m_pAppTitle[0]  = '\0';
242     WIN::LoadString( hInst, IDS_APP_TITLE, m_pAppTitle, MAX_STR_CAPTION );
243 
244     // Obtain path we are running from
245     m_pModuleFile       = new TCHAR[ MAX_PATH ];
246     m_pModuleFile[ 0 ]  = '\0';
247 
248     if ( 0 == WIN::GetModuleFileName( hInst, m_pModuleFile, MAX_PATH ) )
249     {
250         SetError( WIN::GetLastError() );
251         return false;
252     }
253 
254     if ( ! GetCmdLineParameters( &m_pCmdLine ) )
255         return false;
256 
257     m_hMapFile = CreateFileMapping(
258                  INVALID_HANDLE_VALUE,      // use paging file
259                  NULL,                      // default security
260                  PAGE_READWRITE,            // read/write access
261                  0,                         // max. object size
262                  sizeof( int ),             // buffer size
263                  sMemMapName );
264     if ( m_hMapFile )
265     {
266         m_pMSIErrorCode = (int*) MapViewOfFile( m_hMapFile,  // handle to map object
267                         FILE_MAP_ALL_ACCESS,   // read/write permission
268                         0,
269                         0,
270                         sizeof( int ) );
271         if ( m_pMSIErrorCode )
272             *m_pMSIErrorCode = 0;
273         else
274             OutputDebugStringFormat( TEXT("Could not map view of file (%d).\n"), GetLastError() );
275     }
276     else
277         OutputDebugStringFormat( TEXT("Could not create file mapping object (%d).\n"), GetLastError() );
278 
279     Log( TEXT("Starting: %s\r\n"), m_pModuleFile );
280     Log( TEXT(" CommandLine=<%s>\r\n"), m_pCmdLine );
281 
282     if ( m_bQuiet )
283         Log( TEXT(" Using quiet install mode\r\n") );
284 
285     time_t aTime;
286     time( &aTime );
287     tm* pTime = localtime( &aTime );
288     Log( TEXT(" Begin: %s\n"), _tasctime( pTime ) );
289 
290     return true;
291 }
292 
293 //--------------------------------------------------------------------------
GetProfileSection(LPCTSTR pFileName,LPCTSTR pSection,DWORD & rSize,LPTSTR * pRetBuf)294 boolean SetupApp::GetProfileSection( LPCTSTR pFileName, LPCTSTR pSection,
295                                       DWORD& rSize, LPTSTR *pRetBuf )
296 {
297     if ( !rSize || !*pRetBuf )
298     {
299         rSize = 512;
300         *pRetBuf = new TCHAR[ rSize ];
301     }
302 
303     DWORD nRet = GetPrivateProfileSection( pSection, *pRetBuf, rSize, pFileName );
304 
305     if ( nRet && ( nRet + 2 > rSize ) ) // buffer was too small, retry with bigger one
306     {
307         if ( nRet < 32767 - 2 )
308         {
309             delete [] (*pRetBuf);
310             rSize = nRet + 2;
311             *pRetBuf = new TCHAR[ rSize ];
312 
313             nRet = GetPrivateProfileSection( pSection, *pRetBuf, rSize, pFileName );
314         }
315     }
316 
317     if ( !nRet )
318     {
319         SetError( WIN::GetLastError() );
320 
321         TCHAR sBuf[80];
322         StringCchPrintf( sBuf, 80, TEXT("ERROR: GetPrivateProfileSection(): GetLastError returned %u\r\n"), GetError() );
323         Log( sBuf );
324         return false;
325     }
326     else if ( nRet + 2 > rSize )
327     {
328         SetError( ERROR_OUTOFMEMORY );
329         Log( TEXT( "ERROR: GetPrivateProfileSection() out of memory\r\n" ) );
330         return false;
331     }
332 
333     Log( TEXT( " GetProfileSection read %s\r\n" ), pSection );
334 
335     return true;
336 }
337 
338 //--------------------------------------------------------------------------
ReadProfile()339 boolean SetupApp::ReadProfile()
340 {
341     boolean bRet = false;
342     TCHAR *sProfilePath = 0;
343 
344     if ( GetPathToFile( PROFILE_NAME, &sProfilePath ) )
345     {
346         DWORD nSize = 0;
347         LPTSTR pRetBuf = NULL;
348 
349         Log( TEXT( " Open ini file: <%s>\r\n" ), sProfilePath );
350 
351         bRet = GetProfileSection( sProfilePath, SECTION_SETUP, nSize, &pRetBuf );
352 
353         if ( !bRet )
354         {
355             LPTSTR pTmpFile = CopyIniFile( sProfilePath );
356             delete [] sProfilePath;
357             sProfilePath = pTmpFile;
358 
359             if ( sProfilePath )
360             {
361                 SetError( ERROR_SUCCESS );
362 
363                 Log( TEXT( " Could not open inifile, copied ini file to: <%s>\r\n" ), sProfilePath );
364                 bRet = GetProfileSection( sProfilePath, SECTION_SETUP, nSize, &pRetBuf );
365             }
366         }
367 
368         if ( bRet )
369         {
370             LPTSTR pCurLine = pRetBuf;
371             while ( *pCurLine )
372             {
373                 LPTSTR pName = 0;
374                 LPTSTR pValue = 0;
375 
376                 pCurLine += GetNameValue( pCurLine, &pName, &pValue );
377 
378                 if ( lstrcmpi( TEXT( "database" ), pName ) == 0 )
379                 {
380                     m_pDatabase = pValue;
381                     Log( TEXT( "    Database = %s\r\n" ), pValue );
382                 }
383                 else if ( lstrcmpi( TEXT( "msiversion" ), pName ) == 0 )
384                 {
385                     m_pReqVersion = pValue;
386                     Log( TEXT( "    msiversion = %s\r\n" ), pValue );
387                 }
388                 else if ( lstrcmpi( TEXT( "productname" ), pName ) == 0 )
389                 {
390                     m_pProductName = pValue;
391                     Log( TEXT( "    productname = %s\r\n" ), pValue );
392                     m_pAppTitle = SetProdToAppTitle( m_pProductName );
393                 }
394                 else if ( lstrcmpi( TEXT( "upgradekey" ), pName ) == 0 )
395                 {
396                     m_pUpgradeKey = pValue;
397                     Log( TEXT( "    upgradekey = %s\r\n" ), pValue );
398                 }
399                 else if ( lstrcmpi( TEXT( "productversion" ), pName ) == 0 )
400                 {
401                     m_pProductVersion = pValue;
402                     Log( TEXT( "    productversion = %s\r\n" ), pValue );
403                 }
404                 else if ( lstrcmpi( TEXT( "productcode" ), pName ) == 0 )
405                 {
406                     delete [] pValue;
407                 }
408                 else
409                 {
410                     Log( TEXT( "Warning: unknown entry in profile <%s>\r\n" ), pName );
411                     delete [] pValue;
412                 }
413             }
414         }
415 
416         if ( bRet && ( !m_pDatabase || !m_pReqVersion || !m_pProductName ) )
417         {
418             Log( TEXT( "ERROR: incomplete 'Setup' section in profile\r\n" ) );
419             SetError( ERROR_INVALID_DATA );
420             bRet = false;
421         }
422 
423         if ( bRet )
424             bRet = GetProfileSection( sProfilePath, SECTION_LANGUAGE, nSize, &pRetBuf );
425 
426         if ( bRet )
427         {
428             LPTSTR pName = 0;
429             LPTSTR pValue = 0;
430             LPTSTR pCurLine = pRetBuf;
431             LPTSTR pLastChar;
432             int  nNext = 0;
433 
434             // first line in this section should be the language count
435             nNext = GetNameValue( pCurLine, &pName, &pValue );
436             if ( lstrcmpi( TEXT( "count" ), pName ) == 0 )
437             {
438                 Log( TEXT( "    Languages = %s\r\n" ), pValue );
439                 m_nLanguageCount = _tcstol( pValue, &pLastChar, 10 );
440                 pCurLine += nNext;
441                 delete [] pValue;
442             }
443 
444             m_ppLanguageList = new LanguageData*[ m_nLanguageCount ];
445 
446             for ( int i=0; i < m_nLanguageCount; i++ )
447             {
448                 if ( !*pCurLine )
449                 {
450                     m_nLanguageCount = i;
451                     break;
452                 }
453 
454                 pCurLine += GetNameValue( pCurLine, &pName, &pValue );
455                 m_ppLanguageList[ i ] = new LanguageData( pValue );
456                 Log( TEXT( "    Language = %s\r\n" ), pValue );
457 
458                 if ( m_ppLanguageList[ i ]->m_pTransform )
459                     Log( TEXT( "      Transform = %s\r\n" ), m_ppLanguageList[ i ]->m_pTransform );
460 
461                 delete [] pValue;
462             }
463         }
464 
465         if ( pRetBuf )
466             delete [] pRetBuf;
467     }
468 
469     if ( sProfilePath && ! m_pTmpName )
470         delete [] sProfilePath;
471 
472     return bRet;
473 }
474 
475 //--------------------------------------------------------------------------
AddFileToPatchList(TCHAR * pPath,TCHAR * pFile)476 void SetupApp::AddFileToPatchList( TCHAR* pPath, TCHAR* pFile )
477 {
478     if ( m_pPatchFiles == NULL )
479     {
480         m_pPatchFiles = new TCHAR[ MAX_STR_LENGTH ];
481         StringCchCopy( m_pPatchFiles, MAX_STR_LENGTH, TEXT("\"") );
482     }
483     else
484         StringCchCat( m_pPatchFiles, MAX_STR_LENGTH, TEXT(";") );
485 
486     StringCchCat( m_pPatchFiles, MAX_STR_LENGTH, pPath );
487     StringCchCat( m_pPatchFiles, MAX_STR_LENGTH, pFile );
488 }
489 
490 //--------------------------------------------------------------------------
GetPatches()491 boolean SetupApp::GetPatches()
492 {
493     boolean bRet = true;
494 
495     int nPatternLen = lstrlen( m_pModuleFile ) + 7; // 1 for null terminator, 1 for back slash, 5 for extensions
496     TCHAR* pPattern = new TCHAR[ nPatternLen ];
497     TCHAR* pBaseDir = new TCHAR[ nPatternLen ];
498 
499     // find 'setup.exe' in the path so we can remove it
500     TCHAR *pFilePart = 0;
501     if ( 0 == GetFullPathName( m_pModuleFile, nPatternLen, pPattern, &pFilePart ) )
502     {
503         SetError( WIN::GetLastError() );
504         bRet = false;
505     }
506     else
507     {
508         if ( pFilePart )
509             *pFilePart = '\0';
510         StringCchCopy( pBaseDir, nPatternLen, pPattern );
511         StringCchCat( pPattern, nPatternLen, TEXT("*.msp") );
512 
513         WIN32_FIND_DATA aFindFileData;
514 
515         HANDLE hFindPatches = FindFirstFile( pPattern, &aFindFileData );
516 
517         if ( hFindPatches != INVALID_HANDLE_VALUE )
518         {
519             if ( ! IsPatchInstalled( pBaseDir, aFindFileData.cFileName ) )
520                 AddFileToPatchList( pBaseDir, aFindFileData.cFileName );
521 
522             while ( FindNextFile( hFindPatches, &aFindFileData ) )
523             {
524                 if ( ! IsPatchInstalled( pBaseDir, aFindFileData.cFileName ) )
525                     AddFileToPatchList( pBaseDir, aFindFileData.cFileName );
526             }
527 
528             if ( m_pPatchFiles != NULL )
529                 StringCchCat( m_pPatchFiles, MAX_STR_LENGTH, TEXT("\"") );
530 
531             FindClose( hFindPatches );
532         }
533     }
534 
535     delete [] pPattern;
536     delete [] pBaseDir;
537 
538     return bRet;
539 }
540 
541 //--------------------------------------------------------------------------
GetPathToFile(TCHAR * pFileName,TCHAR ** pPath)542 boolean SetupApp::GetPathToFile( TCHAR* pFileName, TCHAR** pPath )
543 {
544     // generate the path to the file = szModuleFile + FileName
545     // note: FileName is a relative path
546 
547     boolean bRet = true;
548 
549     int nTempPath = lstrlen( m_pModuleFile ) + lstrlen( pFileName ) + 2; // 1 for null terminator, 1 for back slash
550     TCHAR* pTempPath = new TCHAR[ nTempPath ];
551 
552     // find 'setup.exe' in the path so we can remove it
553     TCHAR *pFilePart = 0;
554     if ( 0 == GetFullPathName( m_pModuleFile, nTempPath, pTempPath, &pFilePart ) )
555     {
556         SetError( WIN::GetLastError() );
557         bRet = false;
558     }
559     else
560     {
561         if ( pFilePart )
562             *pFilePart = '\0';
563 
564         StringCchCat( pTempPath, nTempPath, pFileName );
565 
566         int nPath = 2 * nTempPath;
567         *pPath = new TCHAR[ nPath ];
568 
569         // normalize the path
570         int nReturn = GetFullPathName( pTempPath, nPath, *pPath, &pFilePart );
571 
572         if ( nReturn > nPath )
573         {
574             // try again, with larger buffer
575             delete [] (*pPath);
576             nPath = nReturn;
577             *pPath = new TCHAR[ nPath ];
578 
579             nReturn = GetFullPathName( pTempPath, nPath, *pPath, &pFilePart );
580         }
581 
582         if ( 0 == nReturn )
583         {
584             // error -- invalid path
585             SetError( WIN::GetLastError() );
586             bRet = false;
587         }
588     }
589 
590     if ( bRet ) // check for the file's existence
591     {
592         DWORD dwFileAttrib = GetFileAttributes( *pPath );
593 
594         if (0xFFFFFFFF == dwFileAttrib)
595         {
596             StringCchCopy( m_pErrorText, MAX_TEXT_LENGTH, pFileName );
597             SetError( ERROR_FILE_NOT_FOUND );
598             bRet = false;
599         }
600     }
601 
602     delete [] pTempPath;
603     return bRet;
604 }
605 
606 //--------------------------------------------------------------------------
GetNameValue(TCHAR * pLine,TCHAR ** pName,TCHAR ** pValue)607 int SetupApp::GetNameValue( TCHAR* pLine, TCHAR** pName, TCHAR** pValue )
608 {
609     int nRet = lstrlen( pLine ) + 1;
610     *pValue = 0;
611 
612     if ( nRet == 1 )
613         return nRet;
614 
615     LPTSTR pChar = pLine;
616     LPTSTR pLast = NULL;
617 
618     // Skip leading spaces.
619     while (' ' == *pChar || '\t' == *pChar)
620         pChar = CharNext( pChar );
621 
622     *pName = pChar;
623 
624     // look for the end of the name
625     while( *pChar && (' ' != *pChar) &&
626            ( '\t' != *pChar ) && ( '=' != *pChar ) )
627         pChar = CharNext( pChar );
628 
629     if ( ! *pChar )
630         return nRet;
631 
632     pLast = pChar;
633     pChar = CharNext( pChar );
634     *pLast = '\0';
635 
636     // look for the start of the value
637     while( ( ' ' == *pChar ) || ( '\t' == *pChar ) ||
638            ( '=' == *pChar ) )
639         pChar = CharNext( pChar );
640 
641     int nValueLen = lstrlen( pChar ) + 1;
642     *pValue = new TCHAR[ nValueLen ];
643 
644     if ( *pValue )
645         StringCchCopy( *pValue, nValueLen, pChar );
646 
647     return nRet;
648 }
649 
650 //--------------------------------------------------------------------------
ChooseLanguage(long & rLanguage)651 boolean SetupApp::ChooseLanguage( long& rLanguage )
652 {
653     rLanguage = 0;
654 
655     if ( m_bQuiet )
656         return true;
657 
658     // When there are none or only one language, there is nothing
659     // to do here
660     if ( m_nLanguageCount > 1 )
661     {
662         TCHAR *sString = new TCHAR[ MAX_LANGUAGE_LEN ];
663 
664         LANGID nUserDefLang = GetUserDefaultLangID();
665         LANGID nSysDefLang = GetSystemDefaultLangID();
666 
667         int nUserPrimary = PRIMARYLANGID( nUserDefLang );
668         int nSysPrimary = PRIMARYLANGID( nSysDefLang );
669 
670         long nUserIndex = -1;
671         long nUserPrimIndex = -1;
672         long nSystemIndex = -1;
673         long nSystemPrimIndex = -1;
674         long nParamIndex = -1;
675 
676         for ( long i=0; i<GetLanguageCount(); i++ )
677         {
678             long nLanguage = GetLanguageID( i );
679             int nPrimary = PRIMARYLANGID( nLanguage );
680             GetLanguageName( nLanguage, sString );
681             Log( TEXT( "    Info: found Language: %s\r\n" ), sString );
682 
683             if ( nLanguage == nUserDefLang )
684                 nUserIndex = i;
685             if ( nPrimary == nUserPrimary )
686                 nUserPrimIndex = i;
687             if ( nLanguage == nSysDefLang )
688                 nSystemIndex = i;
689             if ( nPrimary == nSysPrimary )
690                 nSystemPrimIndex = i;
691             if ( m_nLanguageID && ( nLanguage == m_nLanguageID ) )
692                 nParamIndex = i;
693         }
694 
695         if ( m_nLanguageID && ( nParamIndex == -1 ) )
696         {
697             Log( TEXT( "Warning: Language chosen with parameter -lang not found.\r\n" ) );
698         }
699 
700         if ( nParamIndex != -1 )
701         {
702             Log( TEXT( "Info: Found language chosen with parameter -lang.\r\n" ) );
703             rLanguage = GetLanguageID( nParamIndex );
704         }
705         else if ( nUserIndex != -1 )
706         {
707             Log( TEXT( "Info: Found user default language.\r\n" ) );
708             rLanguage = GetLanguageID( nUserIndex );
709         }
710         else if ( nUserPrimIndex != -1 )
711         {
712             Log( TEXT( "Info: Found user default primary language.\r\n" ) );
713             rLanguage = GetLanguageID( nUserPrimIndex );
714         }
715         else if ( nSystemIndex != -1 )
716         {
717             Log( TEXT( "Info: Found system default language.\r\n" ) );
718             rLanguage = GetLanguageID( nSystemIndex );
719         }
720         else if ( nSystemPrimIndex != -1 )
721         {
722             Log( TEXT( "Info: Found system default primary language.\r\n" ) );
723             rLanguage = GetLanguageID( nSystemPrimIndex );
724         }
725         else
726         {
727             Log( TEXT( "Info: Use default language from ini file.\r\n" ) );
728             rLanguage = GetLanguageID( 0 );
729         }
730         delete [] sString;
731     }
732 
733     return true;
734 }
735 
736 
737 //--------------------------------------------------------------------------
GetPathToMSI()738 LPCTSTR SetupApp::GetPathToMSI()
739 {
740     LPTSTR  sMsiPath = NULL;
741     HKEY    hInstKey = NULL;
742     TCHAR  *sMsiFolder = getInstallerLocation();
743     DWORD   nMsiFolderSize = MAX_PATH + 1;
744 
745     if ( sMsiFolder[0] == '\0' ) // use the default location
746     {
747         Log( TEXT( "  Could not find path to msiexec.exe in registry" ) );
748 
749         DWORD nRet = WIN::GetSystemDirectory( sMsiFolder, nMsiFolderSize );
750         if ( nRet > nMsiFolderSize )
751         {
752             delete [] sMsiFolder;
753             sMsiFolder = new TCHAR[ nRet ];
754             nMsiFolderSize = nRet;
755 
756             nRet = WIN::GetSystemDirectory( sMsiFolder, nMsiFolderSize );
757         }
758         if ( 0 == nRet )
759         {
760             sMsiFolder[0] = '\0';
761             SetError( WIN::GetLastError() );
762         }
763         nMsiFolderSize = nRet;
764     }
765 
766     if ( sMsiFolder[0] != '\0' )
767     {
768         int nLength = lstrlen( sMsiExe ) + lstrlen( sMsiFolder ) + 1;
769         sMsiPath = new TCHAR[ nLength ];
770 
771         if ( FAILED( StringCchCopy( sMsiPath, nLength, sMsiFolder ) ) ||
772              FAILED( StringCchCat( sMsiPath, nLength, sMsiExe ) ) )
773         {
774             delete [] sMsiPath;
775             sMsiPath = NULL;
776         }
777     }
778 
779     if ( ! sMsiPath )
780         Log( TEXT( "ERROR: Can't build path to msiexec.exe!" ) );
781 
782     return sMsiPath;
783 }
784 
785 //--------------------------------------------------------------------------
LaunchInstaller(LPCTSTR pParam)786 boolean SetupApp::LaunchInstaller( LPCTSTR pParam )
787 {
788     LPCTSTR sMsiPath = GetPathToMSI();
789 
790     if ( !sMsiPath )
791     {
792         Log( TEXT( "ERROR: msiexec not found!" ) );
793         SetError( ERROR_FILE_NOT_FOUND );
794         return false;
795     }
796 
797     STARTUPINFO         aSUI;
798     PROCESS_INFORMATION aPI;
799 
800     Log( TEXT( " Will install using <%s>\r\n" ), sMsiPath );
801     Log( TEXT( "   Parameters are: %s\r\n" ), pParam );
802 
803     OutputDebugStringFormat( TEXT( " Will install using <%s>\r\n" ), sMsiPath );
804     OutputDebugStringFormat( TEXT( "   Parameters are: %s\r\n" ), pParam );
805 
806     ZeroMemory( (void*)&aPI, sizeof( PROCESS_INFORMATION ) );
807     ZeroMemory( (void*)&aSUI, sizeof( STARTUPINFO ) );
808 
809     aSUI.cb          = sizeof(STARTUPINFO);
810     aSUI.dwFlags     = STARTF_USESHOWWINDOW;
811     aSUI.wShowWindow = SW_SHOW;
812 
813     DWORD nCmdLineLength = lstrlen( sMsiPath ) + lstrlen( pParam ) + 2;
814     TCHAR *sCmdLine = new TCHAR[ nCmdLineLength ];
815 
816     if ( FAILED( StringCchCopy( sCmdLine, nCmdLineLength, sMsiPath ) ) ||
817          FAILED( StringCchCat(  sCmdLine, nCmdLineLength, TEXT( " " ) ) ) ||
818          FAILED( StringCchCat(  sCmdLine, nCmdLineLength, pParam ) ) )
819     {
820         delete [] sCmdLine;
821         SetError( ERROR_INSTALL_FAILURE );
822         return false;
823     }
824 
825     if ( !WIN::CreateProcess( NULL, sCmdLine, NULL, NULL, FALSE,
826                               CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
827                               &aSUI, &aPI ) )
828     {
829         Log( TEXT( "ERROR: Could not create process %s.\r\n" ), sCmdLine );
830         SetError( WIN::GetLastError() );
831         delete [] sCmdLine;
832         return false;
833     }
834 
835     DWORD nResult = WaitForProcess( aPI.hProcess );
836     bool bRet = true;
837 
838     if( ERROR_SUCCESS != nResult )
839     {
840         Log( TEXT( "ERROR: While waiting for %s.\r\n" ), sCmdLine );
841         SetError( nResult );
842         bRet = false;
843     }
844     else
845     {
846         GetExitCodeProcess( aPI.hProcess, &nResult );
847         SetError( nResult );
848 
849         if ( nResult != ERROR_SUCCESS )
850         {
851             TCHAR sBuf[80];
852             StringCchPrintf( sBuf, 80, TEXT("Warning: msiexec returned %u.\r\n"), nResult );
853             Log( sBuf );
854         }
855         else
856             Log( TEXT( " Installation completed successfully.\r\n" ) );
857     }
858 
859     CloseHandle( aPI.hProcess );
860 
861     delete [] sCmdLine;
862 
863     return bRet;
864 }
865 
866 //--------------------------------------------------------------------------
Install(long nLanguage)867 boolean SetupApp::Install( long nLanguage )
868 {
869     LPTSTR pTransform = NULL;
870 
871     if ( nLanguage ) // look for transformation
872     {
873         for ( int i = 0; i < m_nLanguageCount; i++ )
874         {
875             if ( m_ppLanguageList[i]->m_nLanguageID == nLanguage )
876             {
877                 if ( m_ppLanguageList[i]->m_pTransform )
878                 {
879                     if ( !GetPathToFile( m_ppLanguageList[i]->m_pTransform,
880                                         &pTransform ) )
881                     {
882                         Log( TEXT( "ERROR: Could not find transform <%s\r\n" ), m_ppLanguageList[i]->m_pTransform );
883                         return false;
884                     }
885                 }
886                 break;
887             }
888         }
889     }
890 
891     TCHAR *pDataBasePath = NULL;
892 
893     if ( ! GetPathToFile( m_pDatabase, &pDataBasePath ) )
894     {
895         Log( TEXT( "ERROR: Could not find database <%s\r\n" ), m_pDatabase );
896         SetError( ERROR_INSTALL_SOURCE_ABSENT );
897         return false;
898     }
899 
900     // we will always use the parameter setup used
901     int nParLen = lstrlen( PARAM_SETUP_USED );
902 
903     if ( m_bRegNoMsoTypes )
904         nParLen += lstrlen( PARAM_REG_NO_MSO_TYPES );
905     else if ( m_bRegAllMsoTypes )
906         nParLen += lstrlen( PARAM_REG_ALL_MSO_TYPES );
907 
908     if ( m_pAdvertise )
909         nParLen += lstrlen( m_pAdvertise ) + 1;     // one for the space
910     else if ( m_bIsMinorUpgrade )
911         nParLen += lstrlen( PARAM_MINOR_UPGRADE );
912     else
913         nParLen += lstrlen( PARAM_PACKAGE );
914 
915     nParLen += lstrlen( pDataBasePath ) + 3;        // two quotes, one null
916 
917     if ( NeedReboot() )
918         nParLen += lstrlen( PARAM_REBOOT );
919 
920     if ( m_pPatchFiles )
921     {
922         nParLen += lstrlen( PARAM_PATCH );
923         nParLen += lstrlen( m_pPatchFiles );
924     }
925 
926     if ( pTransform )
927     {
928         nParLen += lstrlen( PARAM_TRANSFORM );
929         nParLen += lstrlen( pTransform ) + 2;       // two quotes
930     }
931 
932     if ( m_pCmdLine )
933         nParLen += lstrlen( m_pCmdLine ) + 1;       // one for the space;
934 
935     TCHAR *pParams = new TCHAR[ nParLen ];
936 
937     StringCchCopy( pParams, nParLen, PARAM_SETUP_USED );
938 
939     if ( m_bRegNoMsoTypes )
940         StringCchCat( pParams, nParLen, PARAM_REG_NO_MSO_TYPES );
941     else if ( m_bRegAllMsoTypes )
942         StringCchCat( pParams, nParLen, PARAM_REG_ALL_MSO_TYPES );
943 
944     if ( m_pAdvertise )
945         StringCchCat( pParams, nParLen, m_pAdvertise );
946     else if ( IsAdminInstall() )
947         StringCchCat( pParams, nParLen, PARAM_ADMIN );
948     else if ( m_bIsMinorUpgrade )
949         StringCchCat( pParams, nParLen, PARAM_MINOR_UPGRADE );
950     else
951         StringCchCat( pParams, nParLen, PARAM_PACKAGE );
952 
953     StringCchCat( pParams, nParLen, TEXT( "\"" ) );
954     StringCchCat( pParams, nParLen, pDataBasePath );
955     StringCchCat( pParams, nParLen, TEXT( "\"" ) );
956 
957     if ( NeedReboot() )
958         StringCchCat( pParams, nParLen, PARAM_REBOOT );
959 
960     if ( m_pPatchFiles )
961     {
962         StringCchCat( pParams, nParLen, PARAM_PATCH );
963         StringCchCat( pParams, nParLen, m_pPatchFiles );
964     }
965 
966     if ( pTransform )
967     {
968         StringCchCat( pParams, nParLen, PARAM_TRANSFORM );
969         StringCchCat( pParams, nParLen, TEXT( "\"" ) );
970         StringCchCat( pParams, nParLen, pTransform );
971         StringCchCat( pParams, nParLen, TEXT( "\"" ) );
972     }
973 
974     if ( m_pCmdLine )
975     {
976         StringCchCat( pParams, nParLen, TEXT( " " ) );
977         StringCchCat( pParams, nParLen, m_pCmdLine );
978     }
979 
980     return LaunchInstaller( pParams );
981 }
982 
983 //--------------------------------------------------------------------------
GetError() const984 UINT SetupApp::GetError() const
985 {
986     UINT nErr = 0;
987 
988     if ( m_pMSIErrorCode )
989         nErr = (UINT) *m_pMSIErrorCode;
990 
991     if ( nErr == 0 )
992         nErr = m_uiRet;
993 
994     if ( nErr != 0 )
995         OutputDebugStringFormat( TEXT("Setup will return error (%d).\n"), nErr );
996     return nErr;
997 }
998 
999 //--------------------------------------------------------------------------
DisplayError(UINT nErr) const1000 void SetupApp::DisplayError( UINT nErr ) const
1001 {
1002     TCHAR sError[ MAX_TEXT_LENGTH ] = {0};
1003     TCHAR sTmp[ MAX_TEXT_LENGTH ] = {0};
1004 
1005     UINT  nMsgType = MB_OK | MB_ICONERROR;
1006 
1007     switch ( nErr )
1008     {
1009         case ERROR_SUCCESS:     break;  // 0
1010 
1011         case ERROR_FILE_NOT_FOUND:  // 2
1012                                 WIN::LoadString( m_hInst, IDS_FILE_NOT_FOUND, sTmp, MAX_TEXT_LENGTH );
1013                                 StringCchPrintf( sError, MAX_TEXT_LENGTH, sTmp, m_pErrorText );
1014                                 break;
1015         case ERROR_INVALID_DATA:    // 13
1016                                 WIN::LoadString( m_hInst, IDS_INVALID_PROFILE, sError, MAX_TEXT_LENGTH );
1017                                 break;
1018         case ERROR_OUTOFMEMORY: WIN::LoadString( m_hInst, IDS_OUTOFMEM, sError, MAX_TEXT_LENGTH );
1019                                 break;
1020         case ERROR_INSTALL_USEREXIT:
1021                                 WIN::LoadString( m_hInst, IDS_USER_CANCELED, sError, MAX_TEXT_LENGTH );
1022                                 break;
1023         case ERROR_INSTALL_ALREADY_RUNNING: // 1618
1024                                 WIN::LoadString( m_hInst, IDS_ALREADY_RUNNING, sError, MAX_TEXT_LENGTH );
1025                                 break;
1026         case ERROR_INSTALL_SOURCE_ABSENT:
1027                                 WIN::LoadString( m_hInst, IDS_NOMSI, sError, MAX_TEXT_LENGTH );
1028                                 break;
1029         case ERROR_DS_INSUFF_ACCESS_RIGHTS: // 8344
1030                                 WIN::LoadString( m_hInst, IDS_REQUIRES_ADMIN_PRIV, sError, MAX_TEXT_LENGTH );
1031                                 break;
1032         case E_ABORT:           WIN::LoadString( m_hInst, IDS_UNKNOWN_ERROR, sError, MAX_TEXT_LENGTH );
1033                                 break;
1034         case ERROR_INVALID_PARAMETER:   // 87
1035                                 WIN::LoadString( m_hInst, IDS_INVALID_PARAM, sTmp, MAX_TEXT_LENGTH );
1036                                 StringCchPrintf( sError, MAX_TEXT_LENGTH, sTmp, m_pErrorText );
1037                                 break;
1038 
1039         case ERROR_SETUP_TO_OLD:    // - 3
1040                                 WIN::LoadString( m_hInst, IDS_SETUP_TO_OLD, sTmp, MAX_TEXT_LENGTH );
1041                                 StringCchPrintf( sError, MAX_TEXT_LENGTH, sTmp, m_pReqVersion, m_pErrorText );
1042                                 break;
1043         case ERROR_SETUP_NOT_FOUND: // - 4
1044                                 WIN::LoadString( m_hInst, IDS_SETUP_NOT_FOUND, sTmp, MAX_TEXT_LENGTH );
1045                                 StringCchPrintf( sError, MAX_TEXT_LENGTH, sTmp, m_pReqVersion );
1046                                 break;
1047         case ERROR_SHOW_USAGE:      // - 2
1048                                 nMsgType = MB_OK | MB_ICONINFORMATION;
1049                                 WIN::LoadString( m_hInst, IDS_USAGE, sError, MAX_TEXT_LENGTH );
1050                                 break;
1051         case ERROR_OS_TO_OLD:       // - 5
1052                                 WIN::LoadString( m_hInst, IDS_OS_TO_OLD, sError, MAX_TEXT_LENGTH );
1053                                 break;
1054         case ERROR_RUNTIME_FAILED:  // - 6
1055                                 WIN::LoadString( m_hInst, IDS_RUNTIME_FAILED, sError, MAX_TEXT_LENGTH );
1056                                 break;
1057 
1058         default:                WIN::LoadString( m_hInst, IDS_UNKNOWN_ERROR, sError, MAX_TEXT_LENGTH );
1059                                 break;
1060     }
1061 
1062     if ( sError[0] )
1063     {
1064         if ( !m_bQuiet )
1065         {
1066             ConvertNewline( sError );
1067             WIN::MessageBox( NULL, sError, m_pAppTitle, nMsgType );
1068         }
1069 
1070         Log( TEXT( "ERROR: %s\r\n" ), sError );
1071     }
1072 }
1073 
1074 //--------------------------------------------------------------------------
GetLanguageID(long nIndex) const1075 long SetupApp::GetLanguageID( long nIndex ) const
1076 {
1077     if ( nIndex >=0 && nIndex < m_nLanguageCount )
1078         return m_ppLanguageList[ nIndex ]->m_nLanguageID;
1079     else
1080         return 0;
1081 }
1082 
1083 //--------------------------------------------------------------------------
GetLanguageName(long nLanguage,LPTSTR sName) const1084 void SetupApp::GetLanguageName( long nLanguage, LPTSTR sName ) const
1085 {
1086     switch ( nLanguage )
1087     {
1088         case 1028: WIN::LoadString( m_hInst, IDS_LANGUAGE_ZH_TW, sName, MAX_LANGUAGE_LEN ); break;
1089         case 1029: WIN::LoadString( m_hInst, IDS_LANGUAGE_CS,    sName, MAX_LANGUAGE_LEN ); break;
1090         case 1030: WIN::LoadString( m_hInst, IDS_LANGUAGE_DA,    sName, MAX_LANGUAGE_LEN ); break;
1091         case 1031: WIN::LoadString( m_hInst, IDS_LANGUAGE_DE_DE, sName, MAX_LANGUAGE_LEN ); break;
1092         case 1032: WIN::LoadString( m_hInst, IDS_LANGUAGE_EL,    sName, MAX_LANGUAGE_LEN ); break;
1093         case 1033: WIN::LoadString( m_hInst, IDS_LANGUAGE_EN_US, sName, MAX_LANGUAGE_LEN ); break;
1094         case 1034: WIN::LoadString( m_hInst, IDS_LANGUAGE_ES,    sName, MAX_LANGUAGE_LEN ); break;
1095         case 1035: WIN::LoadString( m_hInst, IDS_LANGUAGE_FI,    sName, MAX_LANGUAGE_LEN ); break;
1096         case 1036: WIN::LoadString( m_hInst, IDS_LANGUAGE_FR_FR, sName, MAX_LANGUAGE_LEN ); break;
1097         case 1037: WIN::LoadString( m_hInst, IDS_LANGUAGE_HE,    sName, MAX_LANGUAGE_LEN ); break;
1098         case 1038: WIN::LoadString( m_hInst, IDS_LANGUAGE_HU,    sName, MAX_LANGUAGE_LEN ); break;
1099         case 1040: WIN::LoadString( m_hInst, IDS_LANGUAGE_IT_IT, sName, MAX_LANGUAGE_LEN ); break;
1100         case 1041: WIN::LoadString( m_hInst, IDS_LANGUAGE_JA,    sName, MAX_LANGUAGE_LEN ); break;
1101         case 1042: WIN::LoadString( m_hInst, IDS_LANGUAGE_KO,    sName, MAX_LANGUAGE_LEN ); break;
1102         case 1043: WIN::LoadString( m_hInst, IDS_LANGUAGE_NL_NL, sName, MAX_LANGUAGE_LEN ); break;
1103         case 1044: WIN::LoadString( m_hInst, IDS_LANGUAGE_NO_NO, sName, MAX_LANGUAGE_LEN ); break;
1104         case 1045: WIN::LoadString( m_hInst, IDS_LANGUAGE_PL,    sName, MAX_LANGUAGE_LEN ); break;
1105         case 1046: WIN::LoadString( m_hInst, IDS_LANGUAGE_PT_BR, sName, MAX_LANGUAGE_LEN ); break;
1106         case 1049: WIN::LoadString( m_hInst, IDS_LANGUAGE_RU,    sName, MAX_LANGUAGE_LEN ); break;
1107         case 1051: WIN::LoadString( m_hInst, IDS_LANGUAGE_SK,    sName, MAX_LANGUAGE_LEN ); break;
1108         case 1053: WIN::LoadString( m_hInst, IDS_LANGUAGE_SV_SE, sName, MAX_LANGUAGE_LEN ); break;
1109         case 1054: WIN::LoadString( m_hInst, IDS_LANGUAGE_TH,    sName, MAX_LANGUAGE_LEN ); break;
1110         case 1055: WIN::LoadString( m_hInst, IDS_LANGUAGE_TR,    sName, MAX_LANGUAGE_LEN ); break;
1111         case 1061: WIN::LoadString( m_hInst, IDS_LANGUAGE_ET,    sName, MAX_LANGUAGE_LEN ); break;
1112         case 2052: WIN::LoadString( m_hInst, IDS_LANGUAGE_ZH_CN, sName, MAX_LANGUAGE_LEN ); break;
1113         case 2070: WIN::LoadString( m_hInst, IDS_LANGUAGE_PT_PT, sName, MAX_LANGUAGE_LEN ); break;
1114 
1115         default:
1116             {
1117                 TCHAR sTmp[ MAX_LANGUAGE_LEN ] = {0};
1118 
1119                 WIN::LoadString( m_hInst, IDS_UNKNOWN_LANG, sTmp, MAX_LANGUAGE_LEN );
1120                 StringCchPrintf( sName, MAX_LANGUAGE_LEN, sTmp, nLanguage );
1121             }
1122     }
1123 }
1124 
1125 //--------------------------------------------------------------------------
CheckVersion()1126 boolean SetupApp::CheckVersion()
1127 {
1128     boolean bRet = false;
1129 
1130     Log( TEXT( " Looking for installed MSI with version >= %s\r\n" ), m_pReqVersion );
1131 
1132     DLLVERSIONINFO aInfo;
1133 
1134     aInfo.cbSize = sizeof( DLLVERSIONINFO );
1135     if ( NOERROR == aoo_MsiDllGetVersion( &aInfo ) )
1136     {
1137     TCHAR pMsiVersion[ VERSION_SIZE ];
1138     StringCchPrintf( pMsiVersion, VERSION_SIZE, TEXT("%d.%d.%4d"),
1139              aInfo.dwMajorVersion,
1140              aInfo.dwMinorVersion,
1141              aInfo.dwBuildNumber );
1142     if ( _tcsncmp( pMsiVersion, m_pReqVersion, _tcslen( pMsiVersion ) ) < 0 )
1143         {
1144         StringCchCopy( m_pErrorText, MAX_TEXT_LENGTH, pMsiVersion );
1145         SetError( (UINT) ERROR_SETUP_TO_OLD );
1146         Log( TEXT( "Warning: Old MSI version found <%s>, update needed!\r\n" ), pMsiVersion );
1147     }
1148     else
1149     {
1150         Log( TEXT( " Found MSI version <%s>, no update needed\r\n" ), pMsiVersion );
1151         bRet = true;
1152     }
1153     if ( aInfo.dwMajorVersion >= 3 )
1154         m_bSupportsPatch = true;
1155     else
1156         Log( TEXT("Warning: Patching not supported! MSI-Version <%s>\r\n"), pMsiVersion );
1157     }
1158 
1159     return bRet;
1160 }
1161 
1162 //--------------------------------------------------------------------------
CheckForUpgrade()1163 boolean SetupApp::CheckForUpgrade()
1164 {
1165     // When we have patch files we will never try an Minor upgrade
1166     if ( m_pPatchFiles ) return true;
1167 
1168     if ( !m_pUpgradeKey || ( _tcslen( m_pUpgradeKey ) == 0 ) )
1169     {
1170         Log( TEXT( "    No Upgrade Key Found -> continue with standard installation!\r\n" ) );
1171         return true;
1172     }
1173 
1174     HKEY hInstKey = NULL;
1175 
1176     if ( ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, m_pUpgradeKey, 0, KEY_READ, &hInstKey ) )
1177     {
1178         Log( TEXT( " Found Upgrade Key in Registry (HKLM) -> will try minor upgrade!\r\n" ) );
1179         m_bIsMinorUpgrade = true;
1180     }
1181     else if ( ERROR_SUCCESS == RegOpenKeyEx( HKEY_CURRENT_USER, m_pUpgradeKey, 0, KEY_READ, &hInstKey ) )
1182     {
1183         Log( TEXT( " Found Upgrade Key in Registry (HKCU) -> will try minor upgrade!\r\n" ) );
1184         m_bIsMinorUpgrade = true;
1185     }
1186     else
1187     {
1188         Log( TEXT( " Didn't Find Upgrade Key in Registry -> continue with standard installation!\r\n" ) );
1189         return true;
1190     }
1191 
1192     if ( m_pProductVersion && ( _tcslen( m_pProductVersion ) > 0 ) )
1193     {
1194         TCHAR *sProductVersion = new TCHAR[ MAX_PATH + 1 ];
1195         DWORD  nSize = MAX_PATH + 1;
1196 
1197         sProductVersion[0] = '\0';
1198 
1199         // get product version
1200         if ( ERROR_SUCCESS == RegQueryValueEx( hInstKey, PRODUCT_VERSION, NULL, NULL, (LPBYTE)sProductVersion, &nSize ) )
1201         {
1202             if ( lstrcmpi( sProductVersion, m_pProductVersion ) == 0 )
1203             {
1204                 Log( TEXT( " Same Product Version already installed, no minor upgrade!\r\n" ) );
1205                 m_bIsMinorUpgrade = false;
1206             }
1207         }
1208 
1209         delete [] sProductVersion;
1210     }
1211 
1212     return true;
1213 }
1214 
1215 //--------------------------------------------------------------------------
IsTerminalServerInstalled() const1216 boolean SetupApp::IsTerminalServerInstalled() const
1217 {
1218     boolean bIsTerminalServer = false;
1219 
1220     const TCHAR sSearchStr[]   = TEXT("Terminal Server");
1221     const TCHAR sKey[]         = TEXT("System\\CurrentControlSet\\Control\\ProductOptions");
1222     const TCHAR sValue[]       = TEXT("ProductSuite");
1223 
1224     DWORD dwSize = 0;
1225     HKEY  hKey = 0;
1226     DWORD dwType = 0;
1227 
1228     if ( ERROR_SUCCESS == RegOpenKeyEx( HKEY_LOCAL_MACHINE, sKey, 0, KEY_READ, &hKey ) &&
1229          ERROR_SUCCESS == RegQueryValueEx( hKey, sValue, NULL, &dwType, NULL, &dwSize ) &&
1230          dwSize > 0 &&
1231          REG_MULTI_SZ == dwType )
1232     {
1233         TCHAR* sSuiteList = new TCHAR[ (dwSize*sizeof(byte)/sizeof(TCHAR)) + 1 ];
1234 
1235         ZeroMemory(sSuiteList, dwSize);
1236 
1237         if ( ERROR_SUCCESS == RegQueryValueEx( hKey, sValue, NULL, &dwType, (LPBYTE)sSuiteList, &dwSize) )
1238         {
1239             DWORD nMulti = 0;
1240             DWORD nSrch  = lstrlen( sSearchStr );
1241             const TCHAR *sSubString = sSuiteList;
1242 
1243             while (*sSubString)
1244             {
1245                 nMulti = lstrlen( sSubString );
1246                 if ( nMulti == nSrch && 0 == lstrcmp( sSearchStr, sSubString ) )
1247                 {
1248                     bIsTerminalServer = true;
1249                     break;
1250                 }
1251 
1252                 sSubString += (nMulti + 1);
1253             }
1254         }
1255         delete [] sSuiteList;
1256     }
1257 
1258     if ( hKey )
1259         RegCloseKey( hKey );
1260 
1261     return bIsTerminalServer;
1262 }
1263 
1264 //--------------------------------------------------------------------------
AlreadyRunning() const1265 boolean SetupApp::AlreadyRunning() const
1266 {
1267     if ( m_bIgnoreAlreadyRunning )
1268     {
1269         Log( TEXT("Ignoring already running MSI instance!\r\n") );
1270         return false;
1271     }
1272 
1273     const TCHAR *sMutexName    = NULL;
1274     const TCHAR sGUniqueName[] = TEXT( "Global\\_MSISETUP_{EA8130C1-8D3D-4338-9309-1A52D530D846}" );
1275     const TCHAR sUniqueName[]  = TEXT( "_MSISETUP_{EA8130C1-8D3D-4338-9309-1A52D530D846}" );
1276 
1277     if ( IsWin9x() )
1278         sMutexName = sUniqueName;
1279     else if ( ( GetOSVersion() < 5 ) && ! IsTerminalServerInstalled() )
1280         sMutexName = sUniqueName;
1281     else
1282         sMutexName = sGUniqueName;
1283 
1284     HANDLE hMutex = 0;
1285 
1286     hMutex = WIN::CreateMutex( NULL, FALSE, sMutexName );
1287 
1288     if ( !hMutex || ERROR_ALREADY_EXISTS == WIN::GetLastError() )
1289     {
1290         if ( !hMutex )
1291             Log( TEXT( "ERROR: AlreadyRunning() could not create mutex!\r\n" ) );
1292         else
1293             Log( TEXT( "ERROR: There's already a setup running!\r\n" ) );
1294 
1295         return true;
1296     }
1297     Log( TEXT( " No running Setup found\r\n" ) );
1298 
1299     return false;
1300 }
1301 
1302 //--------------------------------------------------------------------------
WaitForProcess(HANDLE hHandle)1303 DWORD SetupApp::WaitForProcess( HANDLE hHandle )
1304 {
1305     DWORD nResult = NOERROR;
1306     boolean bLoop = true;
1307 
1308     MSG aMsg;
1309     ZeroMemory( (void*) &aMsg, sizeof(MSG) );
1310 
1311     while ( bLoop )
1312     {
1313         switch ( WIN::MsgWaitForMultipleObjects( 1, &hHandle, false,
1314                                                  INFINITE, QS_ALLINPUT ) )
1315         {
1316             case WAIT_OBJECT_0: bLoop = false;
1317                 break;
1318 
1319             case (WAIT_OBJECT_0 + 1):
1320             {
1321                 if ( WIN::PeekMessage( &aMsg, NULL, NULL, NULL, PM_REMOVE ) )
1322                 {
1323                     WIN::TranslateMessage( &aMsg );
1324                     WIN::DispatchMessage( &aMsg );
1325                 }
1326                 break;
1327             }
1328 
1329             default:
1330             {
1331                 nResult = WIN::GetLastError();
1332                 bLoop = false;
1333             }
1334         }
1335     }
1336 
1337     return nResult;
1338 }
1339 
1340 //--------------------------------------------------------------------------
Log(LPCTSTR pMessage,LPCTSTR pText) const1341 void SetupApp::Log( LPCTSTR pMessage, LPCTSTR pText ) const
1342 {
1343     if ( m_pLogFile )
1344     {
1345         static boolean bInit = false;
1346 
1347         if ( !bInit )
1348         {
1349             bInit = true;
1350             if ( ! IsWin9x() )
1351                 _ftprintf( m_pLogFile, TEXT("%c"), 0xfeff );
1352 
1353             _tsetlocale( LC_ALL, TEXT("") );
1354             _ftprintf( m_pLogFile, TEXT("\nCodepage=%s\nMultiByte Codepage=[%d]\n"),
1355                                    _tsetlocale( LC_ALL, NULL ), _getmbcp() );
1356         }
1357         if ( pText )
1358         {
1359             _ftprintf( m_pLogFile, pMessage, pText );
1360             OutputDebugStringFormat( pMessage, pText );
1361         }
1362         else
1363         {
1364             _ftprintf( m_pLogFile, pMessage );
1365             OutputDebugStringFormat( pMessage );
1366         }
1367 
1368         fflush( m_pLogFile );
1369     }
1370 }
1371 
1372 //--------------------------------------------------------------------------
GetNextArgument(LPCTSTR pStr,LPTSTR * pArg,LPTSTR * pNext,boolean bStripQuotes)1373 DWORD SetupApp::GetNextArgument( LPCTSTR pStr, LPTSTR *pArg, LPTSTR *pNext,
1374                                   boolean bStripQuotes )
1375 {
1376     boolean bInQuotes = false;
1377     boolean bFoundArgEnd = false;
1378     LPCTSTR pChar = pStr;
1379     LPCTSTR pFirst = NULL;
1380 
1381     if ( NULL == pChar )
1382         return ERROR_NO_MORE_ITEMS;
1383 
1384     while ( ' ' == (*pChar) || '\t' == (*pChar) )
1385         pChar = CharNext( pChar );
1386 
1387     if ( '\0' == (*pChar) )
1388         return ERROR_NO_MORE_ITEMS;
1389 
1390     int nCount = 1;
1391     pFirst = pChar;
1392 
1393     while ( ! bFoundArgEnd )
1394     {
1395         if ( '\0' == (*pChar) )
1396             bFoundArgEnd = true;
1397         else if ( !bInQuotes && ' ' == (*pChar) )
1398             bFoundArgEnd = true;
1399         else if ( !bInQuotes && '\t' == (*pChar) )
1400             bFoundArgEnd = true;
1401         else
1402         {
1403             if ( '\"' == (*pChar) )
1404             {
1405                 bInQuotes = !bInQuotes;
1406                 if ( bStripQuotes )
1407                 {
1408                     if ( pChar == pFirst )
1409                         pFirst = CharNext( pFirst );
1410                     nCount -= 1;
1411                 }
1412             }
1413 
1414             pChar = CharNext( pChar );
1415             nCount += 1;
1416         }
1417     }
1418 
1419     if ( pArg )
1420     {
1421         *pArg = new TCHAR[ nCount ];
1422         StringCchCopyN ( *pArg, nCount, pFirst, nCount-1 );
1423     }
1424 
1425     if ( pNext )
1426         *pNext = CharNext( pChar );
1427 
1428     return ERROR_SUCCESS;
1429 }
1430 
1431 //--------------------------------------------------------------------------
GetCmdLineParameters(LPTSTR * pCmdLine)1432 boolean SetupApp::GetCmdLineParameters( LPTSTR *pCmdLine )
1433 {
1434     int    nRet   = ERROR_SUCCESS;
1435     LPTSTR pStart = NULL;
1436     LPTSTR pNext  = NULL;
1437 
1438     if ( GetNextArgument( *pCmdLine, NULL, &pNext ) != ERROR_SUCCESS )
1439     {
1440         SetError( ERROR_NO_MORE_ITEMS );
1441         return false;
1442     }
1443 
1444     int    nSize = lstrlen( *pCmdLine ) + 2;
1445     TCHAR *pNewCmdLine = new TCHAR[ nSize ];
1446     pNewCmdLine[0] = '\0';
1447 
1448     while ( GetNextArgument( pNext, &pStart, &pNext ) == ERROR_SUCCESS )
1449     {
1450         boolean bDeleteStart = true;
1451 
1452         if ( (*pStart) == '/' || (*pStart) == '-' )
1453         {
1454             LPTSTR pSub = CharNext( pStart );
1455             if ( (*pSub) == 'l' || (*pSub) == 'L' )
1456             {
1457                 pSub = CharNext( pSub );
1458                 if ( (*pSub) == 'a' || (*pSub) == 'A' )
1459                 {   // --- handle the lang parameter ---
1460                     LPTSTR pLanguage = NULL;
1461                     LPTSTR pLastChar;
1462                     if ( GetNextArgument( pNext, &pLanguage, &pNext, true ) != ERROR_SUCCESS )
1463                     {
1464                         StringCchCopy( m_pErrorText, MAX_TEXT_LENGTH, pStart );
1465                         nRet = ERROR_INVALID_PARAMETER;
1466                         break;
1467                     }
1468 
1469                     m_nLanguageID = _tcstol( pLanguage, &pLastChar, 10 );
1470                     delete [] pLanguage;
1471                 }
1472                 else
1473                 {   // --- handle the l(og) parameter ---
1474                     boolean bAppend = false;
1475                     LPTSTR  pFileName = NULL;
1476 
1477                     while ( *pSub )
1478                     {
1479                         if ( *pSub == '+' )
1480                         {
1481                             bAppend = true;
1482                             break;
1483                         }
1484                         pSub = CharNext( pSub );
1485                     }
1486 
1487                     if ( GetNextArgument( pNext, &pFileName, &pNext, true ) != ERROR_SUCCESS )
1488                     {
1489                         StringCchCopy( m_pErrorText, MAX_TEXT_LENGTH, pStart );
1490                         nRet = ERROR_INVALID_PARAMETER;
1491                         break;
1492                     }
1493 
1494                     if ( FAILED( StringCchCat( pNewCmdLine, nSize, pStart ) ) )
1495                     {
1496                         nRet = ERROR_OUTOFMEMORY;
1497                         break;
1498                     }
1499                     // we need to append a '+' otherwise msiexec would overwrite our log file
1500                     if ( !bAppend && FAILED( StringCchCat( pNewCmdLine, nSize, TEXT( "+" ) ) ) )
1501                     {
1502                         nRet = ERROR_OUTOFMEMORY;
1503                         break;
1504                     }
1505                     if ( FAILED( StringCchCat( pNewCmdLine, nSize, TEXT( " \"" ) ) ) ||
1506                         FAILED( StringCchCat( pNewCmdLine, nSize, pFileName ) ) ||
1507                         FAILED( StringCchCat( pNewCmdLine, nSize, TEXT( "\" " ) ) ) )
1508                     {
1509                         nRet = ERROR_OUTOFMEMORY;
1510                         break;
1511                     }
1512 
1513                     if ( bAppend )
1514                         m_pLogFile = _tfopen( pFileName, TEXT( "ab" ) );
1515                     else
1516                         m_pLogFile = _tfopen( pFileName, TEXT( "wb" ) );
1517 
1518                     delete [] pFileName;
1519                 }
1520             }
1521             else if ( (*pSub) == 'q' || (*pSub) == 'Q' )
1522             {   // --- Handle quiet file parameter ---
1523                 pSub = CharNext( pSub );
1524                 if ( ! (*pSub) || (*pSub) == 'n' || (*pSub) == 'N' )
1525                     m_bQuiet = true;
1526 
1527                 if ( FAILED( StringCchCat( pNewCmdLine, nSize, pStart ) ) ||
1528                      FAILED( StringCchCat( pNewCmdLine, nSize, TEXT( " " ) ) ) )
1529                 {
1530                     nRet = ERROR_OUTOFMEMORY;
1531                     break;
1532                 }
1533             }
1534             else if ( _tcsnicmp( pSub, PARAM_RUNNING, _tcslen( PARAM_RUNNING ) ) == 0 )
1535             {
1536                 m_bIgnoreAlreadyRunning = true;
1537             }
1538             else if ( _tcsnicmp( pSub, CMDLN_REG_ALL_MSO_TYPES, _tcslen( CMDLN_REG_ALL_MSO_TYPES ) ) == 0 )
1539             {
1540                 m_bRegAllMsoTypes = true;
1541             }
1542             else if ( _tcsnicmp( pSub, CMDLN_REG_NO_MSO_TYPES, _tcslen( CMDLN_REG_NO_MSO_TYPES ) ) == 0 )
1543             {
1544                 m_bRegNoMsoTypes = true;
1545             }
1546             else if ( (*pSub) == 'i' || (*pSub) == 'I' || (*pSub) == 'f' || (*pSub) == 'F' ||
1547                       (*pSub) == 'p' || (*pSub) == 'P' || (*pSub) == 'x' || (*pSub) == 'X' ||
1548                       (*pSub) == 'y' || (*pSub) == 'Y' || (*pSub) == 'z' || (*pSub) == 'Z' )
1549             {
1550                 StringCchCopy( m_pErrorText, MAX_TEXT_LENGTH, pStart );
1551                 nRet = ERROR_INVALID_PARAMETER;
1552                 break;
1553             }
1554             else if ( (*pSub) == 'a' || (*pSub) == 'A' )
1555             {   // --- Handle Administrative Installation ---
1556                 SetAdminInstall( true );
1557             }
1558             else if ( (*pSub) == 'j' || (*pSub) == 'J' )
1559             {   // --- Handle Administrative Installation ---
1560                 m_pAdvertise = pStart;
1561                 m_bQuiet     = true;
1562                 bDeleteStart = false;
1563             }
1564             else if ( (*pSub) == '?' || (*pSub) == 'h' || (*pSub) == 'H' )
1565             {   // --- Handle Show Usage ---
1566                 nRet = ERROR_SHOW_USAGE;
1567                 break;
1568             }
1569             else
1570             {
1571                 if ( FAILED( StringCchCat( pNewCmdLine, nSize, pStart ) ) ||
1572                      FAILED( StringCchCat( pNewCmdLine, nSize, TEXT( " " ) ) ) )
1573                 {
1574                     nRet = ERROR_OUTOFMEMORY;
1575                     break;
1576                 }
1577             }
1578         }
1579         else
1580         {
1581             if ( FAILED( StringCchCat( pNewCmdLine, nSize, pStart ) ) ||
1582                  FAILED( StringCchCat( pNewCmdLine, nSize, TEXT( " " ) ) ) )
1583             {
1584                 nRet = ERROR_OUTOFMEMORY;
1585                 break;
1586             }
1587         }
1588 
1589         if ( bDeleteStart ) delete [] pStart;
1590         pStart = NULL;
1591     }
1592 
1593     if ( pStart ) delete [] pStart;
1594 
1595     *pCmdLine = pNewCmdLine;
1596 
1597     if ( nRet != ERROR_SUCCESS )
1598     {
1599         SetError( nRet );
1600         return false;
1601     }
1602     else
1603         return true;
1604 }
1605 
1606 //--------------------------------------------------------------------------
IsAdmin()1607 boolean SetupApp::IsAdmin()
1608 {
1609     if ( IsWin9x() )
1610         return true;
1611 
1612     PSID aPsidAdmin;
1613     SID_IDENTIFIER_AUTHORITY aAuthority = SECURITY_NT_AUTHORITY;
1614 
1615     if ( !AllocateAndInitializeSid( &aAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID,
1616                                     DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
1617                                     &aPsidAdmin ) )
1618         return false;
1619 
1620     BOOL bIsAdmin = FALSE;
1621 
1622     if ( GetOSVersion() >= 5 )
1623     {
1624         HMODULE hAdvapi32 = LoadLibrary( ADVAPI32_DLL );
1625 
1626         if ( !hAdvapi32 )
1627             bIsAdmin = FALSE;
1628         else
1629         {
1630             PFnCheckTokenMembership pfnCheckTokenMembership = (PFnCheckTokenMembership) GetProcAddress( hAdvapi32, ADVAPI32API_CheckTokenMembership);
1631             if ( !pfnCheckTokenMembership || !pfnCheckTokenMembership( NULL, aPsidAdmin, &bIsAdmin ) )
1632                 bIsAdmin = FALSE;
1633         }
1634         FreeLibrary( hAdvapi32 );
1635     }
1636     else
1637     {
1638         // NT4, check groups of user
1639         HANDLE hAccessToken = 0;
1640         UCHAR *szInfoBuffer = new UCHAR[ 1024 ]; // may need to resize if TokenInfo too big
1641         DWORD dwInfoBufferSize = 1024;
1642         DWORD dwRetInfoBufferSize = 0;
1643         UINT i=0;
1644 
1645         if ( WIN::OpenProcessToken( WIN::GetCurrentProcess(), TOKEN_READ, &hAccessToken ) )
1646         {
1647             bool bSuccess = false;
1648             bSuccess = WIN::GetTokenInformation( hAccessToken, TokenGroups,
1649                                                  szInfoBuffer, dwInfoBufferSize,
1650                                                  &dwRetInfoBufferSize ) == TRUE;
1651 
1652             if( dwRetInfoBufferSize > dwInfoBufferSize )
1653             {
1654                 delete [] szInfoBuffer;
1655                 szInfoBuffer = new UCHAR[ dwRetInfoBufferSize ];
1656                 dwInfoBufferSize = dwRetInfoBufferSize;
1657                 bSuccess = WIN::GetTokenInformation( hAccessToken, TokenGroups,
1658                                                      szInfoBuffer, dwInfoBufferSize,
1659                                                      &dwRetInfoBufferSize ) == TRUE;
1660             }
1661 
1662             WIN::CloseHandle( hAccessToken );
1663 
1664             if ( bSuccess )
1665             {
1666                 PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)(UCHAR*) szInfoBuffer;
1667                 for( i=0; i<pGroups->GroupCount; i++ )
1668                 {
1669                     if( WIN::EqualSid( aPsidAdmin, pGroups->Groups[i].Sid ) )
1670                     {
1671                         bIsAdmin = TRUE;
1672                         break;
1673                     }
1674                 }
1675             }
1676 
1677             delete [] szInfoBuffer;
1678         }
1679     }
1680 
1681     WIN::FreeSid( aPsidAdmin );
1682 
1683     return bIsAdmin ? true : false;
1684 }
1685 
1686 //--------------------------------------------------------------------------
CopyIniFile(LPCTSTR pIniFile)1687 LPTSTR SetupApp::CopyIniFile( LPCTSTR pIniFile )
1688 {
1689     m_pTmpName = _ttempnam( TEXT( "C:\\" ), TEXT( "Setup" ) );
1690 
1691     if ( !m_pTmpName )
1692     {
1693         Log( TEXT( "ERROR: Could not create temp file\n" ) );
1694         return NULL;
1695     }
1696 
1697     FILE *pOut  = _tfopen( m_pTmpName, TEXT( "wb" ) );
1698     FILE *pIn   = _tfopen( pIniFile, TEXT( "rb" ) );
1699 
1700     if ( pOut && pIn )
1701     {
1702         size_t  nRead, nWritten;
1703         BYTE    pBuf[1024];
1704 
1705         nRead = fread( pBuf, sizeof( BYTE ), 1024, pIn );
1706         while ( nRead && !ferror( pIn ) )
1707         {
1708             nWritten = fwrite( pBuf, sizeof( BYTE ), nRead, pOut );
1709             if ( nWritten != nRead )
1710             {
1711                 Log( TEXT( "ERROR: Could not write all bytes to temp file\n" ) );
1712                 break;
1713             }
1714             nRead = fread( pBuf, sizeof( BYTE ), 1024, pIn );
1715         }
1716     }
1717 
1718     if ( pOut ) fclose( pOut );
1719     if ( pIn ) fclose( pIn );
1720 
1721     return m_pTmpName;
1722 }
1723 
1724 //--------------------------------------------------------------------------
ConvertNewline(LPTSTR pText) const1725 void SetupApp::ConvertNewline( LPTSTR pText ) const
1726 {
1727     int i=0;
1728 
1729     while ( pText[i] != 0 )
1730     {
1731         if ( ( pText[i] == '\\' ) && ( pText[i+1] == 'n' ) )
1732         {
1733             pText[i] = 0x0d;
1734             pText[i+1] = 0x0a;
1735             i+=2;
1736         }
1737         else
1738             i+=1;
1739     }
1740 }
1741 
1742 //--------------------------------------------------------------------------
SetProdToAppTitle(LPCTSTR pProdName)1743 LPTSTR SetupApp::SetProdToAppTitle( LPCTSTR pProdName )
1744 {
1745     if ( !pProdName ) return m_pAppTitle;
1746 
1747     LPTSTR pAppProdTitle = new TCHAR[ MAX_STR_CAPTION ];
1748            pAppProdTitle[0] = '\0';
1749 
1750     WIN::LoadString( m_hInst, IDS_APP_PROD_TITLE, pAppProdTitle, MAX_STR_CAPTION );
1751 
1752     int nAppLen = lstrlen( pAppProdTitle );
1753     int nProdLen = lstrlen( pProdName );
1754 
1755     if ( ( nAppLen == 0 ) || ( nProdLen == 0 ) )
1756     {
1757         delete [] pAppProdTitle;
1758         return m_pAppTitle;
1759     }
1760 
1761     int nLen = nAppLen + nProdLen + 3;
1762 
1763     if ( nLen > STRSAFE_MAX_CCH ) return m_pAppTitle;
1764 
1765     LPTSTR pIndex = _tcsstr( pAppProdTitle, PRODUCT_NAME_VAR );
1766 
1767     if ( pIndex )
1768     {
1769         int nOffset = pIndex - pAppProdTitle;
1770         int nVarLen = lstrlen( PRODUCT_NAME_VAR );
1771 
1772         LPTSTR pNewTitle = new TCHAR[ nLen ];
1773         pNewTitle[0] = '\0';
1774 
1775         if ( nOffset > 0 )
1776         {
1777             StringCchCopyN( pNewTitle, nLen, pAppProdTitle, nOffset );
1778         }
1779 
1780         StringCchCat( pNewTitle, nLen, pProdName );
1781 
1782         if ( nOffset + nVarLen < nAppLen )
1783         {
1784             StringCchCat( pNewTitle, nLen, pIndex + nVarLen );
1785         }
1786 
1787         delete [] m_pAppTitle;
1788         m_pAppTitle = pNewTitle;
1789     }
1790 
1791     delete [] pAppProdTitle;
1792 
1793     return m_pAppTitle;
1794 }
1795 
1796 
1797 //--------------------------------------------------------------------------
IsPatchInstalled(TCHAR * pBaseDir,TCHAR * pFileName)1798 boolean SetupApp::IsPatchInstalled( TCHAR* pBaseDir, TCHAR* pFileName )
1799 {
1800     if ( !m_bSupportsPatch )
1801         return false;
1802 
1803     PMSIHANDLE hSummaryInfo;
1804     int nLen = lstrlen( pBaseDir ) + lstrlen( pFileName ) + 1;
1805     TCHAR *szDatabasePath = new TCHAR [ nLen ];
1806     TCHAR sBuf[80];
1807 
1808     StringCchCopy( szDatabasePath, nLen, pBaseDir );
1809     StringCchCat( szDatabasePath, nLen, pFileName );
1810 
1811     UINT nRet = aoo_MsiGetSummaryInformation( NULL, szDatabasePath, 0, &hSummaryInfo );
1812 
1813     if ( nRet != ERROR_SUCCESS )
1814     {
1815         StringCchPrintf( sBuf, 80, TEXT("ERROR: IsPatchInstalled: MsiGetSummaryInformation returned %u.\r\n"), nRet );
1816         Log( sBuf );
1817         return false;
1818     }
1819 
1820     UINT    uiDataType;
1821     LPTSTR  szPatchID = new TCHAR[ 64 ];
1822     DWORD   cchValueBuf = 64;
1823     nRet = aoo_MsiSummaryInfoGetProperty( hSummaryInfo, PID_REVNUMBER, &uiDataType, NULL, NULL, szPatchID, &cchValueBuf );
1824 
1825     if ( nRet != ERROR_SUCCESS )
1826     {
1827         StringCchPrintf( sBuf, 80, TEXT("ERROR: IsPatchInstalled: MsiSummaryInfoGetProperty returned %u.\r\n"), nRet );
1828         Log( sBuf );
1829         return false;
1830     }
1831 
1832     nRet = aoo_MsiGetPatchInfo( szPatchID, INSTALLPROPERTY_LOCALPACKAGE, NULL, NULL );
1833 
1834     StringCchPrintf( sBuf, 80, TEXT("  GetPatchInfo for (%s) returned (%u)\r\n"), szPatchID, nRet );
1835     Log( sBuf );
1836 
1837     delete []szPatchID;
1838 
1839     if ( nRet == ERROR_BAD_CONFIGURATION )
1840         return false;
1841     else if ( nRet == ERROR_INVALID_PARAMETER )
1842         return false;
1843     else if ( nRet == ERROR_MORE_DATA )
1844         return true;
1845     else if ( nRet == ERROR_SUCCESS )
1846         return true;
1847     else if ( nRet == ERROR_UNKNOWN_PRODUCT )
1848         return false;
1849     else if ( nRet == ERROR_UNKNOWN_PROPERTY )
1850         return false;
1851     else return false;
1852 }
1853 
1854 //--------------------------------------------------------------------------
1855 // The real Windows version, not the shimmed one.
1856 //
1857 // GetVersionEx() and the Windows Installer VersionNT / WindowsBuild properties all
1858 // report Windows 8.1 (6.3 / 9600) on Windows 10 and 11 unless the caller carries a
1859 // supportedOS manifest entry.  Measured on Windows 11 build 26200, msiexec reports
1860 // VersionNT=603 and WindowsBuild=9600 -- which is why this check cannot be expressed
1861 // as an MSI LaunchCondition, and lives here instead.
1862 //
1863 // RtlGetVersion is not subject to that shim.
GetRealWindowsVersion(DWORD * pMajor,DWORD * pBuild)1864 static bool GetRealWindowsVersion( DWORD *pMajor, DWORD *pBuild )
1865 {
1866     typedef LONG ( WINAPI *pfnRtlGetVersion_t )( OSVERSIONINFOW * );
1867 
1868     HMODULE hNtdll = ::GetModuleHandle( TEXT( "ntdll.dll" ) );
1869     if ( hNtdll == NULL )
1870         return false;
1871 
1872     pfnRtlGetVersion_t pRtlGetVersion =
1873         (pfnRtlGetVersion_t)::GetProcAddress( hNtdll, "RtlGetVersion" );
1874     if ( pRtlGetVersion == NULL )
1875         return false;
1876 
1877     OSVERSIONINFOW aInfo;
1878     ZeroMemory( &aInfo, sizeof( aInfo ) );
1879     aInfo.dwOSVersionInfoSize = sizeof( aInfo );
1880 
1881     if ( pRtlGetVersion( &aInfo ) != 0 )
1882         return false;
1883 
1884     *pMajor = aInfo.dwMajorVersion;
1885     *pBuild = aInfo.dwBuildNumber;
1886     return true;
1887 }
1888 
1889 //--------------------------------------------------------------------------
CheckOSVersion()1890 boolean SetupApp::CheckOSVersion()
1891 {
1892     DWORD nMajor = 0;
1893     DWORD nBuild = 0;
1894 
1895     if ( !GetRealWindowsVersion( &nMajor, &nBuild ) )
1896     {
1897         // Could not determine the version.  Let the install proceed rather than
1898         // refuse on a machine we simply failed to identify -- if the OS really is too
1899         // old the runtime install will fail next, and say so.
1900         Log( TEXT( "Warning: could not determine the Windows version.\r\n" ) );
1901         return true;
1902     }
1903 
1904     TCHAR sBuf[ 128 ];
1905 
1906     StringCchPrintf( sBuf, 128, TEXT( " Windows major version %u, build %u\r\n" ),
1907                      nMajor, nBuild );
1908     Log( sBuf );
1909 
1910     if ( nMajor < REQUIRED_WINDOWS_MAJOR )
1911     {
1912         StringCchPrintf( sBuf, 128,
1913                          TEXT( "ERROR: Windows %u is older than the required Windows %u.\r\n" ),
1914                          nMajor, (DWORD)REQUIRED_WINDOWS_MAJOR );
1915         Log( sBuf );
1916         SetError( ERROR_OS_TO_OLD );
1917         return false;
1918     }
1919 
1920     return true;
1921 }
1922 
1923 //--------------------------------------------------------------------------
1924 // Is the VC v14 runtime already usable in THIS process?
1925 //
1926 // Deliberately functional rather than a registry or ProductCode lookup: it needs no
1927 // GUID that goes stale with every servicing update, no registry view juggling, and it
1928 // tests the thing that actually matters -- whether our DLLs will be able to bind.
1929 //
1930 // LOAD_LIBRARY_SEARCH_SYSTEM32 so we probe the machine-wide runtime and cannot be
1931 // fooled by a stray copy sitting next to setup.exe.
1932 //
1933 // This can only answer for setup.exe's own bitness -- a 64 bit process cannot load a
1934 // 32 bit DLL and vice versa.  That is fine: the caller only uses it to skip the
1935 // matching redistributable.  For the other architecture we just run the bundle, which
1936 // is idempotent and returns 1638 quickly when a newer runtime is already there.
RuntimeAlreadyPresent()1937 static bool RuntimeAlreadyPresent()
1938 {
1939     const TCHAR *pModules[] = {
1940         TEXT( "vcruntime140.dll" ),
1941         TEXT( "msvcp140.dll" ),
1942 #if defined( _WIN64 )
1943         // x64 only -- the separate EH runtime introduced with VS2017.
1944         TEXT( "vcruntime140_1.dll" ),
1945 #endif
1946     };
1947 
1948     for ( size_t i = 0; i < sizeof( pModules ) / sizeof( pModules[0] ); ++i )
1949     {
1950         HMODULE hMod = ::LoadLibraryEx( pModules[i], NULL,
1951                                         LOAD_LIBRARY_SEARCH_SYSTEM32 );
1952         if ( hMod == NULL )
1953             return false;
1954         ::FreeLibrary( hMod );
1955     }
1956 
1957     return true;
1958 }
1959 
1960 //--------------------------------------------------------------------------
InstallRuntimes(TCHAR * sRuntimePath,bool bMatchesOwnArchitecture)1961 boolean SetupApp::InstallRuntimes( TCHAR *sRuntimePath, bool bMatchesOwnArchitecture )
1962 {
1963     if ( bMatchesOwnArchitecture && RuntimeAlreadyPresent() )
1964     {
1965         Log( TEXT( " Runtime already present, skipping <%s>\r\n" ), sRuntimePath );
1966         return true;
1967     }
1968 
1969     Log( TEXT( " Will install runtime <%s>\r\n" ), sRuntimePath );
1970     OutputDebugStringFormat( TEXT( " Will install runtime <%s>\r\n" ), sRuntimePath );
1971 
1972     STARTUPINFO         aSUI;
1973     PROCESS_INFORMATION aPI;
1974 
1975     ZeroMemory( (void*)&aPI, sizeof( PROCESS_INFORMATION ) );
1976     ZeroMemory( (void*)&aSUI, sizeof( STARTUPINFO ) );
1977 
1978     aSUI.cb          = sizeof(STARTUPINFO);
1979     aSUI.dwFlags     = STARTF_USESHOWWINDOW;
1980     aSUI.wShowWindow = SW_SHOW;
1981 
1982     DWORD nCmdLineLength = lstrlen( sRuntimePath ) + lstrlen( PARAM_SILENTINSTALL ) + 2;
1983     TCHAR *sCmdLine = new TCHAR[ nCmdLineLength ];
1984 
1985     if ( FAILED( StringCchCopy( sCmdLine, nCmdLineLength, sRuntimePath ) ) ||
1986          FAILED( StringCchCat(  sCmdLine, nCmdLineLength, PARAM_SILENTINSTALL ) ) )
1987     {
1988         delete [] sCmdLine;
1989         SetError( ERROR_INSTALL_FAILURE );
1990         return false;
1991     }
1992 
1993     if ( !WIN::CreateProcess( NULL, sCmdLine, NULL, NULL, FALSE,
1994                               CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
1995                               &aSUI, &aPI ) )
1996     {
1997         Log( TEXT( "ERROR: Could not create process %s.\r\n" ), sCmdLine );
1998         SetError( WIN::GetLastError() );
1999         delete [] sCmdLine;
2000         return false;
2001     }
2002 
2003     DWORD nResult = WaitForProcess( aPI.hProcess );
2004     bool bRet = true;
2005 
2006     if( ERROR_SUCCESS != nResult )
2007     {
2008         Log( TEXT( "ERROR: While waiting for %s.\r\n" ), sCmdLine );
2009         SetError( nResult );
2010         bRet = false;
2011     }
2012     else
2013     {
2014         GetExitCodeProcess( aPI.hProcess, &nResult );
2015 
2016         // Burn reports more than one flavour of success.  Treating anything non-zero as
2017         // a failure would flag every machine that already carries a newer runtime.
2018         if ( nResult == RUNTIME_INSTALL_OK )
2019         {
2020             Log( TEXT( " Installation of runtime completed successfully.\r\n" ) );
2021         }
2022         else if ( nResult == RUNTIME_INSTALL_NEWER )
2023         {
2024             Log( TEXT( " A newer runtime is already installed, nothing to do.\r\n" ) );
2025         }
2026         else if ( nResult == RUNTIME_INSTALL_REBOOT_REQ )
2027         {
2028             Log( TEXT( " Installation of runtime completed, a reboot is required.\r\n" ) );
2029         }
2030         else
2031         {
2032             TCHAR sBuf[80];
2033             StringCchPrintf( sBuf, 80,
2034                              TEXT("ERROR: install runtime returned %u.\r\n"), nResult );
2035             Log( sBuf );
2036             SetError( nResult );
2037             bRet = false;
2038         }
2039     }
2040 
2041     CloseHandle( aPI.hProcess );
2042 
2043     delete [] sCmdLine;
2044 
2045     return bRet;
2046 }
2047 
2048 //--------------------------------------------------------------------------
InstallRuntimes()2049 boolean SetupApp::InstallRuntimes()
2050 {
2051     TCHAR *sRuntimePath = 0;
2052     SYSTEM_INFO siSysInfo;
2053 
2054     HMODULE hKernel32 = ::LoadLibrary(_T("Kernel32.dll"));
2055     if ( hKernel32 != NULL )
2056     {
2057         typedef void (CALLBACK* pfnGetNativeSystemInfo_t)(LPSYSTEM_INFO);
2058         pfnGetNativeSystemInfo_t pfnGetNativeSystemInfo;
2059         pfnGetNativeSystemInfo = (pfnGetNativeSystemInfo_t)::GetProcAddress(hKernel32, "GetNativeSystemInfo");
2060         if ( pfnGetNativeSystemInfo != NULL )
2061         {
2062             pfnGetNativeSystemInfo(&siSysInfo);
2063         }
2064         else
2065         {
2066             // GetNativeSystemInfo does not exist. Maybe the code is running under Windows 2000.
2067             // Use GetSystemInfo instead.
2068             GetSystemInfo(&siSysInfo);
2069         }
2070         FreeLibrary(hKernel32);
2071     }
2072     else
2073     {
2074         // Failed to check Kernel32.dll. There may be something wrong.
2075         // Use GetSystemInfo instead anyway.
2076         GetSystemInfo(&siSysInfo);
2077     }
2078 
2079     OutputDebugStringFormat( TEXT( "found architecture<%d>\r\n" ), siSysInfo.wProcessorArchitecture );
2080 
2081     bool bOk = true;
2082 
2083 #if defined( _WIN64 )
2084 
2085     // A 64 bit office ships no 32 bit binaries at all, so it needs only the x64
2086     // runtime.  (The 32 bit office is the other way round: it cross-builds 64 bit
2087     // shell extensions, which is why the branch below installs both.)
2088     (void)siSysInfo;
2089 
2090     if ( GetPathToFile( RUNTIME_X64_NAME, &sRuntimePath ) )
2091         bOk = InstallRuntimes( sRuntimePath, true ) ? true : false;
2092     else
2093     {
2094         Log( TEXT( "ERROR: no installer for x64 runtime libraries found!\r\n" ) );
2095         bOk = false;
2096     }
2097 
2098     if ( sRuntimePath )
2099         delete [] sRuntimePath;
2100 
2101 #else
2102 
2103     // 32 bit office.  On 64 bit Windows it additionally needs the x64 runtime, because
2104     // its shell extensions (shlxthdl_x64, ooofilt_x64, propertyhdl_x64, so_activex_x64)
2105     // are 64 bit and get loaded by 64 bit Explorer and friends.
2106     if ( siSysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 )
2107     {
2108         if ( GetPathToFile( RUNTIME_X64_NAME, &sRuntimePath ) )
2109         {
2110             if ( !InstallRuntimes( sRuntimePath, false ) )  // cannot probe x64 from a 32 bit process
2111                 bOk = false;
2112         }
2113         else
2114         {
2115             Log( TEXT( "ERROR: no installer for x64 runtime libraries found!\r\n" ) );
2116             bOk = false;
2117         }
2118 
2119         if ( sRuntimePath )
2120         {
2121             delete [] sRuntimePath;
2122             sRuntimePath = 0;
2123         }
2124     }
2125 
2126     if ( GetPathToFile( RUNTIME_X86_NAME, &sRuntimePath ) )
2127     {
2128         if ( !InstallRuntimes( sRuntimePath, true ) )
2129             bOk = false;
2130     }
2131     else
2132     {
2133         Log( TEXT( "ERROR: no installer for x86 runtime libraries found!\r\n" ) );
2134         bOk = false;
2135     }
2136 
2137     if ( sRuntimePath )
2138         delete [] sRuntimePath;
2139 
2140 #endif
2141 
2142     // A failed runtime install used to be swallowed here -- the per-runtime result was
2143     // discarded and this returned true regardless.  The office then installed and could
2144     // not start, with nothing to point at.  Fail loudly instead.
2145     if ( !bOk )
2146     {
2147         Log( TEXT( "ERROR: the Visual C++ runtime could not be installed.\r\n" ) );
2148         SetError( ERROR_RUNTIME_FAILED );
2149         return false;
2150     }
2151 
2152     return true;
2153 }
2154 
2155 //--------------------------------------------------------------------------
2156 //--------------------------------------------------------------------------
LanguageData(LPTSTR pData)2157 LanguageData::LanguageData( LPTSTR pData )
2158 {
2159     m_nLanguageID = 0;
2160     m_pTransform = NULL;
2161 
2162     LPTSTR pLastChar;
2163 
2164     m_nLanguageID = _tcstol( pData, &pLastChar, 10 );
2165 
2166     if ( *pLastChar == ',' )
2167     {
2168         pLastChar += 1;
2169         int nLen = lstrlen( pLastChar ) + 1;
2170         m_pTransform = new TCHAR [ nLen ];
2171         StringCchCopy( m_pTransform, nLen, pLastChar );
2172     }
2173 }
2174 
2175 //--------------------------------------------------------------------------
~LanguageData()2176 LanguageData::~LanguageData()
2177 {
2178     if ( m_pTransform ) delete [] m_pTransform;
2179 }
2180 
2181 //--------------------------------------------------------------------------
2182