home *** CD-ROM | disk | FTP | other *** search
/ Master 95 #1 / MASTER95_1.iso / microsof / vbasic4 / vb4-6.cab / setup1.bas < prev    next >
Encoding:
BASIC Source File  |  1995-07-26  |  173.5 KB  |  5,090 lines

  1. Attribute VB_Name = "basSetup1"
  2. Option Explicit
  3. Option Compare Text
  4.  
  5. '
  6. 'Global Constants
  7. '
  8.  
  9. 'Return values for setup toolkit functions
  10. Global Const gintRET_CONT% = 1
  11. Global Const gintRET_CANCEL% = 2
  12. Global Const gintRET_EXIT% = 3
  13. Global Const gintRET_ABORT% = 4
  14. Global Const gintRET_FATAL% = 5
  15. Global Const gintRET_FINISHEDSUCCESS% = 6 'Used only as parameter to ExitSetup at end of successful install
  16.  
  17. 'Error levels for GetAppRemovalCmdLine()
  18. Global Const APPREMERR_NONE = 0 'no error
  19. Global Const APPREMERR_FATAL = 1 'fatal error
  20. Global Const APPREMERR_NONFATAL = 2 'non-fatal error, user chose to abort
  21. Global Const APPREMERR_USERCANCEL = 3 'user chose to cancel (no error)
  22.  
  23. 'Flag for Path Dialog specifying Source or Dest directory needed
  24. Global Const gstrDIR_SRC$ = "S"
  25. Global Const gstrDIR_DEST$ = "D"
  26.  
  27. 'Beginning of lines in [Files] and [Bootstrap] section of SETUP.LST
  28. Global Const gstrINI_FILE$ = "File"
  29. Global Const gstrINI_REMOTE$ = "Remote"
  30.  
  31. '
  32. 'Type Definitions
  33. '
  34. Type FILEINFO                                               'Setup information file line format
  35.     intDiskNum As Integer                                   'disk number
  36.     fSplit As Integer                                       'split flag
  37.     strSrcName As String                                    'name of source file
  38.     strDestName As String                                   'name of destination file
  39.     strDestDir As String                                    'destination directory
  40.     strRegister As String                                   'registration info
  41.     fShared As Boolean                                      'whether the file is shared or private
  42.     fSystem As Boolean                                      'whether the file is a system file (i.e. should be installed but never removed)
  43.     varDate As Variant                                      'file date
  44.     lFileSize As Long                                       'file size
  45.     sVerInfo As VERINFO                                     'file version number
  46. End Type
  47.  
  48. Type DISKINFO                                               'Disk drive information
  49.     lAvail As Long                                          'Bytes available on drive
  50.     lReq As Long                                            'Bytes required for setup
  51.     lMinAlloc As Long                                       'minimum allocation unit
  52. End Type
  53.  
  54. Type DESTINFO                                               'save dest dir for certain files
  55.     strAppDir As String
  56. #If Win16 Then
  57.     strBtrieve As String
  58. #End If
  59. #If Win32 Then
  60.     strAUTMGR32 As String
  61.     strRACMGR32 As String
  62. #End If
  63. End Type
  64.  
  65. Type REGINFO                                                'save registration info for files
  66.     strFileName As String
  67.     strRegister As String
  68.     
  69.     'The following are used only for remote server registration
  70.     strNetworkAddress As String
  71.     strNetworkProtocol As String
  72.     intAuthentication As Integer
  73. End Type
  74.  
  75. '
  76. 'Global Variables
  77. '
  78. Global gstrSETMSG As String
  79. Global gfRetVal As Integer                                  'return value for form based functions
  80. Global gstrAppName As String                                'name of app being installed
  81. Global gstrTitle As String                                  '"setup" name of app being installed
  82. Global gstrDestDir As String                                'dest dir for application files
  83. Global gstrAppExe As String                                 'name of app .EXE being installed
  84. Global gstrSrcPath As String                                'path of source files
  85. Global gstrSetupInfoFile As String                          'pathname of SETUP.LST file
  86. Global gstrWinDir As String                                 'windows directory
  87. Global gstrWinSysDir As String                              'windows\system directory
  88. Global gsDiskSpace() As DISKINFO                            'disk space for target drives
  89. Global gstrDrivesUsed As String                             'dest drives used by setup
  90. Global glTotalCopied As Long                                'total bytes copied so far
  91. Global gintCurrentDisk As Integer                           'current disk number being installed
  92. Global gsDest As DESTINFO                                   'dest dirs for certain files
  93. #If Win32 And LOGGING Then
  94. Global gstrAppRemovalLog As String                           'name of the app removal logfile
  95. Global gstrAppRemovalEXE As String                           'name of the app removal executable
  96. Global gfAppRemovalFilesMoved As Boolean                     'whether or not the app removal files have been moved to the application directory
  97. #End If
  98. Global gfForceUseDefDest As Boolean                         'If set to true, then the user will not be prompted for the destination directory
  99. Global fMainGroupWasCreated As Boolean                     'Whether or not a main folder/group has been created
  100.  
  101.  
  102. '
  103. 'Form/Module Constants
  104. '
  105.  
  106. 'Possible ProgMan actions
  107. Const mintDDE_ITEMADD% = 1                                  'AddProgManItem flag
  108. Const mintDDE_GRPADD% = 2                                   'AddProgManGroup flag
  109.  
  110. 'Special file names
  111. #If Win16 Then
  112. Const mstrFILE_BTRIEVE$ = "BTRIEVE.TRN"
  113. Const mstrAUTPRX16$ = "AUTPRX16.DLL"
  114. Const mstrAUTPRX$ = "AUTPRX.DLL"
  115. Global Const mstrFILE_RPCREG$ = "RPCREG.DAT"
  116. #End If
  117. #If Win32 And LOGGING Then
  118. Const mstrFILE_APPREMOVALLOGBASE$ = "ST4UNST"               'Base name of the app removal logfile
  119. Const mstrFILE_APPREMOVALLOGEXT$ = ".LOG"                   'Default extension for the app removal logfile
  120. Const mstrFILE_AUTMGR32 = "AUTMGR32.EXE"
  121. Const mstrFILE_RACMGR32 = "RACMGR32.EXE"
  122. Const mstrFILE_CTL3D32$ = "CTL3D32.DLL"
  123. #End If
  124.  
  125. 'Name of temporary file used for concatenation of split files
  126. Const mstrCONCATFILE$ = "VB4STTMP.CCT"
  127.  
  128. 'setup information file registration macros
  129. Const mstrDLLSELFREGISTER$ = "$(DLLSELFREGISTER)"
  130. Const mstrEXESELFREGISTER$ = "$(EXESELFREGISTER)"
  131. Const mstrREMOTEREGISTER$ = "$(REMOTE)"
  132.  
  133. '
  134. 'Form/Module Variables
  135. '
  136. Private msRegInfo() As REGINFO                                  'files to be registered
  137. Private mlTotalToCopy As Long                                   'total bytes to copy
  138. Private mintConcatFile As Integer                               'handle of dest file for concatenation
  139. Private mlSpaceForConcat As Long                                'extra space required for concatenation
  140. Private mstrConcatDrive As String                               'drive to use for concatenation
  141. Private mstrVerTmpName As String                                'temp file name for VerInstallFile API
  142. Public mstrLastCreatedShellGroup As String                      'last folder created via call to CreateShellGroup
  143.  
  144. ' Hkey cache (used for logging purposes)
  145. Private Type HKEY_CACHE
  146.     hkey As Long
  147.     strHkey As String
  148. End Type
  149.  
  150. Private hkeyCache() As HKEY_CACHE
  151.  
  152. #If Win32 Then
  153. ' Registry manipulation API's (32-bit)
  154. Global Const HKEY_CLASSES_ROOT = &H80000000
  155. Global Const HKEY_CURRENT_USER = &H80000001
  156. Global Const HKEY_LOCAL_MACHINE = &H80000002
  157. Global Const HKEY_USERS = &H80000003
  158. Const ERROR_SUCCESS = 0&
  159. Const ERROR_NO_MORE_ITEMS = 259&
  160.  
  161. Const REG_SZ = 1
  162. Const REG_BINARY = 3
  163. Const REG_DWORD = 4
  164.  
  165.  
  166. Declare Function OSRegCloseKey Lib "advapi32" Alias "RegCloseKey" (ByVal hkey As Long) As Long
  167. Declare Function OSRegCreateKey Lib "advapi32" Alias "RegCreateKeyA" (ByVal hkey As Long, ByVal lpszSubKey As String, phkResult As Long) As Long
  168. Declare Function OSRegDeleteKey Lib "advapi32" Alias "RegDeleteKeyA" (ByVal hkey As Long, ByVal lpszSubKey As String) As Long
  169. Declare Function OSRegEnumKey Lib "advapi32" Alias "RegEnumKeyA" (ByVal hkey As Long, ByVal iSubKey As Long, ByVal lpszName As String, ByVal cchName As Long) As Long
  170. Declare Function OSRegOpenKey Lib "advapi32" Alias "RegOpenKeyA" (ByVal hkey As Long, ByVal lpszSubKey As String, phkResult As Long) As Long
  171. Declare Function OSRegQueryValueEx Lib "advapi32" Alias "RegQueryValueExA" (ByVal hkey As Long, ByVal lpszValueName As String, ByVal dwReserved As Long, lpdwType As Long, lpbData As Any, cbData As Long) As Long
  172. Declare Function OSRegSetValueEx Lib "advapi32" Alias "RegSetValueExA" (ByVal hkey As Long, ByVal lpszValueName As String, ByVal dwReserved As Long, ByVal fdwType As Long, lpbData As Any, ByVal cbData As Long) As Long
  173. #Else
  174. ' Registry manipulation API's (16-bit)
  175. Global Const HKEY_CLASSES_ROOT = 1
  176. Const ERROR_SUCCESS = 0&
  177.  
  178. Const REG_SZ = 1
  179.  
  180. Declare Function OSRegCloseKey Lib "shell.dll" Alias "RegCloseKey" (ByVal hkey As Long) As Long
  181. Declare Function OSRegCreateKey Lib "shell.dll" Alias "RegCreateKey" (ByVal hkey As Long, ByVal lpszSubKey As String, phkResult As Long) As Long
  182. Declare Function OSRegDeleteKey Lib "shell.dll" Alias "RegDeleteKey" (ByVal hkey As Long, ByVal lpszSubKey As String) As Long
  183. Declare Function OSRegEnumKey Lib "shell.dll" Alias "RegEnumKey" (ByVal hkey As Long, ByVal iSubKey As Long, ByVal lpstrBuffer As String, ByVal cbBuffer As Long) As Long
  184. Declare Function OSRegOpenKey Lib "shell.dll" Alias "RegOpenKey" (ByVal hkey As Long, ByVal lpszSubKey As String, phkResult As Long) As Long
  185. Declare Function OSRegSetValue Lib "shell.dll" Alias "RegSetValue" (ByVal hkey As Long, ByVal lpszSubKey As String, ByVal fdwType As Long, ByVal lpszValue As Any, ByVal cb As Long) As Long
  186. Declare Function OSRegQueryValue Lib "shell.dll" Alias "RegQueryValue" (ByVal hkey As Long, ByVal lpszSubKey As Any, ByVal lpszValue As String, cb As Long) As Long
  187. #End If
  188.  
  189. #If Win32 Then
  190. Declare Function GetCurrentProcessId Lib "Kernel32" () As Long
  191. #End If
  192.  
  193. '-----------------------------------------------------------
  194. ' SUB: AddPerAppPath
  195. '
  196. ' Adds an application's full pathname and per-app path to the
  197. '   system registry (this is currently only meaningful to
  198. '   Windows 95).
  199. '
  200. ' IN: [strAppExe] - app EXE name, not including path
  201. '     [strAppDir] - full path of EXE, not including filename
  202. '     [strAppPath] - per-app path for this application
  203. '       (semicolon-separated list of directory path names)
  204. '       If this is the empty string (""), no per-app path
  205. '       is registered, but the full pathname of the
  206. '       exe IS still registered.
  207. '
  208. ' OUT:
  209. '   Example registry entries:
  210. '     HKEY_LOCAL_MACHINE\[strPathsBaseKeyName]\MyApp.Exe
  211. '       [Default]=C:\Program Files\MyApp\MyApp.Exe
  212. '       [Path]=C:\Program Files\MyApp;C:\Program Files\MyApp\System
  213. '
  214. '-----------------------------------------------------------
  215. '
  216. #If Win32 And LOGGING Then
  217. Sub AddPerAppPath(ByVal strAppExe As String, ByVal strAppDir As String, ByVal strPerAppPath As String)
  218.     If Not TreatAsWin95() Then
  219.         Exit Sub
  220.     End If
  221.     
  222.     Dim strPathsBaseKeyName As String
  223.     Const strAppPaths$ = "App Paths"
  224.     Const strAppPathKeyName = "Path"
  225.     Dim fOK As Boolean
  226.     Dim hkey As Long
  227.     
  228.     AddDirSep strAppDir
  229.     
  230.     ' Create the new key, whose name is based on the app's name
  231.    If Not RegCreateKey(HKEY_LOCAL_MACHINE, RegPathWinCurrentVersion(), strAppPaths & gstrSEP_DIR & strAppExe, hkey) Then
  232.         GoTo Err
  233.     End If
  234.     
  235.     fOK = True
  236.     
  237.     ' Default value indicates full EXE pathname
  238.     fOK = fOK And RegSetStringValue(hkey, "", strAppDir & strAppExe)
  239.     
  240.     ' [Path] value indicates the per-app path
  241.     If strPerAppPath <> "" Then
  242.         fOK = fOK And RegSetStringValue(hkey, strAppPathKeyName, strPerAppPath)
  243.     End If
  244.     
  245.     If Not fOK Then
  246.         GoTo Err
  247.     End If
  248.     
  249.     RegCloseKey hkey
  250.     
  251.     Exit Sub
  252.     
  253. Err:
  254.     MsgError ResolveResString(resERR_REG), vbExclamation Or vbOKOnly, gstrTitle
  255. End Sub
  256. #End If
  257.  
  258. '-----------------------------------------------------------
  259. ' FUNCTION: AddQuotesToFN
  260. '
  261. ' Given a pathname (directory and/or filename), returns
  262. '   that pathname surrounded by double quotes if the
  263. '   path contains spaces or commas.  This is required for
  264. '   setting up an icon correctly, since otherwise such paths
  265. '   would be interpreted as a pathname plus arguments.
  266. '-----------------------------------------------------------
  267. '
  268. Function AddQuotesToFN(ByVal strFileName) As String
  269.     #If Win32 Then
  270.         If InStr(strFileName, " ") Or InStr(strFileName, ",") Then
  271.             AddQuotesToFN = """" & strFileName & """"
  272.         Else
  273.             AddQuotesToFN = strFileName
  274.         End If
  275.     #Else
  276.         'Quotes around filenames are not in general supported by 16-bit
  277.         AddQuotesToFN = strFileName
  278.     #End If
  279. End Function
  280.  
  281. #If Win16 Then
  282. '-----------------------------------------------------------
  283. ' SUB: AddShareIfNeeded
  284. '
  285. ' Adds file sharing capability which is required for VB4
  286. '-----------------------------------------------------------
  287. '
  288. Sub AddShareIfNeeded()
  289.     Const strSECTIONSTART$ = "["
  290.     Const strCOMMENT$ = ";"
  291.     Const strASSIGN$ = "="
  292.     Const strFILE_VSHARE$ = "VSHARE.386"
  293.     Const strINI_DEVICE$ = "DEVICE="
  294.     Const strFILE_SYSINI$ = "SYSTEM.INI"
  295.     Const strFILE_SYSTEM$ = "SYSTEM."
  296.     Const strINI_386Enh$ = "[386ENH]"
  297.  
  298.     Dim intCount As Integer
  299.     Dim intPos As Integer
  300.     Dim intSysIniFile As Integer
  301.     Dim intTmpFile As Integer
  302.     Dim strTmp As String
  303.     Dim strTmpFileName As String
  304.  
  305.     On Error GoTo ASINError
  306.  
  307.     '
  308.     'If not running under Win 3.x, i.e.; if running under NT 3.5 WOW, etc.
  309.     '
  310.     If IsWindowsNT() Or IsWindows95() Then
  311.         Exit Sub
  312.     End If
  313.     
  314.     '
  315.     '
  316.     'Un-Cache System.Ini File
  317.     '
  318.     intCount = WritePrivateProfileString(0&, 0&, 0&, strFILE_SYSINI)
  319.  
  320.     '
  321.     'Open system.ini file and read until we get to the [386Enh] section
  322.     '
  323.     intSysIniFile = FreeFile
  324.     Open gstrWinDir & strFILE_SYSINI For Input Access Read As intSysIniFile
  325.  
  326.     intCount = 0
  327.  
  328.     Do
  329.         Line Input #intSysIniFile, strTmp
  330.         If EOF(intSysIniFile) Then
  331.             GoTo ASINError
  332.         End If
  333.  
  334.         intCount = intCount + 1
  335.     Loop While Left$(strTmp, Len(strINI_386Enh)) <> strINI_386Enh
  336.  
  337.     '
  338.     'Check each 'device=' line to see if it contains vshare.386 (but not as a comment)
  339.     'If we run out of 'device=' lines before finding it, then we know we need to add it
  340.     '
  341.     Do While Not EOF(intSysIniFile)
  342.         Line Input #intSysIniFile, strTmp
  343.         If Left$(strTmp, Len(strINI_DEVICE)) = strINI_DEVICE Then
  344.             intPos = InStr(strTmp, strASSIGN)
  345.             If intPos = 0 Then
  346.                 GoTo ASINError
  347.             End If
  348.             intPos = InStr(intPos, strTmp, strCOMMENT)
  349.             If intPos > 0 Then
  350.                 strTmp = Left$(strTmp, intPos - 1)
  351.             End If
  352.             If InStr(strTmp, strFILE_VSHARE) > 0 Then
  353.                 '
  354.                 'File already includes vshare.386, bail out
  355.                 '
  356.                 Close intSysIniFile
  357.                 Exit Sub
  358.             End If
  359.         Else
  360.             If Left$(strTmp, 1) = strSECTIONSTART Then
  361.                 Exit Do
  362.             End If
  363.         End If
  364.  
  365.         intCount = intCount + 1
  366.     Loop
  367.  
  368.     '
  369.     'Rewind to beginning of system.ini file and open a temporary file
  370.     '
  371.     Seek intSysIniFile, 1
  372.     strTmpFileName = Space$(gintMAX_SIZE)
  373.     If GetTempFileName(0, gstrNULL, 0, strTmpFileName) = 0 Then
  374.         GoTo ASINError
  375.     End If
  376.  
  377.     intTmpFile = FreeFile
  378.     Open strTmpFileName For Output As intTmpFile
  379.  
  380.     '
  381.     'Read all lines before the location we'll be adding vshare and write them out to a temporary file
  382.     '
  383.     Do
  384.         Line Input #intSysIniFile, strTmp
  385.         Print #intTmpFile, strTmp
  386.         intCount = intCount - 1
  387.     Loop While intCount > 0
  388.  
  389.     '
  390.     'Add device=vshare.386 line
  391.     '
  392.     Print #intTmpFile, strINI_DEVICE & strFILE_VSHARE
  393.     
  394.     '
  395.     'Write out remainder of file
  396.     '
  397.     Do While Not EOF(intSysIniFile)
  398.         Line Input #intSysIniFile, strTmp
  399.         Print #intTmpFile, strTmp
  400.     Loop
  401.  
  402.     Close intSysIniFile
  403.     Close intTmpFile
  404.  
  405.     intCount = 0
  406.  
  407.     On Error Resume Next
  408.  
  409.     '
  410.     'Rename existing system.ini to system.00x
  411.     '
  412.     Do
  413.         Err = 0
  414.         Name gstrWinDir & strFILE_SYSINI As gstrWinDir & strFILE_SYSTEM & Format$(intCount, "000")
  415.         intCount = intCount + 1
  416.         If Err > 0 And Err <> 58 Then
  417.             GoTo ASINError
  418.         End If
  419.     Loop While Err = 58     'File already exists
  420.  
  421.     On Error GoTo ASINError
  422.  
  423.     '
  424.     'Rename or copy new file to system.ini
  425.     '
  426.     If Left$(strTmpFileName, 1) = Left$(gstrWinDir, 1) Then
  427.         Name strTmpFileName As gstrWinDir & strFILE_SYSINI
  428.     Else
  429.         FileCopy strTmpFileName, gstrWinDir & strFILE_SYSINI
  430.         Kill strTmpFileName
  431.     End If
  432.  
  433.     Err = 0
  434.  
  435.     Exit Sub
  436.  
  437. ASINError:
  438.     If intSysIniFile > 0 Then
  439.         Close intSysIniFile
  440.         If intTmpFile > 0 Then
  441.             Close intTmpFile
  442.         End If
  443.     End If
  444.  
  445.     MsgError ResolveResString(resERR_VSHARE), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  446.  
  447.     Exit Sub
  448. End Sub
  449. #End If
  450.  
  451. '-----------------------------------------------------------
  452. ' SUB: CalcDiskSpace
  453. '
  454. ' Calculates disk space required for installing the files
  455. ' listed in the specified section of the setup information
  456. ' file (SETUP.LST)
  457. '-----------------------------------------------------------
  458. '
  459. Sub CalcDiskSpace(ByVal strSECTION As String)
  460.     Static fSplitFile As Integer
  461.     Static lDestFileSpace As Long
  462.  
  463.     Dim intIdx As Integer
  464.     Dim intDrvIdx As Integer
  465.     Dim sFile As FILEINFO
  466.     Dim strDrive As String
  467.     Dim lThisFileSpace As Long
  468.  
  469.     intIdx = 1
  470.  
  471.     On Error GoTo CalcDSError
  472.  
  473.     '
  474.     'For each file in the specified section, read info from the setup info file
  475.     '
  476.     Do While ReadSetupFileLine(strSECTION, intIdx, sFile) = True
  477.         '
  478.         'if the file isn't split or if this is the first section of a split file
  479.         '
  480.         If sFile.strDestDir <> gstrNULL Then
  481.             fSplitFile = sFile.fSplit
  482.  
  483.             '
  484.             'Get the dest drive used for this file.  If this is the first file using
  485.             'the drive for a destination, add the drive to the drives used 'table',
  486.             'allocate an array element for the holding the drive info, and get
  487.             'available disk space and minimum allocation unit
  488.             '
  489.             strDrive = Left$(sFile.strDestDir, 1)
  490.         
  491.             intDrvIdx = InStr(gstrDrivesUsed, strDrive)
  492.             If intDrvIdx = 0 Then
  493.                 gstrDrivesUsed = gstrDrivesUsed & strDrive
  494.                 intDrvIdx = Len(gstrDrivesUsed)
  495.  
  496.                 ReDim Preserve gsDiskSpace(intDrvIdx)
  497.                 gsDiskSpace(intDrvIdx).lAvail = GetDiskSpaceFree(strDrive)
  498.  
  499.                 gsDiskSpace(intDrvIdx).lMinAlloc = GetDrivesAllocUnit(strDrive)
  500.             End If
  501.  
  502.             '
  503.             'Calculate size of the dest final (file size + minimum allocation for drive)
  504.             '
  505.             lThisFileSpace = CalcFinalSize(sFile.lFileSize, strDrive)
  506.             mlTotalToCopy = mlTotalToCopy + lThisFileSpace
  507.  
  508.             '
  509.             'If the file already exists, then if we copy it at all, we'll be
  510.             'replacing it.  So, we get the size of the existing dest file so
  511.             'that we can subtract it from the amount needed later.
  512.             '
  513.             If FileExists(sFile.strDestDir & sFile.strDestName) Then
  514.                 lDestFileSpace = FileLen(sFile.strDestDir & sFile.strDestName)
  515.             Else
  516.                 lDestFileSpace = 0
  517.             End If
  518.         End If
  519.  
  520.         '
  521.         'If file not split, or if the last section of a split file
  522.         '
  523.         If sFile.fSplit = False Then
  524.             '
  525.             'If this is the last section of a split file, then if it's the *largest*
  526.             'split file, set the extra space needed for concatenation to this size
  527.             '
  528.             If fSplitFile = True And lThisFileSpace > mlSpaceForConcat Then
  529.                 mlSpaceForConcat = lThisFileSpace
  530.             End If
  531.  
  532.             '
  533.             'Subtract size of existing dest file, if applicable and then accumulate
  534.             'space required
  535.             '
  536.             lThisFileSpace = lThisFileSpace - lDestFileSpace
  537.             If lThisFileSpace < 0 Then
  538.                 lThisFileSpace = 0
  539.             End If
  540.  
  541.             gsDiskSpace(intDrvIdx).lReq = gsDiskSpace(intDrvIdx).lReq + lThisFileSpace
  542.         End If
  543.  
  544.         intIdx = intIdx + 1
  545.     Loop
  546.  
  547.     Exit Sub
  548.  
  549. CalcDSError:
  550.     MsgError Error$ & LS$ & ResolveResString(resCALCSPACE), MB_ICONSTOP, gstrSETMSG
  551.     ExitSetup frmMessage, gintRET_FATAL
  552. End Sub
  553.  
  554. '-----------------------------------------------------------
  555. ' SUB: CalcFinalSize
  556. '
  557. ' Computes the space required for a file of the size
  558. ' specified on the given dest path.  This includes the
  559. ' file size plus a padding to ensure that the final size
  560. ' is a multiple of the minimum allocation unit for the
  561. ' dest drive
  562. '-----------------------------------------------------------
  563. '
  564. Function CalcFinalSize(lBaseFileSize As Long, strDestPath As String) As Long
  565.     Dim lMinAlloc As Long
  566.     Dim intPadSize As Long
  567.  
  568.     lMinAlloc = gsDiskSpace(InStr(gstrDrivesUsed, Left$(strDestPath, 1))).lMinAlloc
  569.     intPadSize = lMinAlloc - (lBaseFileSize Mod lMinAlloc)
  570.     If intPadSize = lMinAlloc Then
  571.         intPadSize = 0
  572.     End If
  573.  
  574.     CalcFinalSize = lBaseFileSize + intPadSize
  575. End Function
  576.  
  577. '-----------------------------------------------------------
  578. ' SUB: CenterForm
  579. '
  580. ' Centers the passed form just above center on the screen
  581. '-----------------------------------------------------------
  582. '
  583. Sub CenterForm(frm As Form)
  584.     SetMousePtr gintMOUSE_HOURGLASS
  585.  
  586.     frm.Top = (Screen.Height * 0.85) \ 2 - frm.Height \ 2
  587.     frm.Left = Screen.Width \ 2 - frm.Width \ 2
  588.  
  589.     SetMousePtr gintMOUSE_DEFAULT
  590. End Sub
  591.  
  592. '-----------------------------------------------------------
  593. ' FUNCTION: CheckDiskSpace
  594. '
  595. ' Reads from the space required array generated by calling
  596. ' the 'CalcDiskSpace' function and determines whether there
  597. ' is sufficient free space on all of the drives used for
  598. ' installation
  599. '
  600. ' Returns: True if there is enough space, False otherwise
  601. '-----------------------------------------------------------
  602. '
  603. Function CheckDiskSpace() As Integer
  604.     Static fDontAskOnSpaceErr As Integer
  605.  
  606.     Dim intIdx As Integer
  607.     Dim intTmpDrvIdx As Integer
  608.     Dim lDiskSpaceLeft As Long
  609.     Dim lMostSpaceLeft As Long
  610.                                              
  611.     '
  612.     'Default to True (enough space on all drives)
  613.     '
  614.     CheckDiskSpace = True
  615.  
  616.     '
  617.     'For each drive that is the destination for one or more files, compare
  618.     'the space available to the space required.
  619.     '
  620.     For intIdx = 1 To Len(gstrDrivesUsed)
  621.         lDiskSpaceLeft = gsDiskSpace(intIdx).lAvail - gsDiskSpace(intIdx).lReq
  622.         If lDiskSpaceLeft < 0 Then
  623.             GoSub CheckDSAskSpace
  624.         Else
  625.             '
  626.             'If no "TMP" drive was found, or if the "TMP" drive wasn't ready,
  627.             'save the index of the drive and the amount of space on the drive
  628.             'which will have the most free space.  If no "TMP" drive was
  629.             'found in InitDiskInfo(), then this drive will be used as a
  630.             'temporary drive for concatenating split files
  631.             '
  632.             If mstrConcatDrive = gstrNULL Then
  633.                 If lDiskSpaceLeft > lMostSpaceLeft Then
  634.                     lMostSpaceLeft = lDiskSpaceLeft
  635.                     intTmpDrvIdx = intIdx
  636.                 End If
  637.             Else
  638.                 '
  639.                 '"TMP" drive was specified, so we'll use that
  640.                 '
  641.                 If Left$(mstrConcatDrive, 1) = Mid$(gstrDrivesUsed, intIdx, 1) Then
  642.                     intTmpDrvIdx = intIdx
  643.                 End If
  644.             End If
  645.         End If
  646.     Next
  647.  
  648.     '
  649.     'If at least one drive was specified as a destination (if there was at least
  650.     'one CalcDiskSpace call in Form_Load of SETUP1.FRM), then subtract the extra
  651.     'space needed for concatenation from either:
  652.     '   The "TMP" drive if available  - OR -
  653.     '   The drive with the most space remaining
  654.     '
  655.     If intTmpDrvIdx > 0 Then
  656.         gsDiskSpace(intTmpDrvIdx).lReq = gsDiskSpace(intTmpDrvIdx).lReq + mlSpaceForConcat
  657.         If gsDiskSpace(intTmpDrvIdx).lAvail < gsDiskSpace(intTmpDrvIdx).lReq Then
  658.             GoSub CheckDSAskSpace
  659.         End If
  660.  
  661.         '
  662.         'If a "TMP" drive was found, we use it regardless, otherwise we use the drive
  663.         'with the most free space
  664.         '
  665.         If mstrConcatDrive = gstrNULL Then
  666.             mstrConcatDrive = Mid$(gstrDrivesUsed, intTmpDrvIdx, 1) & gstrCOLON & gstrSEP_DIR
  667.             AddDirSep mstrConcatDrive
  668.         End If
  669.     End If
  670.  
  671.     Exit Function
  672.  
  673. CheckDSAskSpace:
  674.     '
  675.     'if the user hasn't been prompted before in the event of not enough free space,
  676.     'then display table of drive space and allow them to (basically) abort, retry,
  677.     'or ignore.
  678.     '
  679.     If fDontAskOnSpaceErr = False Then
  680.         frmDskSpace.Show 1
  681.         If gfRetVal <> gintRET_CONT Then
  682.             CheckDiskSpace = False
  683.             Exit Function
  684.         Else
  685.             fDontAskOnSpaceErr = True
  686.         End If
  687.     End If
  688.  
  689.     Return
  690. End Function
  691.  
  692. '-----------------------------------------------------------
  693. ' FUNCTION: CheckDrive
  694. '
  695. ' Check to see if the specified drive is ready to be read
  696. ' from.  In the case of a drive that holds removable media,
  697. ' this would mean that formatted media was in the drive and
  698. ' that the drive door was closed.
  699. '
  700. ' IN: [strDrive] - drive to check
  701. '     [strCaption] - caption if the drive isn't ready
  702. '
  703. ' Returns: True if the drive is ready, False otherwise
  704. '-----------------------------------------------------------
  705. '
  706. Function CheckDrive(ByVal strDrive As String, ByVal strCaption As String) As Integer
  707.     Dim strDir As String
  708.     Dim strMsg As String
  709.     Dim fIsUNC As Boolean
  710.  
  711.     On Error Resume Next
  712.  
  713.     SetMousePtr gintMOUSE_HOURGLASS
  714.  
  715.     Do
  716.         Err = 0
  717.         fIsUNC = False
  718.         '
  719.         'Attempt to read the current directory of the specified drive.  If
  720.         'an error occurs, we assume that the drive is not ready
  721.         '
  722.         If IsUNCName(strDrive) Then
  723.             fIsUNC = True
  724.             strDir = Dir$(GetUNCShareName(strDrive))
  725.         Else
  726.             strDir = Dir$(Left$(strDrive, 2))
  727.         End If
  728.  
  729.         If Err > 0 Then
  730.             If fIsUNC Then
  731.                 strMsg = Error$ & LS$ & ResolveResString(resCANTREADUNC, "|1", strDrive) & LS$ & ResolveResString(resCHECKUNC)
  732.             Else
  733.                 strMsg = Error$ & LS$ & ResolveResString(resDRVREAD) & strDrive & LS$ & ResolveResString(resDRVCHK)
  734.             End If
  735.             If MsgError(strMsg, MB_ICONEXCLAMATION Or MB_RETRYCANCEL, strCaption) = IDCANCEL Then
  736.                 CheckDrive = False
  737.                 Err = 0
  738.             End If
  739.         Else
  740.             CheckDrive = True
  741.         End If
  742.     Loop While Err
  743.  
  744.     SetMousePtr gintMOUSE_DEFAULT
  745. End Function
  746.  
  747. '-----------------------------------------------------------
  748. ' FUNCTION: CheckOverwritePrivateFile
  749. '
  750. ' Checks if a private file that we are about to install
  751. ' already exists in the destination directory.  If it
  752. ' does, there will be problems if the user ever tries to
  753. ' remove either application, so warn the user and suggest
  754. ' selecting a different destination directory.
  755. '
  756. ' IN: [strFN] - Full path of the private file that is
  757. '               about to be installed.
  758. '
  759. '-----------------------------------------------------------
  760. '
  761. Sub CheckOverwritePrivateFile(ByVal strFN As String)
  762.     Static fIgnoreOverwrite As Boolean
  763.     
  764.     If fIgnoreOverwrite Then
  765.         'If the users once chooses to ignore this warning,
  766.         'we will not bring it up again.
  767.         Exit Sub
  768.     End If
  769.     
  770.     If FileExists(strFN) Then
  771.         Do
  772.             Select Case MsgError(ResolveResString(resOVERWRITEPRIVATE) & LS$ & ResolveResString(resCANCELSETUP), vbYesNo Or vbDefaultButton1 Or vbExclamation, gstrTitle)
  773.             Case vbYes
  774.                 'The user chose to cancel.  (This is best.)
  775.                 MsgError ResolveResString(resCHOOSENEWDEST), vbOKOnly, gstrTitle
  776.                 ExitSetup frmCopy, gintRET_FATAL
  777.             Case Else
  778.                 'One more level of warning to let them know that we highly
  779.                 '  recommend cancelling setup at this point
  780.                 Select Case MsgError(ResolveResString(resOVERWRITEPRIVATE2) & LS$ & ResolveResString(resVERIFYCONTINUE), vbYesNo Or vbDefaultButton2 Or vbExclamation, gstrTitle)
  781.                 Case vbNo
  782.                     'User chose "no, don't continue"
  783.                     'Repeat the first-level warning
  784.                 Case Else
  785.                     'They decided to continue anyway
  786.                     fIgnoreOverwrite = True
  787.                     Exit Do
  788.                 End Select
  789.             End Select
  790.         Loop
  791.     End If
  792. End Sub
  793.  
  794. '-----------------------------------------------------------
  795. ' FUNCTION: ConcatSplitFile
  796. '
  797. ' Reads and appends the source file passed in onto the
  798. ' previously opened destination file specified by
  799. ' mintConcatFile.  mintConcatFile should be opened
  800. ' by calling OpenConcatFile() before calling this function.
  801. '
  802. ' IN: [strSrcName] - Source file to append to destination
  803. '
  804. ' Returns: True if copy was successful, IDIGNORE if user
  805. '          elects to ignore a reported copy error
  806. '-----------------------------------------------------------
  807. '
  808. Function ConcatSplitFile(ByVal strSrcName As String) As Integer
  809.     Const lMAXCOPYBUF& = 64512
  810.     Const lMINCOPYBUFSIZE& = 4096
  811.     Const intOPEN% = 1
  812.     Const intGET% = 2
  813.     Const intPUT% = 3
  814.     Const intMEMFAIL% = 4
  815.  
  816.     Dim intSrcFile As Integer
  817.     Dim intStatus As Integer
  818.     Dim lBytesLeftToWrite As Long
  819.     Dim lBytesThisTime As Long
  820.     Dim byteFileBuf() As Byte 'This must be byte rather than String, so no Unicode conversion takes place
  821.     Dim strMsg As String
  822.  
  823.     On Error GoTo CSFError
  824.     
  825.     '
  826.     'Ensure that the specified source file is available
  827.     '
  828.     If DetectFile(strSrcName) = IDIGNORE Then
  829.         ConcatSplitFile = IDIGNORE
  830.         Exit Function
  831.     End If
  832.  
  833.     lBytesLeftToWrite = FileLen(strSrcName)
  834.  
  835.     '
  836.     'For error reporting, flag that we're attempting to open the file now
  837.     '
  838.     intStatus = intOPEN
  839.  
  840.     '
  841.     'Open the source file for reading now
  842.     '
  843.     intSrcFile = FreeFile
  844.     Open strSrcName For Binary Access Read As intSrcFile
  845.  
  846.     '
  847.     'Initially, we'll try to copy lMAXCOPYBUF bytes at a time.  If our attempt
  848.     'to allocate a copy buffer (Space$(...)) fails, the error handling logic
  849.     'will cause the buffer size to be halved and another allocation attempt to
  850.     'be made.
  851.     '
  852.     lBytesThisTime = lMAXCOPYBUF
  853.     ReDim byteFileBuf(1 To lBytesThisTime) As Byte
  854.  
  855.     While lBytesLeftToWrite <> 0
  856.         '
  857.         'while source file hasn't been read, if the number of bytes left is bigger than
  858.         'the buffer size, reduce the buffer size
  859.         '
  860.         If lBytesThisTime > lBytesLeftToWrite Then
  861.             lBytesThisTime = lBytesLeftToWrite
  862.             ReDim byteFileBuf(1 To lBytesThisTime) As Byte
  863.         End If
  864.         
  865.         '
  866.         'Set operation status and Get from the source file and Put to the dest file
  867.         '
  868.         intStatus = intGET
  869.         Get intSrcFile, , byteFileBuf
  870.  
  871.         intStatus = intPUT
  872.         Put mintConcatFile, , byteFileBuf
  873.  
  874.         lBytesLeftToWrite = lBytesLeftToWrite - lBytesThisTime
  875.     Wend
  876.  
  877.     ConcatSplitFile = True
  878.     GoTo CSFCleanup
  879.  
  880. CSFError:
  881.     If Err = 14 Then    'Out of String Space
  882.         lBytesThisTime = lBytesThisTime \ 2
  883.         If lBytesThisTime >= lMINCOPYBUFSIZE Then
  884.             Resume
  885.         Else
  886.             intStatus = intMEMFAIL
  887.         End If
  888.     End If
  889.  
  890.     strMsg = LF$ & strSrcName
  891.  
  892.     Select Case intStatus
  893.     Case intOPEN
  894.         strMsg = ResolveResString(resCANTOPEN) & strMsg
  895.     Case intGET
  896.         strMsg = ResolveResString(resCANTREAD) & strMsg
  897.     Case intPUT
  898.         strMsg = ResolveResString(resCANTWRITE) & strMsg & LS$ & ResolveResString(resCHKSPACE)
  899.     Case intMEMFAIL
  900.         strMsg = ResolveResString(resOUTOFMEMORY) & strMsg
  901.     End Select
  902.  
  903.     Select Case MsgError(Error$ & LS$ & strMsg, MB_ABORTRETRYIGNORE Or MB_ICONEXCLAMATION Or MB_DEFBUTTON2, gstrSETMSG)
  904.     Case IDABORT
  905.         ExitSetup frmCopy, gintRET_ABORT
  906.     Case IDIGNORE
  907.         ConcatSplitFile = IDIGNORE
  908.     Case IDRETRY
  909.         Resume
  910.     End Select
  911.  
  912. CSFCleanup:
  913.     Close intSrcFile
  914.     Err = 0
  915.     Exit Function
  916. End Function
  917.  
  918. '-----------------------------------------------------------
  919. ' FUNCTION: CopyFile
  920. '
  921. ' Uses the Windows VerInstallFile API to copy a file from
  922. ' the specified source location/name to the destination
  923. ' location/name.  Split files should be combined via the
  924. ' '...Concat...' file routines before calling this
  925. ' function.
  926. ' If the file is successfully updated and the file is a
  927. ' shared file (fShared = True), then the
  928. ' files reference count is updated (32-bits only)
  929. '
  930. ' IN: [strSrcDir] - directory where source file is located
  931. '     [strDestDir] - destination directory for file
  932. '     [strSrcName] - name of source file
  933. '     [strDestName] - name of destination file
  934. '
  935. ' PRECONDITION: NewAction() must have already been called
  936. '               for this file copy (of type either
  937. '               gstrKEY_SHAREDFILE or gstrKEY_PRIVATE --
  938. '               see CopySection for an example of how
  939. '               this works).  See NewAction() and related
  940. '               functions in LOGGING.BAS for comments on
  941. '               using the logging function.
  942. '               Either CommitAction() or AbortAction() will
  943. '               allows be called by this procedure, and
  944. '               should not be done by the caller.
  945. '
  946. ' Returns: True if copy was successful, False otherwise
  947. '
  948. ' POSTCONDITION: The current action will be either committed or
  949. '                aborted.
  950. '-----------------------------------------------------------
  951. '
  952. Function CopyFile(ByVal strSrcDir As String, ByVal strDestDir As String, ByVal strSrcName As String, ByVal strDestName As String, ByVal fShared As Boolean) As Boolean
  953.     Const intUNKNOWN% = 0
  954.     Const intCOPIED% = 1
  955.     Const intNOCOPY% = 2
  956.     Const intFILEUPTODATE% = 3
  957.  
  958.     '
  959.     'VerInstallFile() Flags
  960.     '
  961.     Const VIFF_FORCEINSTALL% = &H1
  962.     Const VIF_TEMPFILE& = &H1
  963.     Const VIF_SRCOLD& = &H4
  964.     Const VIF_DIFFLANG& = &H8
  965.     Const VIF_DIFFCODEPG& = &H10
  966.     Const VIF_DIFFTYPE& = &H20
  967.     Const VIF_WRITEPROT& = &H40
  968.     Const VIF_FILEINUSE& = &H80
  969.     Const VIF_OUTOFSPACE& = &H100
  970.     Const VIF_ACCESSVIOLATION& = &H200
  971.     Const VIF_SHARINGVIOLATION = &H400
  972.     Const VIF_CANNOTCREATE = &H800
  973.     Const VIF_CANNOTDELETE = &H1000
  974.     Const VIF_CANNOTRENAME = &H2000
  975.     Const VIF_OUTOFMEMORY = &H8000&
  976.     Const VIF_CANNOTREADSRC = &H10000
  977.     Const VIF_CANNOTREADDST = &H20000
  978.     Const VIF_BUFFTOOSMALL = &H40000
  979.  
  980.     Static fIgnoreWarn As Integer             'user warned about ignoring error?
  981.  
  982.     Dim strMsg As String
  983.     Dim lRC As Long
  984.     Dim lpTmpNameLen As Long
  985.     Dim intFlags As Integer
  986.     Dim intRESULT As Integer
  987.     Dim fFileAlreadyExisted
  988.  
  989.     On Error Resume Next
  990.  
  991.     CopyFile = False
  992.  
  993.     '
  994.     'Ensure that the source file is available for copying
  995.     '
  996.     If DetectFile(strSrcDir & strSrcName) = IDIGNORE Then
  997.         #If Win32 And LOGGING Then
  998.             AbortAction
  999.         #End If
  1000.         Exit Function
  1001.     End If
  1002.     
  1003.     '
  1004.     'Make the destination directory, prompt the user to retry if there is an error
  1005.     '
  1006.     If Not MakePath(strDestDir) Then
  1007.         #If Win32 And LOGGING Then
  1008.             AbortAction ' Abort file copy
  1009.         #End If
  1010.         Exit Function
  1011.     End If
  1012.  
  1013.     '
  1014.     'Make sure we have the LFN (long filename) of the destination directory
  1015.     '
  1016.     #If Win32 Then
  1017.     strDestDir = GetLongPathName(strDestDir)
  1018.     #End If
  1019.     
  1020.     '
  1021.     'Setup for VerInstallFile call
  1022.     '
  1023.     lpTmpNameLen = gintMAX_SIZE
  1024.     mstrVerTmpName = String$(lpTmpNameLen, 0)
  1025.     intFlags = 0
  1026.     fFileAlreadyExisted = FileExists(strDestDir & strDestName)
  1027.  
  1028.     intRESULT = intUNKNOWN
  1029.  
  1030.     Do While intRESULT = intUNKNOWN
  1031.         'VerInstallFile does not properly handle long filenames,
  1032.         '  so we must give it the short names.
  1033.         Dim strShortSrcName As String
  1034.         Dim strShortDestName As String
  1035.         Dim strShortSrcDir As String
  1036.         Dim strShortDestDir As String
  1037.         
  1038.         #If Win32 Then
  1039.             If Not FileExists(strDestDir & strDestName) Then
  1040.                 'If the destination file does not already
  1041.                 '  exist, we create a dummy with the correct
  1042.                 '  (long) filename so that we can get its
  1043.                 '  short filename for VerInstallFile.
  1044.                 Open strDestDir & strDestName For Output Access Write As #1
  1045.                 Close #1
  1046.             End If
  1047.         
  1048.             On Error GoTo UnexpectedErr
  1049.             strShortSrcDir = GetShortPathName(strSrcDir)
  1050.             strShortSrcName = GetFileName(GetShortPathName(strSrcDir & strSrcName))
  1051.             strShortDestDir = GetShortPathName(strDestDir)
  1052.             strShortDestName = GetFileName(GetShortPathName(strDestDir & strDestName))
  1053.             On Error Resume Next
  1054.         #Else
  1055.             'We cannot support installing long filenames under 16-bit platforms
  1056.             strShortSrcName = strSrcName
  1057.             strShortSrcDir = strSrcDir
  1058.             strShortDestName = strDestName
  1059.             strShortDestDir = strDestDir
  1060.         #End If
  1061.         
  1062.         lRC = VerInstallFile(intFlags, strShortSrcName, strShortDestName, strShortSrcDir, strShortDestDir, 0&, mstrVerTmpName, lpTmpNameLen)
  1063.         If Err <> 0 Then
  1064.             '
  1065.             'If the version or file expansion DLLs couldn't be found, then abort setup
  1066.             '
  1067.             ExitSetup frmCopy, gintRET_FATAL
  1068.         End If
  1069.  
  1070.         If lRC = 0 Then
  1071.             '
  1072.             'File was successfully installed, increment reference count if needed
  1073.             '
  1074.             
  1075.             'One more kludge for long filenames: VerInstallFile may have renamed
  1076.             'the file to its short version if it went through with the copy.
  1077.             'Therefore we simply rename it back to what it should be.
  1078.             Name strDestDir & strShortDestName As strDestDir & strDestName
  1079.             intRESULT = intCOPIED
  1080.         ElseIf lRC And VIF_SRCOLD Then
  1081.             '
  1082.             'Source file was older, so not copied, the existing version of the file
  1083.             'will be used.  Increment reference count if needed
  1084.             '
  1085.             intRESULT = intFILEUPTODATE
  1086.         ElseIf lRC And (VIF_DIFFLANG Or VIF_DIFFCODEPG Or VIF_DIFFTYPE) Then
  1087.             '
  1088.             'We retry and force installation for these cases.  You can modify the code
  1089.             'here to prompt the user about what to do.
  1090.             '
  1091.             intFlags = VIFF_FORCEINSTALL
  1092.         ElseIf lRC And VIF_WRITEPROT Then
  1093.             strMsg = ResolveResString(resWRITEPROT)
  1094.             GoSub CFMsg
  1095.         ElseIf lRC And VIF_FILEINUSE Then
  1096.             strMsg = ResolveResString(resINUSE)
  1097.             GoSub CFMsg
  1098.         ElseIf lRC And VIF_OUTOFSPACE Then
  1099.             strMsg = ResolveResString(resOUTOFSPACE) & Left$(strDestDir, 2)
  1100.             GoSub CFMsg
  1101.         ElseIf lRC And VIF_ACCESSVIOLATION Then
  1102.             strMsg = ResolveResString(resACCESSVIOLATION)
  1103.             GoSub CFMsg
  1104.         ElseIf lRC And VIF_SHARINGVIOLATION Then
  1105.             strMsg = ResolveResString(resSHARINGVIOLATION)
  1106.             GoSub CFMsg
  1107.         ElseIf lRC And VIF_OUTOFMEMORY Then
  1108.             strMsg = ResolveResString(resOUTOFMEMORY)
  1109.             GoSub CFMsg
  1110.         Else
  1111.             '
  1112.             'For these cases, we generically report the error and do not install the file
  1113.             '
  1114.             If lRC And VIF_CANNOTCREATE Then
  1115.                 strMsg = ResolveResString(resCANNOTCREATE)
  1116.             ElseIf lRC And VIF_CANNOTDELETE Then
  1117.                 strMsg = ResolveResString(resCANNOTDELETE)
  1118.             ElseIf lRC And VIF_CANNOTRENAME Then
  1119.                 strMsg = ResolveResString(resCANNOTRENAME)
  1120.             ElseIf lRC And VIF_CANNOTREADSRC Then
  1121.                 strMsg = ResolveResString(resCANNOTREADSRC)
  1122.             ElseIf lRC And VIF_CANNOTREADDST Then
  1123.                 strMsg = ResolveResString(resCANNOTREADDST)
  1124.             ElseIf lRC And VIF_BUFFTOOSMALL Then
  1125.                 strMsg = ResolveResString(resBUFFTOOSMALL)
  1126.             End If
  1127.  
  1128.             strMsg = strMsg & ResolveResString(resNOINSTALL)
  1129.             MsgError strMsg, MB_OK Or MB_ICONEXCLAMATION, gstrTitle
  1130.             intRESULT = intNOCOPY
  1131.         End If
  1132.     Loop
  1133.  
  1134.     '
  1135.     'If there was a temp file left over from VerInstallFile, remove it
  1136.     '
  1137.     If lRC And VIF_TEMPFILE Then
  1138.         Kill mstrVerTmpName
  1139.     End If
  1140.  
  1141.     'Abort or commit the current Action, and do reference counting
  1142.     #If Win32 And LOGGING Then
  1143.         Select Case intRESULT
  1144.         Case intNOCOPY
  1145.             AbortAction
  1146.         Case intCOPIED
  1147.             DecideIncrementRefCount strDestDir & strDestName, fShared, fFileAlreadyExisted
  1148.             AddActionNote ResolveResString(resLOG_FILECOPIED)
  1149.             CommitAction
  1150.             CopyFile = True
  1151.         Case intFILEUPTODATE
  1152.             DecideIncrementRefCount strDestDir & strDestName, fShared, fFileAlreadyExisted
  1153.             AddActionNote ResolveResString(resLOG_FILEUPTODATE)
  1154.             CommitAction
  1155.             CopyFile = True
  1156.         Case Else
  1157.             AbortAction ' Defensive - this shouldn't be reached
  1158.         End Select
  1159.     #End If
  1160.     '
  1161.     'if we successfully copied the file, compare the name of the dest file with
  1162.     'one of the names (possibly) requiring special action.
  1163.     '
  1164.     If intRESULT = intCOPIED Then
  1165.         Select Case strDestName
  1166.         #If Win16 Then
  1167.         Case mstrFILE_BTRIEVE
  1168.             '
  1169.             'Used for updating WIN.INI file in 'DoBtrieve' subroutine
  1170.             '
  1171.             gsDest.strBtrieve = strDestDir & mstrFILE_BTRIEVE
  1172.         #End If
  1173.         End Select
  1174.     End If
  1175.  
  1176.     Exit Function
  1177.  
  1178. UnexpectedErr:
  1179.     Error Err
  1180.     Resume Next
  1181.     
  1182. CFMsg: '(Subroutine)
  1183.     strMsg = strDestDir & strDestName & LS$ & strMsg
  1184.     Select Case MsgError(strMsg, MB_ABORTRETRYIGNORE Or MB_ICONEXCLAMATION Or MB_DEFBUTTON2, gstrTitle)
  1185.     Case IDABORT
  1186.         ExitSetup frmCopy, gintRET_ABORT
  1187.     Case IDIGNORE
  1188.         If fIgnoreWarn = True Then
  1189.             intRESULT = intNOCOPY
  1190.         Else
  1191.             fIgnoreWarn = True
  1192.             strMsg = strMsg & LS$ & ResolveResString(resWARNIGNORE)
  1193.             If MsgError(strMsg, MB_YESNO Or MB_ICONQUESTION Or MB_DEFBUTTON2, gstrTitle) = IDYES Then
  1194.                 intRESULT = intNOCOPY
  1195.             Else
  1196.                 'Will retry
  1197.             End If
  1198.         End If
  1199.     End Select
  1200.  
  1201.     Return
  1202. End Function
  1203.  
  1204. '-----------------------------------------------------------
  1205. ' SUB: CopySection
  1206. '
  1207. ' Attempts to copy the files that need to be copied from
  1208. ' the named section of the setup info file (SETUP.LST)
  1209. '
  1210. ' IN: [strSection] - name of section to copy files from
  1211. '
  1212. '-----------------------------------------------------------
  1213. '
  1214. Sub CopySection(ByVal strSECTION As String)
  1215.     Dim intIdx As Integer
  1216.     Dim fSplit As Integer
  1217.     Dim fSrcVer As Integer
  1218.     Dim sFile As FILEINFO
  1219.     Dim strLastFile As String
  1220.     Dim intRC As Integer
  1221.     Dim lThisFileSize As Long
  1222.     Dim strSrcDir As String
  1223.     Dim strDestDir As String
  1224.     Dim strSrcName As String
  1225.     Dim strDestName As String
  1226.     Dim strRegister As String
  1227.     Dim sSrcVerInfo As VERINFO
  1228.     Dim sDestVerInfo As VERINFO
  1229.     Dim fFileWasUpToDate As Boolean
  1230.  
  1231.     On Error Resume Next
  1232.  
  1233.     intIdx = 1
  1234.  
  1235.     '
  1236.     'For each file in the specified section, read info from the setup info file
  1237.     '
  1238.     Do While ReadSetupFileLine(strSECTION, intIdx, sFile) = True
  1239.         fFileWasUpToDate = False
  1240.         
  1241.         '
  1242.         'If last result was IGNORE, and if this is an extent of a split file,
  1243.         'then no need to process this chunk of the file either
  1244.         '
  1245.         If intRC = IDIGNORE And sFile.strDestName = strDestName Then
  1246.             GoTo CSContinue
  1247.         End If
  1248.  
  1249.         intRC = 0
  1250.  
  1251.         '
  1252.         'If a new disk is called for, or if for some reason we can't find the
  1253.         'source path (user removed the install floppy, for instance) then
  1254.         'prompt for the next disk.  The PromptForNextDisk function won't
  1255.         'actually prompt the user unless it determines that the source drive
  1256.         'contains removeable media or is a network connection
  1257.         '
  1258.         If sFile.intDiskNum <> gintCurrentDisk Or DirExists(gstrSrcPath) = False Then
  1259.             PromptForNextDisk sFile.intDiskNum, sFile.strSrcName
  1260.         End If
  1261.  
  1262.         strSrcName = sFile.strSrcName
  1263.         strSrcDir = gstrSrcPath
  1264.  
  1265.         '
  1266.         'if the file isn't split, or if this is the first section of a split file
  1267.         '
  1268.         If sFile.strDestDir <> gstrNULL Then
  1269.             fSplit = sFile.fSplit
  1270.  
  1271.             strDestDir = sFile.strDestDir
  1272.             strDestName = sFile.strDestName
  1273.             
  1274.             'We need to go ahead and create the destination directory, or else
  1275.             'GetLongPathName() may fail
  1276.             If Not MakePath(strDestDir) Then
  1277.                 intRC = IDIGNORE
  1278.             End If
  1279.             
  1280.             If intRC <> IDIGNORE Then
  1281.                 #If Win32 Then
  1282.                     Err = 0
  1283.                     strDestDir = GetLongPathName(strDestDir)
  1284.                 #End If
  1285.  
  1286.                 frmCopy.lblDestFile.Caption = strDestDir & sFile.strDestName
  1287.                 frmCopy.lblDestFile.Refresh
  1288.  
  1289.                 #If Win32 And LOGGING Then
  1290.                     If sFile.fShared Then
  1291.                         NewAction gstrKEY_SHAREDFILE, """" & strDestDir & strDestName & """"
  1292.                     ElseIf sFile.fSystem Then
  1293.                         NewAction gstrKEY_SYSTEMFILE, """" & strDestDir & strDestName & """"
  1294.                     Else
  1295.                         NewAction gstrKEY_PRIVATEFILE, """" & strDestDir & strDestName & """"
  1296.                         CheckOverwritePrivateFile strDestDir & strDestName
  1297.                     End If
  1298.                 #End If
  1299.             End If
  1300.             
  1301.             '
  1302.             'If the file info just read from SETUP.LST is the application .EXE
  1303.             '(i.e.; it's the value of the AppExe Key in the [Setup] section,
  1304.             'then save it's full pathname for later use
  1305.             '
  1306.             If strDestName = gstrAppExe Then
  1307.                 '
  1308.                 'Used for creating a program manager icon in Form_Load of SETUP1.FRM
  1309.                 'and for registering the per-app path
  1310.                 '
  1311.                 gsDest.strAppDir = strDestDir
  1312.             End If
  1313.  
  1314.             'Special case for CTL3D32.DLL (32-bits only)
  1315.             '-- we never install these files unders Windows 95, only under Windows NT
  1316.             #If Win32 Then
  1317.             If strDestName = mstrFILE_CTL3D32 Then
  1318.                 If Not IsWindowsNT() Then
  1319.                     'We're not running under NT - do not install this file.
  1320.                     intRC = IDIGNORE
  1321.                     #If Win32 And LOGGING Then
  1322.                         LogNote ResolveResString(resCOMMON_CTL3D32NOTCOPIED, "|1", strDestName)
  1323.                         AbortAction
  1324.                     #End If
  1325.                 End If
  1326.             End If
  1327.             #End If
  1328.             
  1329.             'Special case for RPCREG.DAT (16-bits only).  The file on the disk
  1330.             'is only a dummy.  We will build this file instead from information
  1331.             'found on the end-user machine.
  1332.             #If Win16 Then
  1333.                 If strDestName = mstrFILE_RPCREG Then
  1334.                     InstallRpcRegFile
  1335.                     intRC = IDIGNORE
  1336.                 End If
  1337.             #End If
  1338.             
  1339.             strRegister = sFile.strRegister
  1340.  
  1341.             lThisFileSize = CalcFinalSize(sFile.lFileSize, sFile.strDestDir)
  1342.  
  1343.             '
  1344.             'The stuff below trys to save some time by pre-checking whether a file
  1345.             'should be installed before a split file is concatenated or before
  1346.             'VerInstallFile does its think which involves a full file read (for
  1347.             'a compress file) at the minimum.  Basically, if both files have
  1348.             'version numbers, they are compared.  If one file has a version number
  1349.             'and the other doesn't, the one with the version number is deemed
  1350.             '"Newer".  If neither file has a version number, we compare date.
  1351.             '
  1352.             'Always attempt to get the source file version number.  If the setup
  1353.             'info file did not contain a version number (sSrcVerInfo.nMSHi =
  1354.             'gintNOVERINFO), we attempt to read the version number from the source
  1355.             'file.  Reading the version number from a split file will always fail.
  1356.             'That's why it's a good idea to include the version number for a file
  1357.             '(especially split ones) in the setup info file (SETUP.LST)
  1358.             '
  1359.             fSrcVer = True
  1360.             sSrcVerInfo = sFile.sVerInfo
  1361.             If sSrcVerInfo.nMSHi = gintNOVERINFO Then
  1362.                 fSrcVer = GetFileVerStruct(strSrcDir & strSrcName, sSrcVerInfo)
  1363.             End If
  1364.  
  1365.             '
  1366.             'If there is an existing destination file with version information, then
  1367.             'compare its version number to the source file version number.
  1368.             '
  1369.             If intRC <> IDIGNORE Then
  1370.                 If GetFileVerStruct(strDestDir & strDestName, sDestVerInfo, sFile.strRegister = mstrREMOTEREGISTER) = True Then
  1371.                     If fSrcVer = True Then
  1372.                         If IsNewerVer(sSrcVerInfo, sDestVerInfo) = False Then
  1373.                             '
  1374.                             'Existing file is newer than the one we want to install;
  1375.                             'the existing file will be used instead
  1376.                             '
  1377.                             intRC = IDIGNORE
  1378.                             fFileWasUpToDate = True
  1379.                             #If Win32 And LOGGING Then
  1380.                                 DecideIncrementRefCount strDestDir & strDestName, sFile.fShared, True
  1381.                                 AddActionNote ResolveResString(resLOG_FILEUPTODATE)
  1382.                                 CommitAction
  1383.                             #End If
  1384.                         End If
  1385.                     End If
  1386.                 Else
  1387.                     '
  1388.                     'If the destination file has no version info, then we'll copy the
  1389.                     'source file if it *does* have a version.  If neither file has a
  1390.                     'version number, then we compare date.
  1391.                     '
  1392.                     If fSrcVer = False Then
  1393.                         If sFile.varDate <= CVDate(FileDateTime(strDestDir & strDestName)) Then
  1394.                             If Err = 0 Then
  1395.                                 '
  1396.                                 'Although neither the source nor the existing file contain version
  1397.                                 'information, the existing file has a newer date so we'll use it.
  1398.                                 '
  1399.                                 intRC = IDIGNORE
  1400.                                 fFileWasUpToDate = True
  1401.                                 #If Win32 And LOGGING Then
  1402.                                     DecideIncrementRefCount strDestDir & strDestName, sFile.fShared, True
  1403.                                     AddActionNote ResolveResString(resLOG_FILEUPTODATE)
  1404.                                     CommitAction
  1405.                                 #End If
  1406.                             Else
  1407.                                 Err = 0
  1408.                             End If
  1409.                         End If
  1410.                     End If
  1411.                 End If
  1412.             End If
  1413.             
  1414.             '
  1415.             'If we've decided to try the copy, and if this is the first extent of a split file
  1416.             'then open the temporary file used for concatentation
  1417.             '
  1418.             If intRC <> IDIGNORE And fSplit = True Then
  1419.                 mintConcatFile = OpenConcatFile()
  1420.                 If mintConcatFile = -1 Then
  1421.                     'The open failed, and the user chose to ignore the error
  1422.                     mintConcatFile = 0
  1423.                     intRC = IDIGNORE
  1424.                     #If Win32 And LOGGING Then
  1425.                         AbortAction
  1426.                     #End If
  1427.                 End If
  1428.             End If
  1429.         End If
  1430.  
  1431.         '
  1432.         'If this is an extent of a split file, and we're going to try the copy, then
  1433.         'append this source file extent to the end of the concatentation file
  1434.         '
  1435.         If fSplit = True Then
  1436.             If intRC <> IDIGNORE Then
  1437.                 intRC = ConcatSplitFile(strSrcDir & strSrcName)
  1438.                 If intRC = IDIGNORE Then
  1439.                     #If Win32 And LOGGING Then
  1440.                     AbortAction
  1441.                     #End If
  1442.                 End If
  1443.             End If
  1444.  
  1445.             If intRC = IDIGNORE And mintConcatFile > 0 Then
  1446.                 Close mintConcatFile
  1447.                 mintConcatFile = 0
  1448.             End If
  1449.  
  1450.             fSplit = sFile.fSplit
  1451.         End If
  1452.  
  1453.         '
  1454.         'If the file wasn't split, or if this is the last extent of a split file
  1455.         '
  1456.         If fSplit = False Then
  1457.             If mintConcatFile > 0 Then
  1458.                 '
  1459.                 'If this was the last extent of a split file, close the concatenated
  1460.                 'file.  At this point, the concatentated file is a true representation
  1461.                 'of the desired source file, so we point to it instead of the split file
  1462.                 'extent on the installation media
  1463.                 '
  1464.                 Close mintConcatFile
  1465.                 strSrcDir = mstrConcatDrive
  1466.                 strSrcName = mstrCONCATFILE
  1467.             End If
  1468.  
  1469.             '
  1470.             'After all of this, if we're still ready to copy, then give it a whirl!
  1471.             '
  1472.             If intRC <> IDIGNORE Then
  1473.                 ' CopyFile will increment the reference count for us, and will either
  1474.                 ' commit or abort the current Action.
  1475.                 intRC = IIf(CopyFile(strSrcDir, strDestDir, strSrcName, strDestName, sFile.fShared), 0, IDIGNORE)
  1476.             End If
  1477.  
  1478.             '
  1479.             'Save the paths of certain files for later use, if they were
  1480.             'successfully installed or were already on the system
  1481.             '
  1482.             If (intRC = 0 Or fFileWasUpToDate) Then
  1483.                 #If Win32 Then
  1484.                 Select Case strDestName
  1485.                 Case mstrFILE_AUTMGR32
  1486.                     '
  1487.                     'Used for creating an icon if installed
  1488.                     '
  1489.                     gsDest.strAUTMGR32 = strDestDir & mstrFILE_AUTMGR32
  1490.                 Case mstrFILE_RACMGR32
  1491.                     '
  1492.                     'Used for creating an icon if installed
  1493.                     '
  1494.                     gsDest.strRACMGR32 = strDestDir & mstrFILE_RACMGR32
  1495.                 End Select
  1496.                 #End If
  1497.             
  1498.                 '
  1499.                 'If we successfully copied the file, and if registration information was
  1500.                 'specified in the setup info file, save the registration info into an
  1501.                 'array so that we can register all files requiring it in one fell swoop
  1502.                 'after all the files have been copied.
  1503.                 '
  1504.                 If strRegister <> gstrNULL Then
  1505.                     Err = 0
  1506.                     ReDim Preserve msRegInfo(UBound(msRegInfo) + 1)
  1507.     
  1508.                     If Err > 0 Then
  1509.                         ReDim msRegInfo(0)
  1510.                     End If
  1511.     
  1512.                     msRegInfo(UBound(msRegInfo)).strFileName = strDestDir & strDestName
  1513.     
  1514.                     Select Case strRegister
  1515.                     Case mstrDLLSELFREGISTER, mstrEXESELFREGISTER
  1516.                         'Nothing in particular to do
  1517.                     Case mstrREMOTEREGISTER
  1518.                         'We need to look for and parse the corresponding "RemoteX=..." line
  1519.                         If Not ReadSetupRemoteLine(strSECTION, intIdx, msRegInfo(UBound(msRegInfo))) = True Then
  1520.                             MsgError ResolveResString(resREMOTELINENOTFOUND, "|1", strDestName, "|2", gstrINI_REMOTE & Format$(intIdx)), vbExclamation Or vbOKOnly, gstrTitle
  1521.                             ExitSetup frmSetup1, gintRET_FATAL
  1522.                         End If
  1523.                     Case Else
  1524.                         '
  1525.                         'If the registration info specified the name of a file with
  1526.                         'registration info (which we assume if a registration macro
  1527.                         'was not specified), then we also assume that, if no path
  1528.                         'information is available, this reginfo file is in the same
  1529.                         'directory as the file it registers
  1530.                         '
  1531.                         If InStr(strRegister, gstrSEP_DIR) = 0 Then
  1532.                             strRegister = strDestDir & strRegister
  1533.                         End If
  1534.                     End Select
  1535.     
  1536.                     msRegInfo(UBound(msRegInfo)).strRegister = strRegister
  1537.                 End If
  1538.             
  1539.             End If
  1540.  
  1541.             '
  1542.             'If we created a temporary concatenation file, nuke it
  1543.             '
  1544.             If mintConcatFile > 0 Then
  1545.                 Kill mstrConcatDrive & mstrCONCATFILE
  1546.                 mintConcatFile = 0
  1547.             End If
  1548.         End If
  1549.  
  1550.         strLastFile = sFile.strDestName
  1551.  
  1552. CSContinue:
  1553.         '
  1554.         'If the file wasn't split, or if this was the last extent of a split file, then
  1555.         'update the copy status bar.  We need to do the update regardless of whether a
  1556.         'file was actually copied or not.
  1557.         '
  1558.         If sFile.fSplit = False Then
  1559.             glTotalCopied = glTotalCopied + lThisFileSize
  1560.             UpdateStatus frmCopy.picStatus, glTotalCopied / mlTotalToCopy
  1561.         End If
  1562.  
  1563.         '
  1564.         'Give a chance for the 'Cancel' button command to be processed if it was pressed
  1565.         '
  1566.         DoEvents
  1567.         intIdx = intIdx + 1
  1568.     Loop
  1569.  
  1570.     Err = 0
  1571. End Sub
  1572.  
  1573. '-----------------------------------------------------------
  1574. ' SUB: CreateOSProgramGroup
  1575. '
  1576. ' Calls CreateProgManGroup under Windows NT or
  1577. ' CreateShellGroup under Windows 95
  1578. '-----------------------------------------------------------
  1579. '
  1580. Sub CreateOSProgramGroup(frm As Form, ByVal strFolderName As String, ByVal strGroupPath As String)
  1581. #If Win32 And LOGGING Then
  1582.     If TreatAsWin95() Then
  1583.         CreateShellGroup strFolderName
  1584.     Else
  1585. #End If
  1586.         CreateProgManGroup frm, strFolderName, strGroupPath
  1587. #If Win32 And LOGGING Then
  1588.     End If
  1589. #End If
  1590. End Sub
  1591.  
  1592. '-----------------------------------------------------------
  1593. ' SUB: CreateOSLink
  1594. '
  1595. ' Calls CreateProgManItem under Windows NT or
  1596. ' CreateFolderLink under Windows 95.
  1597. '
  1598. ' If fLog is missing, the default is True.
  1599. '-----------------------------------------------------------
  1600. '
  1601. Sub CreateOSLink(frm As Form, ByVal strLinkPath As String, ByVal strLinkArguments As String, ByVal strLinkName As String, Optional ByVal fLog)
  1602.     If IsMissing(fLog) Then
  1603.         fLog = True
  1604.     End If
  1605.     
  1606. #If Win32 And LOGGING Then
  1607.     If TreatAsWin95() Then
  1608.         CreateShellLink strLinkPath, strLinkArguments, strLinkName, fLog
  1609.     Else
  1610. #End If
  1611.         #If Win32 Then
  1612.             strLinkPath = GetShortPathName(strLinkPath)
  1613.         #End If
  1614.         CreateProgManItem frm, strLinkPath & " " & strLinkArguments, strLinkName, fLog
  1615. #If Win32 And LOGGING Then
  1616.     End If
  1617. #End If
  1618. End Sub
  1619.  
  1620. '-----------------------------------------------------------
  1621. ' SUB: CreateProgManGroup
  1622. '
  1623. ' Creates a new group in the Windows program manager if
  1624. ' the specified groupname doesn't already exist
  1625. '
  1626. ' IN: [frm] - form containing a label named 'lblDDE'
  1627. '     [strGroupName] - text name of the group
  1628. '     [strGroupPath] - file system name of the group file,
  1629. '                      ex: 'c:\windows\myapp.grp'.  Under
  1630. '                      Win32, this parameter is passed, but
  1631. '                      it is ignored.
  1632. '     [fLog] - Whether or not to write to the logfile (default
  1633. '                is true if missing)
  1634. '-----------------------------------------------------------
  1635. '
  1636. Sub CreateProgManGroup(frm As Form, ByVal strGroupName As String, ByVal strGroupPath As String, Optional ByVal fLog)
  1637.     '
  1638.     'Call generic progman DDE function with flag to add a group
  1639.     '
  1640.     If IsMissing(fLog) Then
  1641.         fLog = True
  1642.     End If
  1643.     
  1644.     'Save the group name for use in logging on the next call to CallProgManItem
  1645.     mstrLastCreatedShellGroup = strGroupName
  1646.     
  1647.     'Perform the DDE to create the group
  1648.     PerformDDE frm, strGroupName, strGroupPath, gstrNULL, mintDDE_GRPADD, fLog
  1649. End Sub
  1650.  
  1651. '-----------------------------------------------------------
  1652. ' SUB: CreateProgManItem
  1653. '
  1654. ' Creates (or replaces) a program manager icon in the active
  1655. ' program manager group
  1656. '
  1657. ' IN: [frm] - form containing a label named 'lblDDE'
  1658. '     [strCmdLine] - command line for the item/icon,
  1659. '                    Ex: 'c:\myapp\myapp.exe'
  1660. '                    Note:  If this path contains spaces
  1661. '                      or commas, it should be enclosed
  1662. '                      with quotes so that it is properly
  1663. '                      interpreted by Windows (see AddQuotesToFN)
  1664. '     [strIconTitle] - text caption for the icon
  1665. '     [fLog] - Whether or not to write to the logfile (default
  1666. '                is true if missing)
  1667. '
  1668. ' PRECONDITION: CreateProgManGroup has already been called.  The
  1669. '               new icon will be created in the group last created.
  1670. '-----------------------------------------------------------
  1671. '
  1672. Sub CreateProgManItem(frm As Form, ByVal strCmdLine As String, ByVal strIconTitle As String, Optional ByVal fLog)
  1673.     '
  1674.     'Call generic progman DDE function with flag to add an item
  1675.     '
  1676.     If IsMissing(fLog) Then
  1677.         fLog = True
  1678.     End If
  1679.     PerformDDE frm, mstrLastCreatedShellGroup, strCmdLine, strIconTitle, mintDDE_ITEMADD, fLog
  1680. End Sub
  1681.  
  1682. '-----------------------------------------------------------
  1683. ' SUB: CreateShellGroup
  1684. '
  1685. ' Creates a new program group off of Start>Programs in the
  1686. ' Windows 95 shell if the specified folder doesn't already exist.
  1687. '
  1688. ' IN: [strFolderName] - text name of the folder.
  1689. '                      This parameter may not contain
  1690. '                      backslashes.
  1691. '                      ex: "My Application" - this creates
  1692. '                        the folder Start>Programs>My Application
  1693. '     [fLog] - Whether or not to write to the logfile (default
  1694. '                is true if missing)
  1695. '-----------------------------------------------------------
  1696. '
  1697. #If Win32 And LOGGING Then
  1698. Sub CreateShellGroup(ByVal strFolderName As String, Optional ByVal fLog)
  1699.     If IsMissing(fLog) Then
  1700.         fLog = True
  1701.     End If
  1702.  
  1703.     ReplaceDoubleQuotes strFolderName
  1704.     
  1705.     'Save this folder name for use with the next call
  1706.     'to CreateShellLink()
  1707.     mstrLastCreatedShellGroup = strFolderName
  1708.     
  1709.     If strFolderName = "" Then
  1710.         Exit Sub
  1711.     End If
  1712.  
  1713.     If fLog Then
  1714.         NewAction gstrKEY_SHELLFOLDER, """" & strFolderName & """"
  1715.     End If
  1716.  
  1717. Retry:
  1718.     
  1719.     Dim fSuccess As Boolean
  1720.     fSuccess = OSfCreateShellGroup(strFolderName)
  1721.     If fSuccess Then
  1722.         If fLog Then
  1723.             CommitAction
  1724.         End If
  1725.     Else
  1726.         Select Case (MsgError(ResolveResString(resCANTCREATEPROGRAMGROUP, "|1", strFolderName), vbRetryCancel Or vbExclamation, gstrTitle))
  1727.         Case vbCancel
  1728.             ExitSetup frmSetup1, gintRET_EXIT
  1729.             GoTo Retry
  1730.         End Select
  1731.         
  1732.         GoTo Retry
  1733.     End If
  1734. End Sub
  1735. #End If
  1736.  
  1737. '-----------------------------------------------------------
  1738. ' SUB: CreateShellLink
  1739. '
  1740. ' Creates (or replaces) a link in either Start>Programs or
  1741. ' any of its immediate subfolders in the Windows 95 shell.
  1742. '
  1743. ' IN: [strLinkPath] - full path to the target of the link
  1744. '                     Ex: 'c:\Program Files\My Application\MyApp.exe"
  1745. '     [strLinkArguments] - command-line arguments for the link
  1746. '                     Ex: '-f -c "c:\Program Files\My Application\MyApp.dat" -q'
  1747. '     [strLinkName] - text caption for the link
  1748. '     [fLog] - Whether or not to write to the logfile (default
  1749. '                is true if missing)
  1750. '
  1751. ' OUT:
  1752. '   The link will be created in the most recent folder created
  1753. '   by a call to CreateShellGroup.  If this function has
  1754. '   never been called, then the link will be created directly
  1755. '   in the Start>Programs menu, and not in any subfolder.
  1756.  
  1757. '-----------------------------------------------------------
  1758. '
  1759. #If Win32 And LOGGING Then
  1760. Sub CreateShellLink(ByVal strLinkPath As String, ByVal strLinkArguments As String, ByVal strLinkName As String, Optional ByVal fLog)
  1761.     If IsMissing(fLog) Then
  1762.         fLog = True
  1763.     End If
  1764.     
  1765.     If fLog Then
  1766.         NewAction gstrKEY_SHELLLINK, """" & mstrLastCreatedShellGroup & """" & ", " & """" & strLinkName & """"
  1767.     End If
  1768.     
  1769.     ReplaceDoubleQuotes strLinkName
  1770.  
  1771. Retry:
  1772.  
  1773.     Dim fSuccess As Boolean
  1774.     fSuccess = OSfCreateShellLink(mstrLastCreatedShellGroup & "", strLinkName, strLinkPath, strLinkArguments & "") 'the path should never be enclosed in double quotes
  1775.     If fSuccess Then
  1776.         If fLog Then
  1777.             CommitAction
  1778.         End If
  1779.     Else
  1780.         Select Case (MsgError(ResolveResString(resCANTCREATEPROGRAMICON, "|1", strLinkName), vbAbortRetryIgnore Or vbExclamation, gstrTitle))
  1781.         Case vbAbort
  1782.             ExitSetup frmSetup1, gintRET_ABORT
  1783.             GoTo Retry
  1784.         Case vbRetry
  1785.             GoTo Retry
  1786.         Case vbIgnore
  1787.             If fLog Then
  1788.                 AbortAction
  1789.             End If
  1790.         End Select
  1791.     End If
  1792. End Sub
  1793. #End If
  1794.  
  1795. '-----------------------------------------------------------
  1796. ' FUNCTION: DecideIncrementRefCount
  1797. '
  1798. ' Increments the reference count of a file under 32-bits
  1799. ' if the file is a shared file.
  1800. '
  1801. ' IN: [strFullPath] - full pathname of the file to reference
  1802. '                     count.  Example:
  1803. '                     'C:\MYAPP\MYAPP.DAT'
  1804. '     [fShared] - whether the file is shared or private
  1805. '     [fFileAlreadyExisted] - whether or not the file already
  1806. '                             existed on the hard drive
  1807. '                             before our setup program
  1808. '-----------------------------------------------------------
  1809. '
  1810. #If Win32 And LOGGING Then
  1811. Sub DecideIncrementRefCount(ByVal strFullPath As String, ByVal fShared As Boolean, ByVal fFileAlreadyExisted As Boolean)
  1812.     'Reference counting takes place under both Windows 95 and Windows NT
  1813.     If fShared Then
  1814.         IncrementRefCount strFullPath, fFileAlreadyExisted
  1815.     End If
  1816. End Sub
  1817. #End If
  1818.             
  1819. '-----------------------------------------------------------
  1820. ' FUNCTION: DetectFile
  1821. '
  1822. ' Detects whether the specified file exists.  If it can't
  1823. ' be found, the user is given the opportunity to abort,
  1824. ' retry, or ignore finding the file.  This call is used,
  1825. ' for example, to ensure that a floppy with the specified
  1826. ' file name is in the drive before continuing.
  1827. '
  1828. ' IN: [strFileName] - name of file to detect, usually
  1829. '                     should include full path, Example:
  1830. '                     'A:\MYAPP.DAT'
  1831. '
  1832. ' Returns: TRUE if the file was detected, IDIGNORE if
  1833. '          the user chose ignore when the file couldn't
  1834. '          be found, or calls ExitSetup upon 'Abort'
  1835. '-----------------------------------------------------------
  1836. '
  1837. Function DetectFile(ByVal strFileName As String) As Integer
  1838.     Dim strMsg As String
  1839.  
  1840.     DetectFile = True
  1841.                       
  1842.     Do While FileExists(strFileName) = False
  1843.         strMsg = ResolveResString(resCANTOPEN) & LS$ & strFileName
  1844.         Select Case MsgError(strMsg, MB_ABORTRETRYIGNORE Or MB_ICONEXCLAMATION Or MB_DEFBUTTON2, gstrSETMSG)
  1845.         Case IDABORT
  1846.             ExitSetup frmCopy, gintRET_ABORT
  1847.         Case IDIGNORE
  1848.             DetectFile = IDIGNORE
  1849.             Exit Do
  1850.         End Select
  1851.     Loop
  1852. End Function
  1853.  
  1854. '-----------------------------------------------------------
  1855. ' FUNCTION: DirExists
  1856. '
  1857. ' Determines whether the specified directory name exists.
  1858. ' This function is used (for example) to determine whether
  1859. ' an installation floppy is in the drive by passing in
  1860. ' something like 'A:\'.
  1861. '
  1862. ' IN: [strDirName] - name of directory to check for
  1863. '
  1864. ' Returns: True if the directory exists, False otherwise
  1865. '-----------------------------------------------------------
  1866. '
  1867. Function DirExists(ByVal strDirName As String) As Integer
  1868.     Const strWILDCARD$ = "*.*"
  1869.     Const ATTR_DIRECTORY% = 16
  1870.  
  1871.     Dim strDummy As String
  1872.  
  1873.     On Error Resume Next
  1874.  
  1875.     AddDirSep strDirName
  1876.     strDummy = Dir$(strDirName & strWILDCARD, ATTR_DIRECTORY)
  1877. #If Win16 Then
  1878.     DirExists = IIf(Err, False, True)
  1879. #Else
  1880.     DirExists = IIf(strDummy = gstrNULL, False, True)
  1881. #End If
  1882.  
  1883.     Err = 0
  1884. End Function
  1885.  
  1886. '-----------------------------------------------------------
  1887. ' SUB: DoBtrieve
  1888. '
  1889. ' Handles special processing when Btrieve driver is flagged
  1890. ' for installation (Btrieve=1 in [Setup] section of
  1891. ' SETUP.LST)
  1892. '-----------------------------------------------------------
  1893. '
  1894. #If Win16 Then
  1895. Sub DoBtrieve()
  1896.     Const strINI_OPTIONS$ = "OPTIONS"
  1897.     Const strFILE_WININI$ = "WIN.INI"
  1898.     Const strBTROPTS$ = "/m:64 /p:4096 /b:16 /f:20 /l:40 /n:12 /t:"
  1899.  
  1900.     Dim strTmp As String
  1901.     Dim intRC As Integer
  1902.  
  1903.     If gsDest.strBtrieve = gstrNULL Then
  1904.         gsDest.strBtrieve = gstrDestDir & mstrFILE_BTRIEVE
  1905.     End If
  1906.  
  1907.     strTmp = Space$(gintMAX_SIZE)
  1908.     If GetPrivateProfileString(gstrINI_BTRIEVE, strINI_OPTIONS, "1", strTmp, gintMAX_SIZE, strFILE_WININI) <= 1 Then
  1909.         intRC = WritePrivateProfileString(gstrINI_BTRIEVE, strINI_OPTIONS, strBTROPTS & gsDest.strBtrieve, strFILE_WININI)
  1910.     End If
  1911. End Sub
  1912. #End If
  1913.  
  1914. '-----------------------------------------------------------
  1915. ' SUB: EtchedLine
  1916. '
  1917. ' Draws an 'etched' line upon the specified form starting
  1918. ' at the X,Y location passed in and of the specified length.
  1919. ' Coordinates are in the current ScaleMode of the passed
  1920. ' in form.
  1921. '
  1922. ' IN: [frmEtch] - form to draw the line upon
  1923. '     [intX1] - starting horizontal of line
  1924. '     [intY1] - starting vertical of line
  1925. '     [intLength] - length of the line
  1926. '-----------------------------------------------------------
  1927. '
  1928. Sub EtchedLine(frmEtch As Form, ByVal intX1 As Integer, ByVal intY1 As Integer, ByVal intLength As Integer)
  1929.     Const lWHITE& = vb3DHighlight
  1930.     Const lGRAY& = vb3DShadow
  1931.  
  1932.     frmEtch.Line (intX1, intY1)-(intX1 + intLength, intY1), lGRAY
  1933.     frmEtch.Line (frmEtch.CurrentX + 5, intY1 + 20)-(intX1 - 5, intY1 + 20), lWHITE
  1934. End Sub
  1935.  
  1936. '-----------------------------------------------------------
  1937. ' SUB: ExeSelfRegister
  1938. '
  1939. ' Synchronously runs the file passed in (which should be
  1940. ' an executable file that supports the /REGSERVER switch,
  1941. ' for instance, a VB4 generated OLE server .EXE).
  1942. '
  1943. ' IN: [strFileName] - .EXE file to register
  1944. '-----------------------------------------------------------
  1945. '
  1946. Sub ExeSelfRegister(ByVal strFileName As String)
  1947.     Const strREGSWITCH$ = " /REGSERVER"
  1948.  
  1949.     Dim fShell As Integer
  1950.  
  1951.     '
  1952.     'Synchronously shell out and run the .EXE with the self registration switch
  1953.     '
  1954.     fShell = FSyncShell(AddQuotesToFN(strFileName) & strREGSWITCH, 7)
  1955.     frmSetup1.Refresh
  1956. End Sub
  1957.  
  1958. '-----------------------------------------------------------
  1959. ' SUB: ExitSetup
  1960. '
  1961. ' Handles shutdown of the setup app.  Depending upon the
  1962. ' value of the intExitCode parm, may prompt the user and
  1963. ' exit the sub if the user chooses to cancel the exit
  1964. ' process.
  1965. '
  1966. ' IN: [frm] - active form to unload upon exit
  1967. '     [intExitCode] - code specifying exit action
  1968. '-----------------------------------------------------------
  1969. '
  1970. Sub ExitSetup(frm As Form, intExitCode As Integer)
  1971.     Dim strMsg As String
  1972.  
  1973.     On Error Resume Next
  1974.  
  1975.     Select Case intExitCode
  1976.     Case gintRET_EXIT
  1977.         '
  1978.         'If user chose an Exit or Cancel button
  1979.         '
  1980.         If MsgWarning(ResolveResString(resASKEXIT), MB_ICONQUESTION Or MB_YESNO Or MB_DEFBUTTON2, gstrTitle) = IDNO Then
  1981.             Exit Sub
  1982.         End If
  1983.     Case gintRET_ABORT
  1984.         '
  1985.         'If user chose to abort before a pending action
  1986.         '
  1987.         strMsg = ResolveResString(resINCOMPLETE) & LS$ & ResolveResString(resQUITNOW) & LS$
  1988.         strMsg = strMsg & ResolveResString(resQUITSETUP)
  1989.         If MsgWarning(strMsg, MB_ICONQUESTION Or MB_YESNO Or MB_DEFBUTTON2, gstrSETMSG) = IDNO Then
  1990.             Exit Sub
  1991.         End If
  1992.     End Select
  1993.  
  1994.     #If Win32 And LOGGING Then
  1995.     'Abort any pending actions
  1996.     While fWithinAction()
  1997.         AbortAction
  1998.     Wend
  1999.     #End If
  2000.     
  2001.     Close
  2002.  
  2003.     '
  2004.     'Clean up any temporary files from VerInstallFile or split file concatenation
  2005.     '
  2006.     Kill mstrVerTmpName
  2007.     If mintConcatFile > 0 Then
  2008.         Close mintConcatFile
  2009.         Kill mstrConcatDrive & mstrCONCATFILE
  2010.     End If
  2011.  
  2012.     If frm.hWnd <> frmSetup1.hWnd Then
  2013.         Unload frm
  2014.     End If
  2015.     
  2016.     frmSetup1.SetFocus
  2017.  
  2018.     '
  2019.     'Give appropriate ending message depending upon exit code
  2020.     '
  2021.     Select Case intExitCode
  2022.     Case gintRET_EXIT, gintRET_ABORT
  2023.         strMsg = ResolveResString(resINTERRUPTED, "|1", gstrAppName) & LS$ & ResolveResString(resCANRUN, "|1", gstrAppName)
  2024.         MsgWarning strMsg, MB_OK Or MB_ICONSTOP, gstrTitle
  2025.     Case gintRET_FATAL
  2026.         MsgError ResolveResString(resERROR, "|1", gstrAppName), MB_OK Or MB_ICONSTOP, gstrTitle
  2027.     Case gintRET_FINISHEDSUCCESS
  2028.         MsgFunc ResolveResString(resSUCCESS, "|1", gstrAppName), MB_OK, gstrTitle
  2029.     Case Else
  2030.         strMsg = ResolveResString(resINTERRUPTED, "|1", gstrAppName) & LS$ & ResolveResString(resCANRUN, "|1", gstrAppName)
  2031.         MsgWarning strMsg, MB_OK Or MB_ICONSTOP, gstrTitle
  2032.     End Select
  2033.  
  2034.     #If Win32 And LOGGING Then
  2035.         'Stop logging
  2036.         DisableLogging
  2037.     #End If
  2038.     
  2039.     #If Win32 And LOGGING Then
  2040.         '32-bit only: Clean up an aborted installation
  2041.         If (intExitCode = gintRET_FINISHEDSUCCESS) Then
  2042.             'Setup finished successfully - Temporary files should
  2043.             'have already been cleaned up.  Nothing else to do.
  2044.         Else
  2045.             'Setup has been aborted for one reason or another
  2046.             If (gstrAppRemovalEXE <> "") Then
  2047.                 Dim nErrorLevel As Integer
  2048.                 Select Case intExitCode
  2049.                 Case gintRET_FATAL
  2050.                     nErrorLevel = APPREMERR_FATAL
  2051.                 Case gintRET_EXIT
  2052.                     nErrorLevel = APPREMERR_USERCANCEL
  2053.                 Case gintRET_ABORT
  2054.                     nErrorLevel = APPREMERR_NONFATAL
  2055.                 Case Else
  2056.                     nErrorLevel = APPREMERR_FATAL
  2057.                 End Select
  2058.             
  2059.                 MsgFunc ResolveResString(resLOG_ABOUTTOREMOVEAPP), vbInformation Or vbOKOnly, gstrTitle
  2060.                 
  2061.                 Err = 0
  2062.                 Shell GetAppRemovalCmdLine(gstrAppRemovalEXE, gstrAppRemovalLog, nErrorLevel, True), vbNormalFocus
  2063.                 If Err Then
  2064.                     MsgError Error$ & LS$ & ResolveResString(resLOG_CANTRUNAPPREMOVER), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2065.                 End If
  2066.  
  2067.                 'Since the app removal program will attempt to delete this program and all of our runtime
  2068.                 'files, we should exit as soon as possible (otherwise the app remover will not be
  2069.                 'able to remove these files)
  2070.             End If
  2071.             
  2072.             'Note: We do not delete the logfile if an error occurs.
  2073.             'The application removal EXE will do that if needed.
  2074.             
  2075.         End If
  2076.     #End If
  2077.     
  2078.     Unload frmSetup1
  2079.  
  2080.     'End the program
  2081.     End
  2082. End Sub
  2083.  
  2084. '-----------------------------------------------------------
  2085. ' FUNCTION: ProcessCommandLine
  2086. '
  2087. ' Processes the command-line arguments
  2088. '
  2089. ' OUT: Fills in the passed-in byref parameters as appropriate
  2090. '-----------------------------------------------------------
  2091. '
  2092. #If Win32 And LOGGING Then
  2093. Sub ProcessCommandLine(ByVal strCommand As String, ByRef strSrcPath As String, ByRef strAppRemovalLog As String, ByRef strAppRemovalEXE As String)
  2094. #Else
  2095. Sub ProcessCommandLine(ByVal strCommand As String, ByRef strSrcPath As String)
  2096. #End If
  2097.     Dim fErr As Boolean
  2098.     
  2099.     strSrcPath = ""
  2100.     #If Win32 Then
  2101.         strAppRemovalLog = ""
  2102.     #End If
  2103.     
  2104.     strCommand = Trim$(strCommand)
  2105.     
  2106.     ' We expect to find the source directory,
  2107.     ' name/path of the logfile, and name/path
  2108.     ' of the app removal executable, separated only by
  2109.     ' spaces
  2110.     strSrcPath = strExtractFilenameArg(strCommand, fErr)
  2111.     If fErr Then GoTo BadCommandLine
  2112.     
  2113.     #If Win32 Then
  2114.         strAppRemovalLog = strExtractFilenameArg(strCommand, fErr)
  2115.         If fErr Then GoTo BadCommandLine
  2116.         
  2117.         strAppRemovalEXE = strExtractFilenameArg(strCommand, fErr)
  2118.         If fErr Then GoTo BadCommandLine
  2119.         
  2120.         
  2121.         ' Both the app removal logfile and executable must exist
  2122.         If Not FileExists(strAppRemovalLog) Then
  2123.             GoTo BadAppRemovalLog
  2124.         End If
  2125.         
  2126.         If Not FileExists(strAppRemovalEXE) Then
  2127.             GoTo BadAppRemovalEXE
  2128.         End If
  2129.     #End If
  2130.     
  2131.     ' Last check:  There should be nothing else on the command line
  2132.     strCommand = Trim$(strCommand)
  2133.     If strCommand <> "" Then
  2134.         GoTo BadCommandLine
  2135.     End If
  2136.     
  2137.     Exit Sub
  2138.     
  2139. #If Win32 Then
  2140. BadAppRemovalLog:
  2141.     MsgError ResolveResString(resCANTFINDAPPREMOVALLOG, "|1", strAppRemovalLog), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2142.     ExitSetup frmSetup1, gintRET_FATAL
  2143.     
  2144. BadAppRemovalEXE:
  2145.     MsgError ResolveResString(resCANTFINDAPPREMOVALEXE, "|1", strAppRemovalEXE), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2146.     ExitSetup frmSetup1, gintRET_FATAL
  2147. #End If
  2148.     
  2149. BadCommandLine:
  2150.     MsgError ResolveResString(resBADCOMMANDLINE), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2151.     ExitSetup frmSetup1, gintRET_FATAL
  2152. End Sub
  2153.  
  2154. '-----------------------------------------------------------
  2155. ' FUNCTION: GetDrivesAllocUnit
  2156. '
  2157. ' Gets the minimum file size allocation unit for the
  2158. ' specified drive
  2159. '
  2160. ' IN: [strDrive] - Drive to get allocation unit for
  2161. '
  2162. ' Returns: minimum allocation unit of drive, or -1 if
  2163. '          this value couldn't be determined
  2164. '-----------------------------------------------------------
  2165. '
  2166. Function GetDrivesAllocUnit(ByVal strDrive As String) As Long
  2167.     Dim strCurDrive As String
  2168.     Dim lAllocUnit As Long
  2169.  
  2170.     On Error Resume Next
  2171.  
  2172.     '
  2173.     'Save current drive
  2174.     '
  2175.     strCurDrive = Left$(CurDir$, 2)
  2176.  
  2177.     '
  2178.     'append a colon to the end of the drivespec if none supplied
  2179.     '
  2180.     If InStr(strDrive, gstrCOLON) = 0 Or Len(strDrive) > 2 Then
  2181.         strDrive = Left$(strDrive, 1) & gstrCOLON
  2182.     End If
  2183.  
  2184.     '
  2185.     'Change to the drive to determine the allocation unit for.  The AllocUnit()
  2186.     'API returns this value for the current drive only
  2187.     '
  2188.     ChDrive strDrive
  2189.  
  2190.     '
  2191.     'If there was an error accessing the specified drive, flag error return.
  2192.     'It is also possible for the AllocUnit() API to return -1 on other failure
  2193.     '
  2194.     If Err <> 0 Or (strDrive <> Left$(CurDir$, 2)) Then
  2195.         lAllocUnit = -1
  2196.     Else
  2197.         lAllocUnit = AllocUnit()
  2198.         If Err <> 0 Then
  2199.             lAllocUnit = -1
  2200.         End If
  2201.     End If
  2202.  
  2203.     If lAllocUnit = -1 Then
  2204.         MsgError Error$ & LS$ & ResolveResString(resALLOCUNIT) & strDrive, MB_ICONEXCLAMATION, gstrTitle
  2205.     End If
  2206.  
  2207.     GetDrivesAllocUnit = lAllocUnit
  2208.  
  2209.     '
  2210.     'Restore to original drive
  2211.     '
  2212.     ChDrive strCurDrive
  2213.  
  2214.     Err = 0
  2215. End Function
  2216.  
  2217. '-----------------------------------------------------------
  2218. ' FUNCTION: GetFileName
  2219. '
  2220. ' Return the filename portion of a path
  2221. '
  2222. '-----------------------------------------------------------
  2223. '
  2224. Function GetFileName(ByVal strPath As String) As String
  2225.     Dim strFileName As String
  2226.     Dim iSep As Integer
  2227.     
  2228.     strFileName = strPath
  2229.     Do
  2230.         iSep = InStr(strFileName, gstrSEP_DIR)
  2231.         If iSep = 0 Then iSep = InStr(strFileName, gstrCOLON)
  2232.         If iSep = 0 Then
  2233.             GetFileName = strFileName
  2234.             Exit Function
  2235.         Else
  2236.             strFileName = Right(strFileName, Len(strFileName) - iSep)
  2237.         End If
  2238.     Loop
  2239. End Function
  2240.  
  2241. '-----------------------------------------------------------
  2242. ' FUNCTION: GetFileSize
  2243. '
  2244. ' Determine the size (in bytes) of the specified file
  2245. '
  2246. ' IN: [strFileName] - name of file to get size of
  2247. '
  2248. ' Returns: size of file in bytes, or -1 if an error occurs
  2249. '-----------------------------------------------------------
  2250. '
  2251. Function GetFileSize(strFileName As String) As Long
  2252.     On Error Resume Next
  2253.  
  2254.     GetFileSize = FileLen(strFileName)
  2255.  
  2256.     If Err > 0 Then
  2257.         GetFileSize = -1
  2258.         Err = 0
  2259.     End If
  2260. End Function
  2261.  
  2262. #If Win32 And LOGGING Then
  2263. '-----------------------------------------------------------
  2264. ' FUNCTION: GetAppRemovalCmdLine
  2265. '
  2266. ' Returns the correct command-line arguments (including
  2267. ' path to the executable for use in calling the
  2268. ' application removal executable)
  2269. '
  2270. ' IN: [strAppRemovalEXE] - Full path/filename of the app removal EXE
  2271. '     [strAppRemovalLog] - Full path/filename of the app removal logfile
  2272. '     [nErrorLevel] - Error level:
  2273. '                        APPREMERR_NONE - no error
  2274. '                        APPREMERR_FATAL - fatal error
  2275. '                        APPREMERR_NONFATAL - non-fatal error, user chose to abort
  2276. '                        APPREMERR_USERCANCEL - user chose to cancel (no error)
  2277. '     [fWaitForParent] - True if the application removal utility should wait
  2278. '                        for the parent (this process) to finish before starting
  2279. '                        to remove files.  Otherwise it may not be able to remove
  2280. '                        this process' executable file, depending upon timing.
  2281. '                        Defaults to False if not specified.
  2282. '-----------------------------------------------------------
  2283. '
  2284. Function GetAppRemovalCmdLine(ByVal strAppRemovalEXE As String, ByVal strAppRemovalLog, ByVal nErrorLevel As Integer, Optional fWaitForParent)
  2285.     Dim strEXE As String
  2286.     Dim strLog As String
  2287.     Dim strErrLevel As String
  2288.     Dim strForce As String
  2289.     Dim strWait As String
  2290.  
  2291.     If IsMissing(fWaitForParent) Then
  2292.         fWaitForParent = False
  2293.     End If
  2294.     
  2295.     strEXE = AddQuotesToFN(strAppRemovalEXE)
  2296.     strLog = "-n " & """" & GetLongPathName(strAppRemovalLog) & """"
  2297.     strErrLevel = IIf(nErrorLevel <> APPREMERR_NONE, "-e " & Format(nErrorLevel), "")
  2298.     If nErrorLevel <> APPREMERR_NONE Then
  2299.         strForce = " -f"
  2300.     End If
  2301.     If fWaitForParent Then
  2302.         Dim curProcessId As Currency
  2303.         Dim Wrap As Currency
  2304.         Dim lProcessId As Long
  2305.         Dim cProcessId As Currency
  2306.         
  2307.         Wrap = 2 * (CCur(&H7FFFFFFF) + 1)
  2308.  
  2309.         'Always print as an unsigned long
  2310.         lProcessId = GetCurrentProcessId()
  2311.         cProcessId = lProcessId
  2312.         If cProcessId < 0 Then cProcessId = cProcessId + Wrap
  2313.  
  2314.         strWait = " -w " & str(cProcessId)
  2315.     End If
  2316.     
  2317.     GetAppRemovalCmdLine = strEXE & " " & strLog & " " & strErrLevel & strForce & strWait
  2318. End Function
  2319. #End If
  2320.  
  2321. #If Win32 And LOGGING Then
  2322. '-----------------------------------------------------------
  2323. ' FUNCTION: IncrementRefCount
  2324. '
  2325. ' Increments the reference count on a file in the registry
  2326. ' so that it may properly be removed if the user chooses
  2327. ' to remove this application.
  2328. '
  2329. ' IN: [strFullPath] - FULL path/filename of the file
  2330. '     [fFileAlreadyExisted] - indicates whether the given
  2331. '                             file already existed on the
  2332. '                             hard drive
  2333. '-----------------------------------------------------------
  2334. '
  2335. Sub IncrementRefCount(ByVal strFullPath As String, ByVal fFileAlreadyExisted As Boolean)
  2336.     Dim strSharedDLLsKey As String
  2337.     strSharedDLLsKey = RegPathWinCurrentVersion() & "\SharedDLLs"
  2338.     
  2339.     'We must always use the LFN for the filename, so that we can uniquely
  2340.     'and accurately identify the file in the registry.
  2341.     strFullPath = GetLongPathName(strFullPath)
  2342.     
  2343.     'Get the current reference count for this file
  2344.     Dim fSuccess As Boolean
  2345.     Dim hkey As Long
  2346.     fSuccess = RegCreateKey(HKEY_LOCAL_MACHINE, strSharedDLLsKey, "", hkey)
  2347.     If fSuccess Then
  2348.         Dim lCurRefCount As Long
  2349.         If Not RegQueryRefCount(hkey, strFullPath, lCurRefCount) Then
  2350.             'No current reference count for this file
  2351.             If fFileAlreadyExisted Then
  2352.                 'If there was no reference count, but the file was found
  2353.                 'on the hard drive, it means one of two things:
  2354.                 '  1) This file is shipped with the operating system
  2355.                 '  2) This file was installed by an older setup program
  2356.                 '     that does not do reference counting
  2357.                 'In either case, the correct conservative thing to do
  2358.                 'is assume that the file is needed by some application,
  2359.                 'which means it should have a reference count of at
  2360.                 'least 1.  This way, our application removal program
  2361.                 'will not delete this file.
  2362.                 lCurRefCount = 1
  2363.             Else
  2364.                 lCurRefCount = 0
  2365.             End If
  2366.         End If
  2367.         
  2368.         'Increment the count in the registry
  2369.         fSuccess = RegSetNumericValue(hkey, strFullPath, lCurRefCount + 1, False)
  2370.         If Not fSuccess Then
  2371.             GoTo DoErr
  2372.         End If
  2373.         RegCloseKey hkey
  2374.     Else
  2375.         GoTo DoErr
  2376.     End If
  2377.     
  2378.     Exit Sub
  2379.     
  2380. DoErr:
  2381.     'An error message should have already been shown to the user
  2382.     Exit Sub
  2383. End Sub
  2384. #End If
  2385.  
  2386. '-----------------------------------------------------------
  2387. ' FUNCTION: InitDiskInfo
  2388. '
  2389. ' Called before calculating disk space to initialize
  2390. ' values used/determined when calculating disk space
  2391. ' required.
  2392. '-----------------------------------------------------------
  2393. '
  2394. Sub InitDiskInfo()
  2395.     Const strTmp$ = "TMP"
  2396.     Const strTEMP$ = "TEMP"
  2397.  
  2398.     '
  2399.     'Initialize "table" of drives used and disk space array
  2400.     '
  2401.     gstrDrivesUsed = gstrNULL
  2402.     Erase gsDiskSpace
  2403.  
  2404.     mlTotalToCopy = 0
  2405.  
  2406.     '
  2407.     'Get drive/directory for temporary files
  2408.     '
  2409.     mstrConcatDrive = UCase$(Environ$(strTmp))
  2410.     If mstrConcatDrive = gstrNULL Then
  2411.         mstrConcatDrive = UCase$(Environ$(strTEMP))
  2412.     End If
  2413.     AddDirSep mstrConcatDrive
  2414.  
  2415.     If mstrConcatDrive <> gstrNULL Then
  2416.         If CheckDrive(mstrConcatDrive, ResolveResString(resTEMPDRIVE)) = False Then
  2417.             mstrConcatDrive = gstrNULL
  2418.         Else
  2419.             '
  2420.             'If we found a temp drive and the drive is "ready", add it to the
  2421.             'table of drives used
  2422.             '
  2423.             gstrDrivesUsed = Left$(mstrConcatDrive, 1)
  2424.             ReDim Preserve gsDiskSpace(1)
  2425.             gsDiskSpace(1).lAvail = GetDiskSpaceFree(mstrConcatDrive)
  2426.             gsDiskSpace(1).lMinAlloc = GetDrivesAllocUnit(mstrConcatDrive)
  2427.         End If
  2428.     End If
  2429. End Sub
  2430.  
  2431. '-----------------------------------------------------------
  2432. ' FUNCTION: IsDisplayNameUnique
  2433. '
  2434. ' Determines whether a given display name for registering
  2435. '   the application removal executable is unique or not.  This
  2436. '   display name is the title which is presented to the
  2437. '   user in Windows 95's control panel Add/Remove Programs
  2438. '   applet.
  2439. '
  2440. ' IN: [hkeyAppRemoval] - open key to the path in the registry
  2441. '                       containing application removal entries
  2442. '     [strDisplayName] - the display name to test for uniqueness
  2443. '
  2444. ' Returns: True if the given display name is already in use,
  2445. '          False if otherwise
  2446. '-----------------------------------------------------------
  2447. '
  2448. #If Win32 And LOGGING Then
  2449. Function IsDisplayNameUnique(ByVal hkeyAppRemoval As Long, ByVal strDisplayName As String) As Boolean
  2450.     Dim lIdx As Long
  2451.     Dim strSubkey As String
  2452.     Dim strDisplayNameExisting As String
  2453.     Const strKEY_DISPLAYNAME$ = "DisplayName"
  2454.     
  2455.     IsDisplayNameUnique = True
  2456.     
  2457.     lIdx = 0
  2458.     Do
  2459.         Select Case RegEnumKey(hkeyAppRemoval, lIdx, strSubkey)
  2460.         Case ERROR_NO_MORE_ITEMS
  2461.             'No more keys - must be unique
  2462.             Exit Do
  2463.         Case ERROR_SUCCESS
  2464.             'We have a key to some application removal program.  Compare its
  2465.             '  display name with ours
  2466.             Dim hkeyExisting As Long
  2467.             
  2468.             If RegOpenKey(hkeyAppRemoval, strSubkey, hkeyExisting) Then
  2469.                 If RegQueryStringValue(hkeyExisting, strKEY_DISPLAYNAME, strDisplayNameExisting) Then
  2470.                     If strDisplayNameExisting = strDisplayName Then
  2471.                         'There is a match to an existing display name
  2472.                         IsDisplayNameUnique = False
  2473.                         RegCloseKey hkeyExisting
  2474.                         Exit Do
  2475.                     End If
  2476.                 End If
  2477.                 RegCloseKey hkeyExisting
  2478.             End If
  2479.         Case Else
  2480.             'Error, we must assume it's unique.  An error will probably
  2481.             '  occur later when trying to add to the registry
  2482.             Exit Do
  2483.         End Select
  2484.         lIdx = lIdx + 1
  2485.     Loop
  2486. End Function
  2487. #End If
  2488.  
  2489. '-----------------------------------------------------------
  2490. ' FUNCTION: IsNewerVer
  2491. '
  2492. ' Compares two file version structures and determines
  2493. ' whether the source file version is newer (greater) than
  2494. ' the destination file version.  This is used to determine
  2495. ' whether a file needs to be installed or not
  2496. '
  2497. ' IN: [sSrcVer] - source file version information
  2498. '     [sDestVer] - dest file version information
  2499. '
  2500. ' Returns: True if source file is newer than dest file,
  2501. '          False if otherwise
  2502. '-----------------------------------------------------------
  2503. '
  2504. Function IsNewerVer(sSrcVer As VERINFO, sDestVer As VERINFO) As Integer
  2505.     IsNewerVer = False
  2506.  
  2507.     If sSrcVer.nMSHi > sDestVer.nMSHi Then GoTo INVNewer
  2508.     If sSrcVer.nMSHi < sDestVer.nMSHi Then GoTo INVOlder
  2509.     
  2510.     If sSrcVer.nMSLo > sDestVer.nMSLo Then GoTo INVNewer
  2511.     If sSrcVer.nMSLo < sDestVer.nMSLo Then GoTo INVOlder
  2512.     
  2513.     If sSrcVer.nLSHi > sDestVer.nLSHi Then GoTo INVNewer
  2514.     If sSrcVer.nLSHi < sDestVer.nLSHi Then GoTo INVOlder
  2515.     
  2516.     If sSrcVer.nLSLo > sDestVer.nLSLo Then GoTo INVNewer
  2517.  
  2518.     GoTo INVOlder
  2519.  
  2520. INVNewer:
  2521.     IsNewerVer = True
  2522. INVOlder:
  2523. End Function
  2524.  
  2525. '-----------------------------------------------------------
  2526. ' FUNCTION: IsValidDestDir
  2527. '
  2528. ' Determines whether or not the destination directory
  2529. ' specifed in the "DefaultDir" key of the [Setup] section
  2530. ' in SETUP.LST or a destination dir entered by the user
  2531. ' is not a subdirectory of the source directory.
  2532. '
  2533. ' Notes: [gstrSrcPath] - points to the source directory
  2534. '        [gstrDestDir] - points to the dest directory
  2535. '
  2536. ' Returns: True if dest dir is a valid location, False
  2537. '          otherwise
  2538. '-----------------------------------------------------------
  2539. '
  2540. Function IsValidDestDir() As Integer
  2541.     Dim strMsg As String
  2542.     Dim intSrc As Integer
  2543.     Dim intDest As Integer
  2544.  
  2545.     '
  2546.     'Both of these paths are *always* in the format 'X:\' or 'X:\DIRNAME\'.
  2547.     '
  2548.  
  2549.     intSrc = InStr(4, gstrSrcPath, gstrSEP_DIR)
  2550.     If intSrc = 0 Then
  2551.         intSrc = Len(gstrSrcPath)
  2552.     End If
  2553.  
  2554.     intDest = InStr(4, gstrDestDir, gstrSEP_DIR)
  2555.     If intDest = 0 Then
  2556.         intDest = Len(gstrDestDir)
  2557.     End If
  2558.  
  2559.     If Left$(gstrDestDir, intDest) = Left$(gstrSrcPath, intSrc) > 0 Then
  2560.         IsValidDestDir = False
  2561.         strMsg = ResolveResString(resDIRSPECIFIED) & LF$ & gstrDestDir & LF$ & ResolveResString(resSAMEASSRC)
  2562.         MsgFunc strMsg, MB_OK Or MB_ICONEXCLAMATION, gstrTitle
  2563.     Else
  2564.         IsValidDestDir = True
  2565.     End If
  2566. End Function
  2567.  
  2568. '-----------------------------------------------------------
  2569. ' FUNCTION: MakePath
  2570. '
  2571. ' Creates the specified directory path
  2572. '
  2573. ' IN: [strDirName] - name of the dir path to make
  2574. '     [fAllowIgnore] - whether or not to allow the user to
  2575. '                      ignore any encountered errors.  If
  2576. '                      false, the function only returns
  2577. '                      if successful.  If missing, this
  2578. '                      defaults to True.
  2579. '
  2580. ' Returns: True if successful, False if error and the user
  2581. '          chose to ignore.  (The function does not return
  2582. '          if the user selects ABORT/CANCEL on an error.)
  2583. '-----------------------------------------------------------
  2584. '
  2585. Public Function MakePath(ByVal strDir As String, Optional ByVal fAllowIgnore) As Boolean
  2586.     If IsMissing(fAllowIgnore) Then
  2587.         fAllowIgnore = True
  2588.     End If
  2589.     
  2590.     Do
  2591.         If MakePathAux(strDir) Then
  2592.             MakePath = True
  2593.             Exit Function
  2594.         Else
  2595.             Dim strMsg As String
  2596.             Dim iRet As Integer
  2597.             
  2598.             strMsg = ResolveResString(resMAKEDIR) & LF$ & strDir
  2599.             iRet = MsgError(strMsg, IIf(fAllowIgnore, MB_ABORTRETRYIGNORE, MB_RETRYCANCEL) Or MB_ICONEXCLAMATION Or MB_DEFBUTTON2, gstrSETMSG)
  2600.             Select Case iRet
  2601.             Case IDABORT, IDCANCEL
  2602.                 ExitSetup frmCopy, gintRET_ABORT
  2603.             Case IDIGNORE
  2604.                 MakePath = False
  2605.                 Exit Function
  2606.             Case IDRETRY
  2607.             End Select
  2608.         End If
  2609.     Loop
  2610. End Function
  2611.  
  2612. #If Win32 And LOGGING Then
  2613. '----------------------------------------------------------
  2614. ' SUB: MoveAppRemovalFiles
  2615. '
  2616. ' Moves the app removal logfile to the application directory,
  2617. ' and registers the app removal executable with the operating
  2618. ' system.
  2619. '----------------------------------------------------------
  2620. Sub MoveAppRemovalFiles()
  2621.     Dim strNewAppRemovalLogName As String
  2622.     
  2623.     'Find a unique name for the app removal logfile in the
  2624.     'application directory
  2625.     
  2626.     '...First try the default extension
  2627.     strNewAppRemovalLogName = gstrDestDir & mstrFILE_APPREMOVALLOGBASE & mstrFILE_APPREMOVALLOGEXT
  2628.     If FileExists(strNewAppRemovalLogName) Then
  2629.         '...Next try incrementing integral extensions
  2630.         Dim iExt As Integer
  2631.         Do
  2632.             If iExt > 999 Then
  2633.                 GoTo CopyErr
  2634.             End If
  2635.             
  2636.             strNewAppRemovalLogName = gstrDestDir & mstrFILE_APPREMOVALLOGBASE & gstrSEP_EXT & Format(iExt, "000")
  2637.             If Not FileExists(strNewAppRemovalLogName) Then
  2638.                 Exit Do 'Unique name was found
  2639.             Else
  2640.                 iExt = iExt + 1
  2641.             End If
  2642.         Loop
  2643.     End If
  2644.     
  2645.     
  2646.     
  2647.     On Error GoTo CopyErr
  2648.     FileCopy gstrAppRemovalLog, strNewAppRemovalLogName
  2649.     
  2650.     'Now we need to start logging in the new logfile, so that the
  2651.     'creation of the application removal icon under NT gets logged.
  2652.     EnableLogging strNewAppRemovalLogName
  2653.     
  2654.     On Error GoTo 0
  2655.     If Not RegisterAppRemovalEXE(gstrAppRemovalEXE, strNewAppRemovalLogName) Then
  2656.         If TreatAsWin95() Then
  2657.             MsgError ResolveResString(resCANTREGISTERAPPREMOVER), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2658.         Else
  2659.             MsgError ResolveResString(resCANTCREATEAPPREMOVALICON), MB_ICONEXCLAMATION Or MB_OK, gstrTitle
  2660.         End If
  2661.         ExitSetup frmSetup1, gintRET_FATAL
  2662.     End If
  2663.     
  2664.     'Now we can delete the original logfile, since we no longer have a reference
  2665.     'to it, and start using the new logfile
  2666.     On Error Resume Next
  2667.     Kill gstrAppRemovalLog
  2668.     
  2669.     'This temporary app removal logfile should no longer be used
  2670.     gstrAppRemovalLog = strNewAppRemovalLogName
  2671.     gfAppRemovalFilesMoved = True
  2672.     
  2673.     Exit Sub
  2674.     
  2675. CleanUpOnErr:
  2676.     On Error Resume Next
  2677.     Kill strNewAppRemovalLogName
  2678.     On Error GoTo 0
  2679.     MsgError ResolveResString(resCANTCOPYLOG, "|1", gstrAppRemovalLog), vbExclamation Or vbOKOnly, gstrTitle
  2680.     ExitSetup Screen.ActiveForm, gintRET_FATAL
  2681.     
  2682. CopyErr:
  2683.     Resume CleanUpOnErr
  2684. End Sub
  2685. #End If
  2686.  
  2687. '-----------------------------------------------------------
  2688. ' FUNCTION: OpenConcatFile
  2689. '
  2690. ' Opens a file to be the destination for concatenation of
  2691. ' two or more source files that (typically) have been
  2692. ' split across disks.
  2693. '
  2694. ' Returns: The handle of the file to use for concatentation
  2695. '          if the open was successful, or -1 if the open
  2696. '          failed and the user chose to ignore the error.
  2697. '-----------------------------------------------------------
  2698. '
  2699. Function OpenConcatFile() As Integer
  2700.     Dim intFileNum As Integer
  2701.     Dim strMsg As String
  2702.  
  2703.     On Error Resume Next
  2704.  
  2705.     Do
  2706.         Kill mstrConcatDrive & mstrCONCATFILE
  2707.         Err = 0
  2708.  
  2709.         intFileNum = FreeFile
  2710.         Open mstrConcatDrive & mstrCONCATFILE For Binary Access Write As intFileNum
  2711.  
  2712.         If Err > 0 Then
  2713.             strMsg = ResolveResString(resNOCREATE) & LS$ & mstrConcatDrive & mstrCONCATFILE
  2714.             strMsg = strMsg & LS$ & ResolveResString(resNOTPROTECT)
  2715.             Select Case MsgError(strMsg, MB_ABORTRETRYIGNORE Or MB_ICONEXCLAMATION Or MB_DEFBUTTON2, gstrSETMSG)
  2716.             Case IDABORT
  2717.                 ExitSetup frmCopy, gintRET_ABORT
  2718.             Case IDIGNORE
  2719.                 OpenConcatFile = -1
  2720.                 Exit Function
  2721.             End Select
  2722.         End If
  2723.     Loop While Err > 0
  2724.  
  2725.     OpenConcatFile = intFileNum
  2726. End Function
  2727.  
  2728. '-----------------------------------------------------------
  2729. ' SUB: ParseDate
  2730. '
  2731. ' Same as CDate with a string argument, except that it
  2732. ' ignores the current localization settings.  This is
  2733. ' important because SETUP.LST always uses the same
  2734. ' format for dates.
  2735. '
  2736. ' IN: [strDate] - string representing the date in
  2737. '                 the format mm/dd/yy or mm/dd/yyyy
  2738. ' OUT: The date which strDate represents
  2739. '-----------------------------------------------------------
  2740. '
  2741. Function ParseDate(ByVal strDate As String) As Date
  2742.     Const strSEP$ = "/"
  2743.     Dim iMonth As Integer
  2744.     Dim iDay As Integer
  2745.     Dim iYear As Integer
  2746.     Dim iPos As Integer
  2747.     
  2748.     iPos = InStr(strDate, strSEP)
  2749.     If iPos = 0 Then GoTo Err
  2750.     iMonth = Val(Left$(strDate, iPos - 1))
  2751.     strDate = Mid$(strDate, iPos + 1)
  2752.     
  2753.     iPos = InStr(strDate, strSEP)
  2754.     If iPos = 0 Then GoTo Err
  2755.     iDay = Val(Left$(strDate, iPos - 1))
  2756.     strDate = Mid$(strDate, iPos + 1)
  2757.     
  2758.     iYear = Val(strDate)
  2759.     If iYear < 100 Then iYear = iYear + 1900
  2760.     
  2761.     ParseDate = DateSerial(iYear, iMonth, iDay)
  2762.     
  2763.     Exit Function
  2764.     
  2765. Err:
  2766.     Error 13 'Type mismatch error, same as intrinsic CDate triggers on error
  2767. End Function
  2768.  
  2769. '-----------------------------------------------------------
  2770. ' SUB: PerformDDE
  2771. '
  2772. ' Performs a Program Manager DDE operation as specified
  2773. ' by the intDDE flag and the passed in parameters.
  2774. ' Possible operations are:
  2775. '
  2776. '   mintDDE_ITEMADD:  Add an icon to the active group
  2777. '   mintDDE_GRPADD:   Create a program manager group
  2778. '
  2779. ' IN: [frm] - form containing a label named 'lblDDE'
  2780. '     [strGroup] - name of group to create
  2781. '     [strTitle] - title of icon or group
  2782. '     [strCmd] - command line for icon/item to add
  2783. '     [intDDE] - ProgMan DDE action to perform
  2784. '-----------------------------------------------------------
  2785. '
  2786. Sub PerformDDE(frm As Form, ByVal strGroup As String, ByVal strCmd As String, ByVal strTitle As String, ByVal intDDE As Integer, ByVal fLog As Boolean)
  2787.     Const strCOMMA$ = ","
  2788.     Const strRESTORE$ = ", 1)]"
  2789.     Const strENDCMD$ = ")]"
  2790.     Const strSHOWGRP$ = "[ShowGroup("
  2791.     Const strADDGRP$ = "[CreateGroup("
  2792.     Const strREPLITEM$ = "[ReplaceItem("
  2793.     Const strADDITEM$ = "[AddItem("
  2794.  
  2795.     Dim intIdx As Integer        'loop variable
  2796.  
  2797.     SetMousePtr gintMOUSE_HOURGLASS
  2798.  
  2799.     '
  2800.     'Initialize for DDE Conversation with Windows Program Manager in
  2801.     'manual mode (.LinkMode = 2) where destination control is not auto-
  2802.     'matically updated.  Set DDE timeout for 10 seconds.  The loop around
  2803.     'DoEvents() is to allow time for the DDE Execute to be processsed.
  2804.     '
  2805.  
  2806.     Dim intRetry As Integer
  2807.     For intRetry = 1 To 20
  2808.         On Error Resume Next
  2809.         frm.lblDDE.LinkTopic = "PROGMAN|PROGMAN"
  2810.         If Err = 0 Then
  2811.             Exit For
  2812.         End If
  2813.         DoEvents
  2814.     Next intRetry
  2815.         
  2816.     frm.lblDDE.LinkMode = 2
  2817.     For intIdx = 1 To 10
  2818.       DoEvents
  2819.     Next
  2820.     frm.lblDDE.LinkTimeout = 100
  2821.  
  2822.     On Error Resume Next
  2823.  
  2824.     If Err = 0 Then
  2825.         Select Case intDDE
  2826.         Case mintDDE_ITEMADD
  2827.             'The item will be created in the most-recently created group
  2828.             #If Win32 And LOGGING Then
  2829.                 'Write the action to the logfile
  2830.                 If fLog Then
  2831.                     NewAction gstrKEY_PROGMANITEM, """" & strGroup & """" & ", " & """" & strTitle & """"
  2832.                 End If
  2833.             #End If
  2834.             frm.lblDDE.LinkExecute strREPLITEM & strTitle & strENDCMD
  2835.             Err = 0
  2836.             frm.lblDDE.LinkExecute strADDITEM & strCmd & strCOMMA & strTitle & String$(3, strCOMMA) & strENDCMD
  2837.         Case mintDDE_GRPADD
  2838.             #If Win16 Then
  2839.                 frm.lblDDE.LinkExecute strADDGRP & strGroup & strCOMMA & strCmd & strENDCMD
  2840.             #Else
  2841.                 ' Win32
  2842.                 #If LOGGING Then
  2843.                     'Write the action to the logfile
  2844.                     If fLog Then
  2845.                         NewAction gstrKEY_PROGMANGROUP, """" & strGroup & """"
  2846.                     End If
  2847.                 #End If
  2848.                 frm.lblDDE.LinkExecute strADDGRP & strGroup & strENDCMD
  2849.             #End If
  2850.             frm.lblDDE.LinkExecute strSHOWGRP & strGroup & strRESTORE
  2851.         End Select
  2852.     End If
  2853.  
  2854.     
  2855.     '
  2856.     'Disconnect DDE Link
  2857.     '
  2858.  
  2859.     frm.lblDDE.LinkMode = 0
  2860.     frm.lblDDE.LinkTopic = ""
  2861.  
  2862.  
  2863.     SetMousePtr gintMOUSE_DEFAULT
  2864.  
  2865.     #If Win32 And LOGGING Then
  2866.         If fLog Then
  2867.             CommitAction
  2868.         End If
  2869.     #End If
  2870.     
  2871.     Err = 0
  2872. End Sub
  2873.  
  2874. '-----------------------------------------------------------
  2875. ' SUB: PromptForNextDisk
  2876. '
  2877. ' If the source media is removable or a network connection,
  2878. ' prompts the user to insert the specified disk number
  2879. ' containing the filename which is used to determine that
  2880. ' the correct disk is inserted.
  2881. '
  2882. ' IN: [intDiskNum] - disk number to insert
  2883. '     [strDetectFile] - file to search for to ensure that
  2884. '                       the correct disk was inserted
  2885. '
  2886. ' Notes: [gstrSrcPath] - used to identify the source drive
  2887. '-----------------------------------------------------------
  2888. '
  2889. Sub PromptForNextDisk(ByVal intDiskNum As Integer, ByVal strDetectFile As String)
  2890.     Static intDrvType As Integer
  2891.  
  2892.     Dim intRC As Integer
  2893.     Dim strMsg As String
  2894.     Dim strDrive As String
  2895.  
  2896.     On Error Resume Next
  2897.  
  2898.     '
  2899.     'Get source drive and, if we haven't yet determined it, get the
  2900.     'source drive type
  2901.     '
  2902.     
  2903.     strDrive = Left$(gstrSrcPath, 2)
  2904.     If intDrvType = 0 Then
  2905.         If IsUNCName(strDrive) Then
  2906.             intDrvType = intDRIVE_REMOTE
  2907.             strDrive = gstrSrcPath
  2908.         Else
  2909.             intDrvType = GetDriveType(Asc(strDrive) - 65)
  2910.         End If
  2911.     End If
  2912.  
  2913.     If intDrvType <> intDRIVE_FIXED Then
  2914.         While FileExists(gstrSrcPath & strDetectFile) = False
  2915.             Select Case intDrvType
  2916.             Case 0, intDRIVE_REMOVABLE
  2917.                 strMsg = ResolveResString(resINSERT) & LF$ & ResolveResString(resDISK) & Format$(intDiskNum)
  2918.                 strMsg = strMsg & ResolveResString(resINTO) & strDrive
  2919.             Case intDRIVE_REMOTE
  2920.                 strMsg = ResolveResString(resCHKCONNECT) & strDrive
  2921.             End Select
  2922.  
  2923.             Beep
  2924.             intRC = MsgFunc(strMsg, MB_OKCANCEL Or MB_ICONEXCLAMATION, gstrSETMSG)
  2925.             If intRC = IDCANCEL Then
  2926.                 ExitSetup frmCopy, gintRET_EXIT
  2927.             End If
  2928.         Wend
  2929.     End If
  2930.  
  2931.     gintCurrentDisk = intDiskNum
  2932. End Sub
  2933.  
  2934. '-----------------------------------------------------------
  2935. ' FUNCTION: ReadIniFile
  2936. '
  2937. ' Reads a value from the specified section/key of the
  2938. ' specified .INI file
  2939. '
  2940. ' IN: [strIniFile] - name of .INI file to read
  2941. '     [strSection] - section where key is found
  2942. '     [strKey] - name of key to get the value of
  2943. '
  2944. ' Returns: non-zero terminated value of .INI file key
  2945. '-----------------------------------------------------------
  2946. '
  2947. Function ReadIniFile(ByVal strIniFile As String, ByVal strSECTION As String, ByVal strKey As String) As String
  2948.     Dim strBuffer As String
  2949.     Dim intPos As Integer
  2950.  
  2951.     '
  2952.     'If successful read of .INI file, strip any trailing zero returned by the Windows API GetPrivateProfileString
  2953.     '
  2954.     strBuffer = Space$(gintMAX_SIZE)
  2955.     
  2956.     If GetPrivateProfileString(strSECTION, strKey, gstrNULL, strBuffer, gintMAX_SIZE, strIniFile) > 0 Then
  2957.         ReadIniFile = RTrim$(StripTerminator(strBuffer))
  2958.     Else
  2959.         ReadIniFile = gstrNULL
  2960.     End If
  2961. End Function
  2962.  
  2963. '-----------------------------------------------------------
  2964. ' SUB: ReadSetupFileLine
  2965. '
  2966. ' Reads the requested 'FileX=' key from the specified
  2967. ' section of the setup information file (SETUP.LST).
  2968. '
  2969. ' IN: [strSection] - name of section to read from SETUP.LST,
  2970. '                    Ex: "Files"
  2971. '     [intFileNum] - file number index to read
  2972. '
  2973. ' OUT: [sFile] - FILEINFO Type variable that, after parsing,
  2974. '                holds the information for the file
  2975. '                described.
  2976. '
  2977. ' Returns: True if the requested info was successfully read,
  2978. '          False otherwise
  2979. '
  2980. ' Notes: Lines in the setup information file have the
  2981. '        following format:
  2982. '
  2983. '        #,[SPLIT],SrcName,DestName,DestDir,Register,
  2984. '        Date,Size,Version
  2985. '
  2986. '        [#] - disk number where this file is located
  2987. '        [SPLIT] - optional, determines whether this is
  2988. '                  an extent of a split file.  The last
  2989. '                  extent does not specify this key
  2990. '        [SrcName] - filename on the installation media
  2991. '        [DestName] - file name to use when copied
  2992. '
  2993. '        (For split files, the following info is required only
  2994. '        for the *first* extent)
  2995. '
  2996. '        [DestDir] - dirname or macro specifying destdir
  2997. '        [Register] - reginfo file name or macro specifying
  2998. '                     file registration action
  2999. '        [Date] - date of the source file
  3000. '        [Size] - size of the source file
  3001. '        [Version] - optional, version number string
  3002. '-----------------------------------------------------------
  3003. '
  3004. Function ReadSetupFileLine(ByVal strSECTION As String, ByVal intFileNum As Integer, sFile As FILEINFO) As Integer
  3005.     Static strSplitName As String
  3006.     Const CompareBinary = 0
  3007.  
  3008.     Dim strLine As String
  3009.     Dim strMsg As String
  3010.     Dim intOffset As Integer
  3011.     Dim intAnchor As Integer
  3012.     Dim fDone As Integer
  3013.     Dim fErr As Boolean
  3014.  
  3015.     ReadSetupFileLine = False
  3016.  
  3017.     sFile.fSystem = False
  3018.     sFile.fShared = False
  3019.     
  3020.     '
  3021.     'Read the requested line, if unable to read it (strLine = gstrNULL) then exit
  3022.     '
  3023.     strLine = ReadIniFile(gstrSetupInfoFile, strSECTION, gstrINI_FILE & Format$(intFileNum))
  3024.     If strLine = gstrNULL Then
  3025.         Exit Function
  3026.     End If
  3027.  
  3028.     '
  3029.     'Get the disk number
  3030.     '
  3031.     intOffset = InStr(1, strLine, gstrCOMMA, CompareBinary)
  3032.     sFile.intDiskNum = Val(Left$(strLine, intOffset - 1))
  3033.     If sFile.intDiskNum < 1 Then
  3034.         GoTo RSFLError
  3035.     End If
  3036.  
  3037.     '
  3038.     'Is this a split file extent (other than the last extent of a split file)
  3039.     '
  3040.     intAnchor = intOffset + 1
  3041.     
  3042.     intOffset = InStr(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3043.     If intOffset > 0 Then
  3044.         sFile.fSplit = IIf(Mid$(strLine, intAnchor, intOffset - intAnchor) = gstrNULL, False, True)
  3045.     Else
  3046.         GoTo RSFLError
  3047.     End If
  3048.  
  3049.     '
  3050.     'source file name, ensure it's not a UNC name
  3051.     '
  3052.     intAnchor = intOffset + 1
  3053.     sFile.strSrcName = strExtractFilenameItem(strLine, intAnchor, fErr)
  3054.     If fErr Then GoTo RSFLError
  3055.     If IsUNCName(sFile.strSrcName) = True Then GoTo RSFLError
  3056.     intAnchor = intAnchor + 1 'Skip past the comma
  3057.     
  3058.     '
  3059.     'dest file name, ensure it's not a UNC name
  3060.     '
  3061.     sFile.strDestName = strExtractFilenameItem(strLine, intAnchor, fErr)
  3062.     If fErr Then GoTo RSFLError
  3063.     If IsUNCName(sFile.strDestName) = True Then GoTo RSFLError
  3064.     If Mid$(strLine, intAnchor, 1) = gstrCOMMA Then
  3065.         If IsUNCName(sFile.strDestName) = True Then
  3066.             GoTo RSFLError
  3067.         End If
  3068.         intAnchor = intAnchor + 1 'Skip past the comma
  3069.     Else
  3070.         '
  3071.         'If no list separator after the dest file name, then this should be a
  3072.         'split file extent
  3073.         '
  3074.         If strSplitName = gstrNULL Then
  3075.             GoTo RSFLError
  3076.         Else
  3077.             sFile.strDestDir = gstrNULL
  3078.             fDone = True
  3079.         End If
  3080.     End If
  3081.  
  3082.     '
  3083.     'Ensure that SPLIT files in SETUP.LST are ended properly by checking that all dest
  3084.     'file names after the first SPLIT line are identical, up to and including the
  3085.     'dest file name of the very next occurring *non* SPLIT line.
  3086.     '
  3087.     If sFile.fSplit = True Then
  3088.         If strSplitName = gstrNULL Then
  3089.             strSplitName = sFile.strDestName
  3090.         Else
  3091.             If strSplitName <> sFile.strDestName Then
  3092.                 GoTo RSFLError
  3093.             End If
  3094.         End If
  3095.     Else
  3096.         If strSplitName <> gstrNULL And strSplitName <> sFile.strDestName Then
  3097.             GoTo RSFLError
  3098.         Else
  3099.             strSplitName = gstrNULL
  3100.         End If
  3101.     End If
  3102.  
  3103.     If fDone = True Then
  3104.         GoTo RSFLDone
  3105.     End If
  3106.  
  3107.     '
  3108.     'parse and resolve destination directory
  3109.     '
  3110.     intOffset = InStr(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3111.     If intOffset > 0 Then
  3112.         Dim strInitialDestDir As String
  3113.         strInitialDestDir = Mid$(strLine, intAnchor, intOffset - intAnchor)
  3114.         If InStr(strInitialDestDir, gstrWINSYSDESTSYSFILE) Then
  3115.             sFile.fSystem = True
  3116.         End If
  3117.         sFile.strDestDir = ResolveDestDir(strInitialDestDir)
  3118.         If sFile.strDestDir <> "?" Then
  3119.             sFile.strDestDir = ResolveDir(sFile.strDestDir, False, False)
  3120.             If sFile.strDestDir = gstrNULL Or IsUNCName(sFile.strDestDir) Then
  3121.                 GoTo RSFLError
  3122.             End If
  3123.         End If
  3124.     Else
  3125.         GoTo RSFLError
  3126.     End If
  3127.  
  3128.     '
  3129.     'file registration information
  3130.     '
  3131.     intAnchor = intOffset + 1
  3132.     intOffset = InStr(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3133.     If intOffset > 0 Then
  3134.         sFile.strRegister = Mid$(strLine, intAnchor, intOffset - intAnchor)
  3135.     Else
  3136.         GoTo RSFLError
  3137.     End If
  3138.  
  3139.     '
  3140.     'Extract file share type
  3141.     '
  3142.     intAnchor = intOffset + 1
  3143.     intOffset = InStr(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3144.     sFile.fShared = False
  3145.     If intOffset > 0 Then
  3146.         Dim strShareType As String
  3147.         strShareType = Mid$(strLine, intAnchor, intOffset - intAnchor)
  3148.         Select Case strShareType
  3149.         Case mstrPRIVATEFILE
  3150.             sFile.fShared = False
  3151.         Case mstrSHAREDFILE
  3152.             If sFile.fSystem Then
  3153.                 'A file cannot be both system and shared
  3154.                 GoTo RSFLError
  3155.             End If
  3156.             
  3157.             sFile.fShared = True
  3158.         Case Else
  3159.             GoTo RSFLError
  3160.         End Select
  3161.     End If
  3162.     
  3163.     '
  3164.     'Extract file date and convert to a date variant
  3165.     '
  3166.     intAnchor = intOffset + 1
  3167.     intOffset = InStr(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3168.     If intOffset > 0 Then
  3169.         If IsDate(Mid$(strLine, intAnchor, intOffset - intAnchor)) = True Then
  3170.             sFile.varDate = ParseDate(Mid$(strLine, intAnchor, intOffset - intAnchor))
  3171.         Else
  3172.             GoTo RSFLError
  3173.         End If
  3174.     End If
  3175.  
  3176.     '
  3177.     'Get file size, this may be the last field on the line, so need special check
  3178.     '
  3179.     intAnchor = intOffset + 1
  3180.     intOffset = InStr(intAnchor, strLine, gstrCOMMA, CompareBinary)
  3181.     If intOffset > 0 Then
  3182.         sFile.lFileSize = Val(Mid$(strLine, intAnchor, intOffset - intAnchor))
  3183.     Else
  3184.         sFile.lFileSize = Val(Mid$(strLine, intAnchor))
  3185.     End If
  3186.     If sFile.lFileSize < 0 Then
  3187.         GoTo RSFLError
  3188.     End If
  3189.  
  3190.     '
  3191.     'If there was a comma after the file size, the rest of the line is assumed to be
  3192.     'the version number, otherwise flag that there is no version info
  3193.     '
  3194.     If intOffset > 0 Then
  3195.         PackVerInfo Mid$(strLine, intOffset + 1), sFile.sVerInfo
  3196.     Else
  3197.         sFile.sVerInfo.nMSHi = gintNOVERINFO
  3198.     End If
  3199.  
  3200. RSFLDone:
  3201.     ReadSetupFileLine = True
  3202.     Exit Function
  3203.  
  3204. RSFLError:
  3205.     strMsg = gstrSetupInfoFile & LS$ & ResolveResString(resINVLINE) & LS$
  3206.     strMsg = strMsg & ResolveResString(resSECTNAME) & strSECTION & LF$ & strLine
  3207.     MsgError strMsg, MB_ICONSTOP, gstrTitle
  3208.     ExitSetup frmSetup1, gintRET_FATAL
  3209. End Function
  3210.  
  3211. '-----------------------------------------------------------
  3212. ' SUB: ReadSetupRemoteLine
  3213. '
  3214. ' Reads the requested 'RemoteX=' key from the specified
  3215. ' section of the setup information file (SETUP.LST).
  3216. '
  3217. ' IN: [strSection] - name of section to read from SETUP.LST,
  3218. '                    Ex: "Files"
  3219. '     [intFileNum] - remote number index to read
  3220. '
  3221. ' OUT: [rInfo] - REGINFO Type variable that, after parsing,
  3222. '                holds the information for the line
  3223. '                described.
  3224. '
  3225. ' Returns: True if the requested info was successfully read,
  3226. '          False otherwise
  3227. '
  3228. ' Notes: Remote server lines in the setup information file
  3229. '        have the following format:
  3230. '
  3231. '        address,protocol,authentication-level
  3232. '
  3233. '        [address] - network address of the server, if known
  3234. '        [protocol] - network protocol name, if known
  3235. '        [authentication level] - authentication level (or 0 for default)
  3236. '-----------------------------------------------------------
  3237. '
  3238. Function ReadSetupRemoteLine(ByVal strSECTION As String, ByVal intFileNum As Integer, rInfo As REGINFO) As Integer
  3239.     Dim strLine As String
  3240.     Dim strMsg As String
  3241.     Dim intAnchor As Integer
  3242.     Dim fErr As Boolean
  3243.  
  3244.     ReadSetupRemoteLine = False
  3245.  
  3246.     '
  3247.     'Read the requested line, if unable to read it (strLine = gstrNULL) then exit
  3248.     '
  3249.     strLine = ReadIniFile(gstrSetupInfoFile, strSECTION, gstrINI_REMOTE & Format$(intFileNum))
  3250.     If strLine = gstrNULL Then
  3251.         Exit Function
  3252.     End If
  3253.  
  3254.     '
  3255.     'Get the network address
  3256.     '
  3257.     intAnchor = 1
  3258.     fErr = False
  3259.     If Mid$(strLine, intAnchor, 1) = gstrCOMMA Then
  3260.         rInfo.strNetworkAddress = ""
  3261.     Else
  3262.         rInfo.strNetworkAddress = strExtractFilenameItem(strLine, intAnchor, fErr)
  3263.     End If
  3264.     If fErr Then GoTo RSRLError
  3265.     intAnchor = intAnchor + 1 'Skip past the comma
  3266.  
  3267.     '
  3268.     'Get the network protocol
  3269.     '
  3270.     If Mid$(strLine, intAnchor, 1) = gstrCOMMA Then
  3271.         rInfo.strNetworkProtocol = ""
  3272.     Else
  3273.         rInfo.strNetworkProtocol = strExtractFilenameItem(strLine, intAnchor, fErr)
  3274.     End If
  3275.     If fErr Then GoTo RSRLError
  3276.     intAnchor = intAnchor + 1 'Skip past the comma
  3277.  
  3278.     '
  3279.     'Get the authentication level (must be a single digit
  3280.     '  in the range 0..6)
  3281.     '
  3282.     Const intMaxAuthentication = 6
  3283.     Dim strAuthentication As String
  3284.     
  3285.     strAuthentication = Mid$(strLine, intAnchor)
  3286.     If Len(strAuthentication) <> 1 Then GoTo RSRLError
  3287.     If (Asc(strAuthentication) < Asc("0")) Or (Asc(strAuthentication) > Asc("9")) Then GoTo RSRLError
  3288.     rInfo.intAuthentication = Val(strAuthentication)
  3289.     If rInfo.intAuthentication > intMaxAuthentication Then GoTo RSRLError
  3290.  
  3291.     ReadSetupRemoteLine = True
  3292.     Exit Function
  3293.  
  3294. RSRLError:
  3295.     strMsg = gstrSetupInfoFile & LS$ & ResolveResString(resINVLINE) & LS$
  3296.     strMsg = strMsg & ResolveResString(resSECTNAME) & strSECTION & LF$ & strLine
  3297.     MsgError strMsg, MB_ICONSTOP, gstrTitle
  3298.     ExitSetup frmSetup1, gintRET_FATAL
  3299. End Function
  3300.  
  3301. '-----------------------------------------------------------
  3302. ' FUNCTION: RegCloseKey
  3303. '
  3304. ' Closes an open registry key.
  3305. '
  3306. ' Returns: True on success, else False.
  3307. '-----------------------------------------------------------
  3308. '
  3309. Function RegCloseKey(ByVal hkey As Long) As Boolean
  3310.     Dim lResult As Long
  3311.     
  3312.     On Error GoTo 0
  3313.     lResult = OSRegCloseKey(hkey)
  3314.     RegCloseKey = (lResult = ERROR_SUCCESS)
  3315. End Function
  3316.  
  3317. '-----------------------------------------------------------
  3318. ' FUNCTION: RegCreateKey
  3319. '
  3320. ' Opens (creates if already exists) a key in the system registry.
  3321. '
  3322. ' IN: [hkey] - The HKEY parent.
  3323. '     [lpszSubKeyPermanent] - The first part of the subkey of
  3324. '         'hkey' that will be created or opened.  The application
  3325. '         removal utility (32-bit only) will never delete any part
  3326. '         of this subkey.  May NOT be an empty string ("").
  3327. '     [lpszSubKeyRemovable] - The subkey of hkey\lpszSubKeyPermanent
  3328. '         that will be created or opened.  If the application is
  3329. '         removed (32-bit only), then this entire subtree will be
  3330. '         deleted, if it is empty at the time of application removal.
  3331. '         If this parameter is an empty string (""), then the entry
  3332. '         will not be logged.
  3333. '
  3334. ' OUT: [phkResult] - The HKEY of the newly-created or -opened key.
  3335. '
  3336. ' Returns: True if the key was created/opened OK, False otherwise
  3337. '   Upon success, phkResult is set to the handle of the key.
  3338. '
  3339. '-----------------------------------------------------------
  3340. Function RegCreateKey(ByVal hkey As Long, ByVal lpszSubKeyPermanent As String, ByVal lpszSubKeyRemovable As String, phkResult As Long) As Boolean
  3341.     Dim lResult As Long
  3342.     #If Win32 Then
  3343.     Dim strHkey As String
  3344.     Dim fLog As Boolean
  3345.     #End If
  3346.     Dim strSubKeyFull As String
  3347.  
  3348.     On Error GoTo 0
  3349.  
  3350.     If lpszSubKeyPermanent = "" Then
  3351.         RegCreateKey = False 'Error: lpszSubKeyPermanent must not = ""
  3352.         Exit Function
  3353.     End If
  3354.     
  3355.     If Left$(lpszSubKeyRemovable, 1) = "\" Then
  3356.         lpszSubKeyRemovable = Mid$(lpszSubKeyRemovable, 2)
  3357.     End If
  3358.  
  3359.     #If Win32 Then
  3360.         If lpszSubKeyRemovable = "" Then
  3361.             fLog = False
  3362.         Else
  3363.             fLog = True
  3364.         End If
  3365.     #End If
  3366.     
  3367.     If lpszSubKeyRemovable <> "" Then
  3368.         strSubKeyFull = lpszSubKeyPermanent & "\" & lpszSubKeyRemovable
  3369.     Else
  3370.         strSubKeyFull = lpszSubKeyPermanent
  3371.     End If
  3372.     #If Win32 Then
  3373.         strHkey = strGetHKEYString(hkey)
  3374.     #End If
  3375.  
  3376.     #If Win32 And LOGGING Then
  3377.         If fLog Then
  3378.             NewAction _
  3379.               gstrKEY_REGKEY, _
  3380.               """" & strHkey & "\" & lpszSubKeyPermanent & """" _
  3381.                 & ", " & """" & lpszSubKeyRemovable & """"
  3382.         End If
  3383.     #End If
  3384.  
  3385.     lResult = OSRegCreateKey(hkey, strSubKeyFull, phkResult)
  3386.     If lResult = ERROR_SUCCESS Then
  3387.         RegCreateKey = True
  3388.         #If Win32 And LOGGING Then
  3389.             If fLog Then
  3390.                 CommitAction
  3391.             End If
  3392.             AddHkeyToCache phkResult, strHkey & "\" & strSubKeyFull
  3393.         #End If
  3394.     Else
  3395.         RegCreateKey = False
  3396.         MsgError ResolveResString(resERR_REG), vbOKOnly Or vbExclamation, gstrTitle
  3397.         #If Win32 And LOGGING Then
  3398.             If fLog Then
  3399.                 AbortAction
  3400.             End If
  3401.         #End If
  3402.     End If
  3403. End Function
  3404.  
  3405. '-----------------------------------------------------------
  3406. ' FUNCTION: RegDeleteKey
  3407. '
  3408. ' Deletes an existing key in the system registry.
  3409. '
  3410. ' Returns: True on success, False otherwise
  3411. '-----------------------------------------------------------
  3412. '
  3413. Function RegDeleteKey(ByVal hkey As Long, ByVal lpszSubKey As String) As Boolean
  3414.     Dim lResult As Long
  3415.     
  3416.     On Error GoTo 0
  3417.     lResult = OSRegDeleteKey(hkey, lpszSubKey)
  3418.     RegDeleteKey = (lResult = ERROR_SUCCESS)
  3419. End Function
  3420.  
  3421. '-----------------------------------------------------------
  3422. ' SUB: RegEdit
  3423. '
  3424. ' Calls REGEDIT to add the information in the specifed file
  3425. ' to the system registry.  If your .REG file requires path
  3426. ' information based upon the destination directory given by
  3427. ' the user, then you will need to write and call a .REG fixup
  3428. ' routine before performing the registration below.
  3429. '
  3430. ' WARNING: Use of this functionality under Win32 is not recommended,
  3431. ' WARNING: because the application removal utility does not support
  3432. ' WARNING: undoing changes that occur as a result of calling
  3433. ' WARNING: REGEDIT on an arbitrary .REG file.
  3434. ' WARNING: Instead, it is recommended that you use the RegCreateKey(),
  3435. ' WARNING: RegOpenKey(), RegSetStringValue(), etc. functions in
  3436. ' WARNING: this module instead.  These make entries to the
  3437. ' WARNING: application removal logfile, thus enabling application
  3438. ' WARNING: removal to undo such changes.
  3439. '
  3440. ' IN: [strRegFile] - name of file containing reg. info
  3441. '-----------------------------------------------------------
  3442. '
  3443. Sub RegEdit(ByVal strRegFile As String)
  3444.     Const strREGEDIT$ = "REGEDIT /S "
  3445.  
  3446.     Dim fShellOK As Integer
  3447.  
  3448.     On Error Resume Next
  3449.  
  3450.     If FileExists(strRegFile) = True Then
  3451.         #If Win32 Then
  3452.             'Because regedit is a 16-bit application, it does not accept
  3453.             'double quotes around the filename.  Thus, if strRegFile
  3454.             'contains spaces, the only way to get this to work is to pass
  3455.             'regedit the short pathname version of the filename.
  3456.             strRegFile = GetShortPathName(strRegFile)
  3457.         #End If
  3458.         
  3459.         fShellOK = FSyncShell(strREGEDIT & strRegFile, 7)
  3460.         frmSetup1.Refresh
  3461.     Else
  3462.         MsgError ResolveResString(resCANTFINDREGFILE, "|1", strRegFile), vbExclamation Or vbOKOnly, gstrTitle
  3463.         ExitSetup frmSetup1, gintRET_FATAL
  3464.     End If
  3465.  
  3466.     Err = 0
  3467. End Sub
  3468.  
  3469. ' FUNCTION: RegEnumKey
  3470. '
  3471. ' Enumerates through the subkeys of an open registry
  3472. ' key (returns the "i"th subkey of hkey, if it exists)
  3473. '
  3474. ' Returns:
  3475. '   ERROR_SUCCESS on success.  strSubkeyName is set to the name of the subkey.
  3476. '   ERROR_NO_MORE_ITEMS if there are no more subkeys (32-bit only)
  3477. '   anything else - error
  3478. '
  3479. Function RegEnumKey(ByVal hkey As Long, ByVal i As Long, strKeyName As String) As Long
  3480.     Dim strResult As String
  3481.     
  3482.     strResult = String(300, " ")
  3483.     RegEnumKey = OSRegEnumKey(hkey, i, strResult, Len(strResult))
  3484.     strKeyName = StripTerminator(strResult)
  3485. End Function
  3486.  
  3487. '-----------------------------------------------------------
  3488. ' SUB: RegisterFiles
  3489. '
  3490. ' Loop through the list (array) of files to register that
  3491. ' was created in the CopySection function and register
  3492. ' each file therein as required
  3493. '
  3494. ' Notes: msRegInfo() array created by CopySection function
  3495. '-----------------------------------------------------------
  3496. '
  3497. Sub RegisterFiles()
  3498.     Const strEXT_EXE$ = "EXE"
  3499.  
  3500.     Dim intIdx As Integer
  3501.     Dim intLastIdx As Integer
  3502.     Dim strFileName As String
  3503.  
  3504.     On Error Resume Next
  3505.  
  3506.     '
  3507.     'Get number of items to register, if none then we can get out of here
  3508.     '
  3509.     intLastIdx = UBound(msRegInfo)
  3510.     If Err > 0 Then
  3511.         GoTo RFCleanup
  3512.     End If
  3513.  
  3514.     For intIdx = 0 To intLastIdx
  3515.         strFileName = msRegInfo(intIdx).strFileName
  3516.  
  3517.         Select Case msRegInfo(intIdx).strRegister
  3518.         Case mstrDLLSELFREGISTER
  3519.             Dim intDllSelfRegRet As Integer
  3520.             Dim intErrRes As Integer
  3521.             Const FAIL_OLE = 2
  3522.             Const FAIL_LOAD = 3
  3523.             Const FAIL_ENTRY = 4
  3524.             Const FAIL_REG = 5
  3525.             
  3526.             #If Win32 And LOGGING Then
  3527.                 NewAction gstrKEY_DLLSELFREGISTER, """" & strFileName & """"
  3528.             #End If
  3529.             
  3530. RetryDllSelfReg:
  3531.             Err = 0
  3532.             intErrRes = 0
  3533.             intDllSelfRegRet = DLLSelfRegister(strFileName)
  3534.             If Err Then
  3535.                 intErrRes = resCOMMON_CANTREGUNEXPECTED
  3536.             Else
  3537.                 Select Case intDllSelfRegRet
  3538.                 Case 0
  3539.                     'Good - everything's okay
  3540.                 Case FAIL_OLE
  3541.                     intErrRes = resCOMMON_CANTREGOLE
  3542.                 Case FAIL_LOAD
  3543.                     intErrRes = resCOMMON_CANTREGLOAD
  3544.                 Case FAIL_ENTRY
  3545.                     intErrRes = resCOMMON_CANTREGENTRY
  3546.                 Case FAIL_REG
  3547.                     intErrRes = resCOMMON_CANTREGREG
  3548.                 Case Else
  3549.                     intErrRes = resCOMMON_CANTREGUNEXPECTED
  3550.                 End Select
  3551.             End If
  3552.             
  3553.             If intErrRes Then
  3554.                 'There was some kind of error
  3555.                 
  3556.                 #If Win32 And LOGGING Then
  3557.                     'Log the more technical version of the error message -
  3558.                     'this would be too confusing to show to the end user
  3559.                     LogError ResolveResString(intErrRes, "|1", strFileName)
  3560.                 #End If
  3561.                 
  3562.                 'Now show a general error message to the user
  3563. AskWhatToDo:
  3564.                 Dim strMsg As String
  3565.                 
  3566.                 strMsg = ResolveResString(resCOMMON_CANTREG, "|1", strFileName)
  3567.                 #If 0 Then 'See vb4:11057
  3568.                 #If Win16 Then
  3569.                     If GetFileName(strFileName) = mstrAUTPRX Or GetFileName(strFileName) = mstrAUTPRX16 Then
  3570.                         strMsg = strMsg & LS$ & ResolveResString(resCOMMON_CANTREGAUTPRXRPC1) & "  " & ResolveResString(resCOMMON_CANTREGAUTPRXRPC2)
  3571.                     End If
  3572.                 #End If
  3573.                 #End If
  3574.                 
  3575.                 Select Case MsgError(strMsg, vbExclamation Or vbAbortRetryIgnore, gstrTitle)
  3576.                 Case vbAbort:
  3577.                     ExitSetup frmSetup1, gintRET_ABORT
  3578.                     GoTo AskWhatToDo
  3579.                 Case vbRetry:
  3580.                     GoTo RetryDllSelfReg
  3581.                 Case vbIgnore:
  3582.                     #If Win32 And LOGGING Then
  3583.                         AbortAction
  3584.                     #End If
  3585.                 End Select
  3586.             Else
  3587.                 #If Win32 And LOGGING Then
  3588.                     CommitAction
  3589.                 #End If
  3590.             End If
  3591.         Case mstrEXESELFREGISTER
  3592.             '
  3593.             'Only self register EXE files
  3594.             '
  3595.             If Extension(strFileName) = strEXT_EXE Then
  3596.                 #If Win32 And LOGGING Then
  3597.                     NewAction gstrKEY_EXESELFREGISTER, """" & strFileName & """"
  3598.                 #End If
  3599.                 Err = 0
  3600.                 ExeSelfRegister strFileName
  3601.                 #If Win32 And LOGGING Then
  3602.                     If Err Then
  3603.                         AbortAction
  3604.                     Else
  3605.                         CommitAction
  3606.                     End If
  3607.                 #End If
  3608.             End If
  3609.         Case mstrREMOTEREGISTER
  3610.             #If Win32 And LOGGING Then
  3611.                 NewAction gstrKEY_REMOTEREGISTER, """" & strFileName & """"
  3612.             #End If
  3613.             Err = 0
  3614.             RemoteRegister strFileName, msRegInfo(intIdx)
  3615.             #If Win32 And LOGGING Then
  3616.                 If Err Then
  3617.                     AbortAction
  3618.                 Else
  3619.                     CommitAction
  3620.                 End If
  3621.             #End If
  3622.         Case Else
  3623.             RegEdit msRegInfo(intIdx).strRegister
  3624.         End Select
  3625.     Next
  3626.  
  3627.     Erase msRegInfo
  3628.  
  3629. RFCleanup:
  3630.     Err = 0
  3631. End Sub
  3632.  
  3633. #If Win32 And LOGGING Then
  3634. '----------------------------------------------------------
  3635. ' SUB: RegisterAppRemovalEXE
  3636. '
  3637. ' Registers the application removal program (Windows 95 only)
  3638. ' or else places an icon for it in the application directory.
  3639. '
  3640. ' Returns True on success, False otherwise.
  3641. '----------------------------------------------------------
  3642. Function RegisterAppRemovalEXE(ByVal strAppRemovalEXE As String, ByVal strAppRemovalLog As String) As Boolean
  3643.     On Error GoTo Err
  3644.     
  3645.     Const strREGSTR_VAL_AppRemoval_DISPLAYNAME = "DisplayName"
  3646.     Const strREGSTR_VAL_AppRemoval_COMMANDLINE = "UninstallString"
  3647.     
  3648.     Dim strREGSTR_PATH_UNINSTALL As String
  3649.     strREGSTR_PATH_UNINSTALL = RegPathWinCurrentVersion() & "\Uninstall"
  3650.     
  3651.     'The command-line for the application removal executable is simply the path
  3652.     'for the installation logfile
  3653.     Dim strAppRemovalCmdLine As String
  3654.     strAppRemovalCmdLine = GetAppRemovalCmdLine(strAppRemovalEXE, strAppRemovalLog, APPREMERR_NONE)
  3655.         
  3656.     Dim iAppend As Integer
  3657.         
  3658.     If TreatAsWin95() Then
  3659.         'Create registry entries to tell Windows where the app removal executable is,
  3660.         '  how it should be displayed to the user, and what the command-line arguments are
  3661.         
  3662.         Dim fOK As Boolean
  3663.         Dim hkeyAppRemoval As Long
  3664.         Dim hkeyOurs As Long
  3665.         Dim i As Integer
  3666.         
  3667.         'Go ahead and create a key to the main Uninstall branch
  3668.         If Not RegCreateKey(HKEY_LOCAL_MACHINE, strREGSTR_PATH_UNINSTALL, "", hkeyAppRemoval) Then
  3669.             GoTo Err
  3670.         End If
  3671.         
  3672.         'We need a unique key.  This key is never shown to the end user.  We will use a key of
  3673.         'the form 'ST4UNST #xxx'
  3674.         Dim strAppRemovalKey As String
  3675.         Dim strAppRemovalKeyBase As String
  3676.         Dim hkeyTest As Long
  3677.         strAppRemovalKeyBase = mstrFILE_APPREMOVALLOGBASE$ & " #"
  3678.         iAppend = 1
  3679.         
  3680.         Do
  3681.             strAppRemovalKey = strAppRemovalKeyBase & Format(iAppend)
  3682.             If RegOpenKey(hkeyAppRemoval, strAppRemovalKey, hkeyTest) Then
  3683.                 'This key already exists.  But we need a unique key.
  3684.                 RegCloseKey hkeyTest
  3685.             Else
  3686.                 'We've found a key that doesn't already exist.  Use it.
  3687.                 Exit Do
  3688.             End If
  3689.             
  3690.             iAppend = iAppend + 1
  3691.         Loop
  3692.         
  3693.     
  3694.         'We also need a unique displayname.  This name is
  3695.         'the only means the user has to identify the application
  3696.         'to remove
  3697.         Dim strDisplayName As String
  3698.         strDisplayName = gstrAppName 'First try... Application name
  3699.         If Not IsDisplayNameUnique(hkeyAppRemoval, strDisplayName) Then
  3700.             'Second try... Add path
  3701.             strDisplayName = strDisplayName & " (" & gstrDestDir & ")"
  3702.             If Not IsDisplayNameUnique(hkeyAppRemoval, strDisplayName) Then
  3703.                 'Subsequent tries... Append a unique integer
  3704.                 Dim strDisplayNameBase As String
  3705.                 
  3706.                 strDisplayNameBase = strDisplayName
  3707.                 iAppend = 3
  3708.                 Do
  3709.                     strDisplayName = strDisplayNameBase & " #" & Format(iAppend)
  3710.                     If IsDisplayNameUnique(hkeyAppRemoval, strDisplayName) Then
  3711.                         Exit Do
  3712.                     Else
  3713.                         iAppend = iAppend + 1
  3714.                     End If
  3715.                 Loop
  3716.             End If
  3717.         End If
  3718.         
  3719.         'Go ahead and fill in entries for the app removal executable
  3720.         If Not RegCreateKey(hkeyAppRemoval, strAppRemovalKey, "", hkeyOurs) Then
  3721.             GoTo Err
  3722.         End If
  3723.         If Not RegSetStringValue(hkeyOurs, strREGSTR_VAL_AppRemoval_DISPLAYNAME, strDisplayName, False) Then
  3724.             GoTo Err
  3725.         End If
  3726.         If Not RegSetStringValue(hkeyOurs, strREGSTR_VAL_AppRemoval_COMMANDLINE, strAppRemovalCmdLine, False) Then
  3727.             GoTo Err
  3728.         End If
  3729.     Else
  3730.         ' Under NT, we simply place an icon to the app removal EXE in the program manager
  3731.         If fMainGroupWasCreated Then
  3732.             CreateProgManItem frmSetup1, strAppRemovalCmdLine, ResolveResString(resAPPREMOVALICONNAME, "|1", gstrAppName)
  3733.         Else
  3734.             'If you get this message, it means that you incorrectly customized Form_Load().
  3735.             'Under 32-bits and NT 3.51, a Program Manager group must always be created.
  3736.             MsgError ResolveResString(resNOFOLDERFORICON, "|1", strAppRemovalEXE), MB_OK Or MB_ICONEXCLAMATION, gstrTitle
  3737.             ExitSetup frmSetup1, gintRET_FATAL
  3738.         End If
  3739.     End If
  3740.     
  3741.     RegCloseKey hkeyAppRemoval
  3742.     RegCloseKey hkeyOurs
  3743.     
  3744.     RegisterAppRemovalEXE = True
  3745.     Exit Function
  3746.     
  3747. Err:
  3748.     If hkeyOurs Then
  3749.         RegCloseKey hkeyOurs
  3750.         RegDeleteKey hkeyAppRemoval, strAppRemovalKey
  3751.     End If
  3752.     If hkeyAppRemoval Then
  3753.         RegCloseKey hkeyAppRemoval
  3754.     End If
  3755.     
  3756.     RegisterAppRemovalEXE = False
  3757.     Exit Function
  3758. End Function
  3759. #End If
  3760.  
  3761. '-----------------------------------------------------------
  3762. ' FUNCTION: RegOpenKey
  3763. '
  3764. ' Opens an existing key in the system registry.
  3765. '
  3766. ' Returns: True if the key was opened OK, False otherwise
  3767. '   Upon success, phkResult is set to the handle of the key.
  3768. '-----------------------------------------------------------
  3769. '
  3770. Function RegOpenKey(ByVal hkey As Long, ByVal lpszSubKey As String, phkResult As Long) As Boolean
  3771.     Dim lResult As Long
  3772.     #If Win32 Then
  3773.     Dim strHkey As String
  3774.     #End If
  3775.  
  3776.     On Error GoTo 0
  3777.  
  3778.     #If Win32 Then
  3779.         strHkey = strGetHKEYString(hkey)
  3780.     #End If
  3781.  
  3782.     lResult = OSRegOpenKey(hkey, lpszSubKey, phkResult)
  3783.     If lResult = ERROR_SUCCESS Then
  3784.         RegOpenKey = True
  3785.         #If Win32 And LOGGING Then
  3786.         AddHkeyToCache phkResult, strHkey & "\" & lpszSubKey
  3787.         #End If
  3788.     Else
  3789.         RegOpenKey = False
  3790.     End If
  3791. End Function
  3792.  
  3793. #If Win32 And LOGGING Then
  3794. '----------------------------------------------------------
  3795. ' FUNCTION: RegPathWinCurrentVersion
  3796. '
  3797. ' Returns the name of the registry key
  3798. ' "\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion"
  3799. '----------------------------------------------------------
  3800. Function RegPathWinCurrentVersion() As String
  3801.     RegPathWinCurrentVersion = "SOFTWARE\Microsoft\Windows\CurrentVersion"
  3802. End Function
  3803. #End If
  3804.  
  3805. '----------------------------------------------------------
  3806. ' FUNCTION: RegQueryIntValue
  3807. '
  3808. ' Retrieves the integer data for a named
  3809. ' (strValueName = name) or unnamed (strValueName = "")
  3810. ' value within a registry key.  If the named value
  3811. ' exists, but its data is not a REG_DWORD, this function
  3812. ' fails.
  3813. '
  3814. ' NOTE: There is no 16-bit version of this function.
  3815. '
  3816. ' Returns: True on success, else False.
  3817. '   On success, lData is set to the numeric data value
  3818. '
  3819. '----------------------------------------------------------
  3820. #If Win32 Then
  3821. Function RegQueryNumericValue(ByVal hkey As Long, ByVal strValueName As String, lData As Long) As Boolean
  3822.     Dim lResult As Long
  3823.     Dim lValueType As Long
  3824.     Dim lBuf As Long
  3825.     Dim lDataBufSize As Long
  3826.     
  3827.     RegQueryNumericValue = False
  3828.     
  3829.     On Error GoTo 0
  3830.     
  3831.     ' Get length/data type
  3832.     lDataBufSize = 4
  3833.         
  3834.     lResult = OSRegQueryValueEx(hkey, strValueName, 0&, lValueType, lBuf, lDataBufSize)
  3835.     If lResult = ERROR_SUCCESS Then
  3836.         If lValueType = REG_DWORD Then
  3837.             lData = lBuf
  3838.             RegQueryNumericValue = True
  3839.         End If
  3840.     End If
  3841. End Function
  3842. #End If
  3843.  
  3844. ' FUNCTION: RegQueryStringValue
  3845. '
  3846. ' Retrieves the string data for a named
  3847. ' (strValueName = name) or unnamed (strValueName = "")
  3848. ' value within a registry key.  If the named value
  3849. ' exists, but its data is not a string, this function
  3850. ' fails.
  3851. '
  3852. ' NOTE: For 16-bits, strValueName MUST be "" (but the
  3853. ' NOTE: parameter is left in for source code compatability)
  3854. '
  3855. ' Returns: True on success, else False.
  3856. '   On success, strData is set to the string data value
  3857. '
  3858. Function RegQueryStringValue(ByVal hkey As Long, ByVal strValueName As String, strData As String) As Boolean
  3859. #If Win32 Then
  3860.     Dim lResult As Long
  3861.     Dim lValueType As Long
  3862.     Dim strBuf As String
  3863.     Dim lDataBufSize As Long
  3864.     
  3865.     RegQueryStringValue = False
  3866.     On Error GoTo 0
  3867.     ' Get length/data type
  3868.     lResult = OSRegQueryValueEx(hkey, strValueName, 0&, lValueType, ByVal 0&, lDataBufSize)
  3869.     If lResult = ERROR_SUCCESS Then
  3870.         If lValueType = REG_SZ Then
  3871.             strBuf = String(lDataBufSize, " ")
  3872.             lResult = OSRegQueryValueEx(hkey, strValueName, 0&, 0&, ByVal strBuf, lDataBufSize)
  3873.             If lResult = ERROR_SUCCESS Then
  3874.                 RegQueryStringValue = True
  3875.                 strData = StripTerminator(strBuf)
  3876.             End If
  3877.         End If
  3878.     End If
  3879. #Else
  3880. '16-bit
  3881.     Dim lResult As Long
  3882.     Dim lValueType As Long
  3883.     Dim strBuf As String
  3884.     Dim lDataBufSize As Long
  3885.  
  3886.     RegQueryStringValue = False
  3887.     
  3888.     If strValueName <> "" Then
  3889.         'Under 16-bits, strValueName MUST be ""
  3890.         Exit Function
  3891.     End If
  3892.     
  3893.     On Error GoTo 0
  3894.     lDataBufSize = 500
  3895.     strBuf = String(lDataBufSize, " ")
  3896.     lResult = OSRegQueryValue(hkey, "", strBuf, lDataBufSize)
  3897.     If lResult = ERROR_SUCCESS Then
  3898.         RegQueryStringValue = True
  3899.         strData = StripTerminator(strBuf)
  3900.     End If
  3901. #End If
  3902. End Function
  3903.  
  3904. '----------------------------------------------------------
  3905. ' FUNCTION: RegQueryRefCount
  3906. '
  3907. ' Retrieves the data inteded as a reference count for a
  3908. ' particular value within a registry key.  Although
  3909. ' REG_DWORD is the preferred way of storing reference
  3910. ' counts, it is possible that some installation programs
  3911. ' may incorrect use a string or binary value instead.
  3912. ' This routine accepts the data whether it is a string,
  3913. ' a binary value or a DWORD (Long).
  3914. '
  3915. ' NOTE: There is no 16-bit version of this function.
  3916. '
  3917. ' Returns: True on success, else False.
  3918. '   On success, lrefcount is set to the numeric data value
  3919. '
  3920. '----------------------------------------------------------
  3921. #If Win32 Then
  3922. Function RegQueryRefCount(ByVal hkey As Long, ByVal strValueName As String, lRefCount As Long) As Boolean
  3923.     Dim lResult As Long
  3924.     Dim lValueType As Long
  3925.     Dim lBuf As Long
  3926.     Dim lDataBufSize As Long
  3927.  
  3928.     RegQueryRefCount = False
  3929.  
  3930.     On Error GoTo 0
  3931.  
  3932.     ' Get length/data type
  3933.     lDataBufSize = 4
  3934.  
  3935.     lResult = OSRegQueryValueEx(hkey, strValueName, 0&, lValueType, lBuf, lDataBufSize)
  3936.     If lResult = ERROR_SUCCESS Then
  3937.         Select Case lValueType
  3938.         Case REG_DWORD
  3939.             lRefCount = lBuf
  3940.             RegQueryRefCount = True
  3941.         Case REG_BINARY
  3942.             If lDataBufSize = 4 Then
  3943.                 lRefCount = lBuf
  3944.                 RegQueryRefCount = True
  3945.             End If
  3946.         Case REG_SZ
  3947.             Dim strRefCount As String
  3948.             
  3949.             If RegQueryStringValue(hkey, strValueName, strRefCount) Then
  3950.                 lRefCount = Val(strRefCount)
  3951.                 RegQueryRefCount = True
  3952.             End If
  3953.         End Select
  3954.     End If
  3955. End Function
  3956. #End If
  3957.  
  3958. ' FUNCTION: RegSetNumericValue
  3959. '
  3960. ' Associates a named (strValueName = name) or unnamed (strValueName = "")
  3961. '   value with a registry key.
  3962. '
  3963. ' If fLog is missing or is True, then this action is logged in the logfile,
  3964. ' and the value will be deleted by the application removal utility if the
  3965. ' user choose to remove the installed application.
  3966. '
  3967. ' NOTE: There is no 16-bit version of this function.
  3968. '
  3969. ' Returns: True on success, else False.
  3970. '
  3971. #If Win32 Then
  3972. Function RegSetNumericValue(ByVal hkey As Long, ByVal strValueName As String, ByVal lData As Long, Optional ByVal fLog) As Boolean
  3973.     Dim lResult As Long
  3974.     Dim strHkey As String
  3975.  
  3976.     On Error GoTo 0
  3977.     
  3978.     If IsMissing(fLog) Then fLog = True
  3979.  
  3980.     strHkey = strGetHKEYString(hkey)
  3981.     
  3982.     If fLog Then
  3983.         NewAction _
  3984.           gstrKEY_REGVALUE, _
  3985.           """" & strHkey & """" _
  3986.             & ", " & """" & strValueName & """"
  3987.     End If
  3988.  
  3989.     lResult = OSRegSetValueEx(hkey, strValueName, 0&, REG_DWORD, lData, 4)
  3990.     If lResult = ERROR_SUCCESS Then
  3991.         RegSetNumericValue = True
  3992.         If fLog Then
  3993.             CommitAction
  3994.         End If
  3995.     Else
  3996.         RegSetNumericValue = False
  3997.         MsgError ResolveResString(resERR_REG), vbOKOnly Or vbExclamation, gstrTitle
  3998.         If fLog Then
  3999.             AbortAction
  4000.         End If
  4001.     End If
  4002. End Function
  4003. #End If
  4004.  
  4005. ' FUNCTION: RegSetStringValue
  4006. '
  4007. ' Associates a named (strValueName = name) or unnamed (strValueName = "")
  4008. '   value with a registry key.
  4009. '
  4010. ' If fLog is missing or is True, then this action is logged in the
  4011. ' logfile, and the value will be deleted by the application removal
  4012. ' utility if the user choose to remove the installed application.
  4013. '
  4014. ' NOTE: For 16-bits, strValueName MUST be "" (but the
  4015. ' NOTE: parameter is left in for source code compatability)
  4016. '
  4017. ' NOTE: Under 16-bits, fLog is ignored.
  4018. '
  4019. ' Returns: True on success, else False.
  4020. '
  4021. Function RegSetStringValue(ByVal hkey As Long, ByVal strValueName As String, ByVal strData As String, Optional ByVal fLog) As Boolean
  4022.     Dim lResult As Long
  4023.     #If Win32 Then
  4024.     Dim strHkey As String
  4025.     #End If
  4026.     
  4027.     On Error GoTo 0
  4028.     
  4029.     If IsMissing(fLog) Then fLog = True
  4030.  
  4031.     If hkey = 0 Then
  4032.         Exit Function
  4033.     End If
  4034.     
  4035.     #If Win32 Then
  4036.         strHkey = strGetHKEYString(hkey)
  4037.     
  4038.         If fLog Then
  4039.             NewAction _
  4040.               gstrKEY_REGVALUE, _
  4041.               """" & strHkey & """" _
  4042.                 & ", " & """" & strValueName & """"
  4043.         End If
  4044.     #Else 'Win16
  4045.         If strValueName <> "" Then
  4046.             'Under 16-bits, strValueName MUST be ""
  4047.             RegSetStringValue = False
  4048.             Exit Function
  4049.         End If
  4050.     #End If
  4051.  
  4052.     #If Win32 Then
  4053.         lResult = OSRegSetValueEx(hkey, strValueName, 0&, REG_SZ, ByVal strData, Len(strData) + 1)
  4054.     #Else
  4055.         lResult = OSRegSetValue(hkey, "", REG_SZ, strData, Len(strData) + 1)
  4056.     #End If
  4057.     
  4058.     If lResult = ERROR_SUCCESS Then
  4059.         RegSetStringValue = True
  4060.         #If Win32 Then
  4061.             If fLog Then
  4062.                 CommitAction
  4063.             End If
  4064.         #End If
  4065.     Else
  4066.         RegSetStringValue = False
  4067.         MsgError ResolveResString(resERR_REG), vbOKOnly Or vbExclamation, gstrTitle
  4068.         #If Win32 Then
  4069.             If fLog Then
  4070.                 AbortAction
  4071.             End If
  4072.         #End If
  4073.     End If
  4074. End Function
  4075.  
  4076. '-----------------------------------------------------------
  4077. ' SUB: RemoteRegister
  4078. '
  4079. ' Synchronously run the client registration utility on the
  4080. ' given remote server registration file in order to set it
  4081. ' up properly in the registry.
  4082. '
  4083. ' IN: [strFileName] - .EXE file to register
  4084. '-----------------------------------------------------------
  4085. '
  4086. Sub RemoteRegister(ByVal strFileName As String, rInfo As REGINFO)
  4087.     #If Win32 Then
  4088.         Const strClientRegistrationUtility$ = "CLIREG32.EXE"
  4089.     #Else
  4090.         Const strClientRegistrationUtility$ = "CLIREG16.EXE"
  4091.     #End If
  4092.     Const strAddressSwitch = " /s "
  4093.     Const strProtocolSwitch = " /p "
  4094.     Const strNoLogoSwitch = " /nologo "
  4095.     Const strAuthenticationSwitch = " /a "
  4096.     Const strTypelibSwitch = " /t "
  4097.     Const strEXT_REMOTE$ = "VBR"
  4098.     Const strEXT_REMOTETLB$ = "TLB"
  4099.  
  4100.     Dim strAddress As String
  4101.     Dim strProtocol As String
  4102.     Dim intAuthentication As Integer
  4103.     Dim strCmdLine As String
  4104.     Dim fShell As Integer
  4105.     Dim strMatchingTLB As String
  4106.  
  4107.     'Find the name of the matching typelib file.  This should have already
  4108.     'been installed to the same directory as the .VBR file.
  4109.     strMatchingTLB = strFileName
  4110.     If Right$(strMatchingTLB, Len(strEXT_REMOTE)) = strEXT_REMOTE Then
  4111.         strMatchingTLB = Left$(strMatchingTLB, Len(strMatchingTLB) - Len(strEXT_REMOTE))
  4112.     End If
  4113.     strMatchingTLB = strMatchingTLB & strEXT_REMOTETLB
  4114.  
  4115.     strAddress = rInfo.strNetworkAddress
  4116.     strProtocol = rInfo.strNetworkProtocol
  4117.     intAuthentication = rInfo.intAuthentication
  4118.     frmRemoteServerDetails.GetServerDetails strFileName, strAddress, strProtocol
  4119.     frmMessage.Refresh
  4120.     strCmdLine = _
  4121.       strClientRegistrationUtility _
  4122.       & strAddressSwitch & """" & strAddress & """" _
  4123.       & strProtocolSwitch & strProtocol _
  4124.       & strAuthenticationSwitch & Format$(intAuthentication) & " " _
  4125.       & strNoLogoSwitch _
  4126.       & strTypelibSwitch & """" & strMatchingTLB & """" & " " _
  4127.       & """" & strFileName & """"
  4128.       
  4129.     '
  4130.     'Synchronously shell out and run the utility with the correct switches
  4131.     '
  4132.     fShell = FSyncShell(strCmdLine, vbNormal)
  4133.     If Not fShell Then
  4134.         MsgError ResolveResString(resCANTRUNCLIREG, "|1", strClientRegistrationUtility), vbOKOnly Or vbExclamation, gintRET_FATAL
  4135.         ExitSetup frmSetup1, gintRET_FATAL
  4136.     End If
  4137. End Sub
  4138.  
  4139. '-----------------------------------------------------------
  4140. ' SUB: RemoveShellLink
  4141. '
  4142. ' Removes a link in either Start>Programs or any of its
  4143. ' immediate subfolders in the Windows 95 shell.
  4144. '
  4145. ' IN: [strFolderName] - text name of the immediate folder
  4146. '                       in which the link to be removed
  4147. '                       currently exists, or else the
  4148. '                       empty string ("") to indicate that
  4149. '                       the link can be found directly in
  4150. '                       the Start>Programs menu.
  4151. '     [strLinkName] - text caption for the link
  4152. '
  4153. ' This action is never logged in the app removal logfile.
  4154. '
  4155. ' PRECONDITION: strFolderName has already been created and is
  4156. '               an immediate subfolder of Start>Programs, if it
  4157. '               is not equal to ""
  4158. '-----------------------------------------------------------
  4159. '
  4160. #If Win32 And LOGGING Then
  4161. Sub RemoveShellLink(ByVal strFolderName As String, ByVal strLinkName As String)
  4162.     Dim fSuccess As Boolean
  4163.     
  4164.     ReplaceDoubleQuotes strFolderName
  4165.     ReplaceDoubleQuotes strLinkName
  4166.     
  4167.     fSuccess = OSfRemoveShellLink(strFolderName, strLinkName)
  4168. End Sub
  4169. #End If
  4170.  
  4171. '-----------------------------------------------------------
  4172. ' FUNCTION: ResolveDestDir
  4173. '
  4174. ' Given a destination directory string, equate any macro
  4175. ' portions of the string to their runtime determined
  4176. ' actual locations and return a string reflecting the
  4177. ' actual path.
  4178. '
  4179. ' IN: [strDestDir] - string containing directory macro info
  4180. '                    and/or actual dir path info
  4181. '
  4182. ' Return: A string containing the resolved dir name
  4183. '-----------------------------------------------------------
  4184. '
  4185. Function ResolveDestDir(ByVal strDestDir As String) As String
  4186.     Const strMACROSTART$ = "$("
  4187.     Const strMACROEND$ = ")"
  4188.  
  4189.     Dim intPos As Integer
  4190.     Dim strResolved As String
  4191.     #If Win32 Then
  4192.     Dim hkey As Long
  4193.     Dim strPathsKey As String
  4194.     strPathsKey = RegPathWinCurrentVersion()
  4195.     #End If
  4196.  
  4197.     'We take the first part of destdir, and if its $( then we need to get the portion
  4198.     'of destdir up to and including the last paren.  We then test against this for
  4199.     'macro expansion.  If no ) is found after finding $(, then must assume that it's
  4200.     'just a normal file name and do no processing.  Only enter the case statement
  4201.     'if intPos > 0.
  4202.  
  4203.     If Left$(strDestDir, 2) = strMACROSTART Then
  4204.         intPos = InStr(strDestDir, strMACROEND)
  4205.  
  4206.         Select Case Left$(strDestDir, intPos)
  4207.         Case gstrAPPDEST
  4208.             If gstrDestDir <> gstrNULL Then
  4209.                 strResolved = gstrDestDir
  4210.             Else
  4211.                 strResolved = "?"
  4212.             End If
  4213.         Case gstrWINDEST
  4214.             strResolved = gstrWinDir
  4215.         Case gstrWINSYSDEST, gstrWINSYSDESTSYSFILE
  4216.             strResolved = gstrWinSysDir
  4217.         Case gstrPROGRAMFILES
  4218.             #If Win32 And LOGGING Then
  4219.                 If TreatAsWin95() Then
  4220.                     Const strProgramFilesKey = "ProgramFilesDir"
  4221.  
  4222.                     If RegOpenKey(HKEY_LOCAL_MACHINE, strPathsKey, hkey) Then
  4223.                         RegQueryStringValue hkey, strProgramFilesKey, strResolved
  4224.                         RegCloseKey hkey
  4225.                     End If
  4226.                 End If
  4227.             #End If
  4228.  
  4229.             If strResolved = "" Then
  4230.                 'If not otherwise set, let strResolved be the root of the first fixed disk
  4231.                 strResolved = strRootDrive()
  4232.             End If
  4233.         Case gstrCOMMONFILES
  4234.             'First determine the correct path of Program Files\Common Files, if under Win95
  4235.             strResolved = strGetCommonFilesPath()
  4236.             If strResolved = "" Then
  4237.                 'If not otherwise set, let strResolved be the Windows directory
  4238.                 strResolved = gstrWinDir
  4239.             End If
  4240.         Case gstrCOMMONFILESSYS
  4241.             'First determine the correct path of Program Files\Common Files, if under Win95
  4242.             Dim strCommonFiles As String
  4243.             
  4244.             strCommonFiles = strGetCommonFilesPath()
  4245.             If strCommonFiles <> "" Then
  4246.                 'Okay, now just add \System, and we're done
  4247.                 strResolved = strCommonFiles & "System\"
  4248.             Else
  4249.                 'If Common Files isn't in the registry, then map the
  4250.                 'entire macro to the Windows\{system,system32} directory
  4251.                 strResolved = gstrWinSysDir
  4252.             End If
  4253.         Case gstrDAODEST
  4254.             strResolved = strGetDAOPath()
  4255.         Case Else
  4256.             intPos = 0
  4257.         End Select
  4258.     End If
  4259.     
  4260.     AddDirSep strResolved
  4261.  
  4262.     If intPos = 0 Then
  4263.         '
  4264.         'if no drive spec, and doesn't begin with any root path indicator ("\"),
  4265.         'then we assume that this destination is relative to the app dest dir
  4266.         '
  4267.         If Mid$(strDestDir, 2, 1) <> gstrCOLON Then
  4268.             If Left$(strDestDir, 1) <> gstrSEP_DIR Then
  4269.                 strResolved = gstrDestDir
  4270.             End If
  4271.         End If
  4272.     Else
  4273.         If Mid$(strDestDir, intPos + 1, 1) = gstrSEP_DIR Then
  4274.             intPos = intPos + 1
  4275.         End If
  4276.     End If
  4277.  
  4278.     ResolveDestDir = strResolved & Mid$(strDestDir, intPos + 1)
  4279. End Function
  4280.  
  4281. '-----------------------------------------------------------
  4282. ' FUNCTION: ResolveDir
  4283. '
  4284. ' Given a pathname, resolve it to its smallest form.  If
  4285. ' the pathname is invalid, then optionally warn the user.
  4286. '
  4287. ' IN: [strPathName] - pathname to resolve
  4288. '     [fMustExist] - enforce that the path actually exists
  4289. '     [fWarn] - If True, warn user upon invalid path
  4290. '
  4291. ' Return: A string containing the resolved dir name
  4292. '-----------------------------------------------------------
  4293. '
  4294. Function ResolveDir(ByVal strPathName As String, fMustExist As Integer, fWarn As Integer) As String
  4295.     Const OF_PARSE% = &H100
  4296.     Const HFILE_ERROR% = -1
  4297.  
  4298.     Dim sOFS As OFSTRUCT
  4299.     Dim strMsg As String
  4300.     Dim fInValid As Integer
  4301.     Dim strResolvedPath As String
  4302.  
  4303.     On Error Resume Next
  4304.  
  4305.     '
  4306.     'If the pathname is a UNC name (16-bit only), or if it's in actuality a file name, then it's invalid
  4307.     '
  4308.     #If Win16 Then
  4309.     If IsUNCName(strPathName) = True Then
  4310.         fInValid = True
  4311.         GoTo RDContinue
  4312.     End If
  4313.     #End If
  4314.     If FileExists(strPathName) = True Then
  4315.         fInValid = True
  4316.         GoTo RDContinue
  4317.     End If
  4318.  
  4319.     strResolvedPath = strPathName
  4320.  
  4321.     If InStr(3, strResolvedPath, gstrSEP_DIR) > 0 Then
  4322.         '
  4323.         'temporarily remove any trailing dir sep of OpenFile will always fail
  4324.         '
  4325.         If Right$(strResolvedPath, 1) = gstrSEP_DIR Then
  4326.             strResolvedPath = Left$(strResolvedPath, Len(strResolvedPath) - 1)
  4327.         End If
  4328.  
  4329.         '
  4330.         'The Windows API OpenFile actually does all of the work of resolving the
  4331.         'file name, i.e.; paths like "C:\.\TEMP\..\TEMP\.\.." are resolved to "C:\"
  4332.         'and so on
  4333.         '
  4334.         If OpenFile(strResolvedPath, sOFS, OF_PARSE) = HFILE_ERROR Then
  4335.             ChDir strResolvedPath
  4336.             AddDirSep strResolvedPath
  4337.             If Err > 0 Then
  4338.                 Err = 0
  4339.                 ChDir strResolvedPath
  4340.                 If Err > 0 Then
  4341.                     fInValid = True
  4342.                 End If
  4343.             End If
  4344.         Else
  4345.             '
  4346.             'Remove any terminator and ensure that the drive specified is valid
  4347.             'and available
  4348.             '
  4349.             strResolvedPath = StripTerminator(sOFS.szPathName)
  4350.             
  4351.             If CheckDrive(strResolvedPath, gstrTitle) = False Then
  4352.                 fInValid = True
  4353.             Else
  4354.                 AddDirSep strResolvedPath
  4355.                 If fMustExist = True Then
  4356.                     Err = 0
  4357.                     
  4358.                     Dim strDummy As String
  4359.                     strDummy = Dir$(strResolvedPath & "*.*")
  4360.                     
  4361.                     If Err > 0 Then
  4362.                         strMsg = ResolveResString(resNOTEXIST) & LS$
  4363.                         fInValid = True
  4364.                     End If
  4365.                 End If
  4366.             End If
  4367.         End If
  4368.     Else
  4369.         fInValid = True
  4370.     End If
  4371.  
  4372. RDContinue:
  4373.     If fInValid = True Then
  4374.         If fWarn = True Then
  4375.             strMsg = strMsg & ResolveResString(resDIRSPECIFIED) & LS$ & strPathName & LS$
  4376.             strMsg = strMsg & ResolveResString(resDIRINVALID)
  4377.             MsgError strMsg, MB_OK Or MB_ICONEXCLAMATION, ResolveResString(resDIRINVNAME)
  4378.         End If
  4379.  
  4380.         ResolveDir = gstrNULL
  4381.     Else
  4382.         ResolveDir = strResolvedPath
  4383.     End If
  4384.  
  4385.     Err = 0
  4386. End Function
  4387.  
  4388. '-----------------------------------------------------------
  4389. ' SUB: RestoreProgMan
  4390. '
  4391. ' Restores Windows Program Manager
  4392. '-----------------------------------------------------------
  4393. '
  4394. Sub RestoreProgMan()
  4395.     Const strPMTITLE$ = "Program Manager"
  4396.  
  4397.     On Error Resume Next
  4398.  
  4399.     'Try the localized name first
  4400.     AppActivate ResolveResString(resPROGRAMMANAGER)
  4401.     
  4402.     If Err Then
  4403.         'If that doesn't work, try the English name
  4404.         AppActivate strPMTITLE
  4405.     End If
  4406.  
  4407.     Err = 0
  4408. End Sub
  4409.  
  4410. '-----------------------------------------------------------
  4411. ' FUNCTION: SetFileDateTime
  4412. '
  4413. ' Set the Destination File's date and time to the Source file's date and time
  4414. '
  4415. ' IN: [strFileGetTime] - file to get time/date info from
  4416. '     [strFileSetTime] - file to set time/date info for
  4417. '
  4418. ' Returns: True if set date/time successful, False otherwise
  4419. '-----------------------------------------------------------
  4420. '
  4421. Function SetFileDateTime(strFileGetTime As String, strFileSetTime As String) As Integer
  4422.     SetFileDateTime = IIf(SetTime(strFileGetTime, strFileSetTime) = -1, False, True)
  4423. End Function
  4424.  
  4425. '-----------------------------------------------------------
  4426. ' SUB: ShowPathDialog
  4427. '
  4428. ' Display form to allow user to get either a source or
  4429. ' destination path
  4430. '
  4431. ' IN: [strPathRequest] - determines whether to ask for the
  4432. '                        source or destination pathname.
  4433. '                        gstrDIR_SRC for source path
  4434. '                        gstrDIR_DEST for destination path
  4435. '-----------------------------------------------------------
  4436. '
  4437. Sub ShowPathDialog(ByVal strPathRequest As String)
  4438.     frmSetup1.Tag = strPathRequest
  4439.  
  4440.     '
  4441.     'frmPath.Form_Load() reads frmSetup1.Tag to determine whether
  4442.     'this is a request for the source or destination path
  4443.     '
  4444.     frmPath.Show 1
  4445.  
  4446.     If strPathRequest = gstrDIR_SRC Then
  4447.         gstrSrcPath = frmSetup1.Tag
  4448.     Else
  4449.         If gfRetVal = gintRET_CONT Then
  4450.             gstrDestDir = frmSetup1.Tag
  4451.         End If
  4452.     End If
  4453. End Sub
  4454.  
  4455. '-----------------------------------------------------------
  4456. ' FUNCTION: strExtractFilenameArg
  4457. '
  4458. ' Extracts a quoted or unquoted filename from a string
  4459. '   containing command-line arguments
  4460. '
  4461. ' IN: [str] - string containing a filename.  This filename
  4462. '             begins at the first character, and continues
  4463. '             to the end of the string or to the first space
  4464. '             or switch character, or, if the string begins
  4465. '             with a double quote, continues until the next
  4466. '             double quote
  4467. ' OUT: Returns the filename, without quotes
  4468. '      str is set to be the remainder of the string after
  4469. '      the filename and quote (if any)
  4470. '
  4471. '-----------------------------------------------------------
  4472. '
  4473. Function strExtractFilenameArg(str As String, fErr As Boolean)
  4474.     Dim strFileName As String
  4475.     
  4476.     str = Trim$(str)
  4477.     
  4478.     Dim iEndFilenamePos As Integer
  4479.     If Left$(str, 1) = """" Then
  4480.         ' Filenames is surrounded by quotes
  4481.         iEndFilenamePos = InStr(2, str, """") ' Find matching quote
  4482.         If iEndFilenamePos > 0 Then
  4483.             strFileName = Mid$(str, 2, iEndFilenamePos - 2)
  4484.             str = Right$(str, Len(str) - iEndFilenamePos)
  4485.         Else
  4486.             fErr = True
  4487.             Exit Function
  4488.         End If
  4489.     Else
  4490.         ' Filename continues until next switch or space or quote
  4491.         Dim iSpacePos As Integer
  4492.         Dim iSwitch1 As Integer
  4493.         Dim iSwitch2 As Integer
  4494.         Dim iQuote As Integer
  4495.         
  4496.         iSpacePos = InStr(str, " ")
  4497.         iSwitch1 = InStr(str, gstrSwitchPrefix1)
  4498.         iSwitch2 = InStr(str, gstrSwitchPrefix2)
  4499.         iQuote = InStr(str, """")
  4500.         
  4501.         If iSpacePos = 0 Then iSpacePos = Len(str) + 1
  4502.         If iSwitch1 = 0 Then iSwitch1 = Len(str) + 1
  4503.         If iSwitch2 = 0 Then iSwitch2 = Len(str) + 1
  4504.         If iQuote = 0 Then iQuote = Len(str) + 1
  4505.         
  4506.         iEndFilenamePos = iSpacePos
  4507.         If iSwitch1 < iEndFilenamePos Then iEndFilenamePos = iSwitch1
  4508.         If iSwitch2 < iEndFilenamePos Then iEndFilenamePos = iSwitch2
  4509.         If iQuote < iEndFilenamePos Then iEndFilenamePos = iQuote
  4510.         
  4511.         strFileName = Left$(str, iEndFilenamePos - 1)
  4512.         If iEndFilenamePos > Len(str) Then
  4513.             str = ""
  4514.         Else
  4515.             str = Right(str, Len(str) - iEndFilenamePos + 1)
  4516.         End If
  4517.     End If
  4518.     
  4519.     strFileName = Trim$(strFileName)
  4520.     If strFileName = "" Then
  4521.         fErr = True
  4522.         Exit Function
  4523.     End If
  4524.     
  4525.     fErr = False
  4526.     strExtractFilenameArg = strFileName
  4527.     str = Trim$(str)
  4528. End Function
  4529.  
  4530. '-----------------------------------------------------------
  4531. ' FUNCTION: strStripQuotes
  4532. '
  4533. ' Removes double quotes from the beginning and ending of a
  4534. ' string, if they are present
  4535. '-----------------------------------------------------------
  4536. '
  4537. Function strStripQuotes(ByVal str As String) As String
  4538.     If Left$(str, 1) = """" Then
  4539.         str = Mid$(str, 2)
  4540.     End If
  4541.     If Right$(str, 1) = """" Then
  4542.         str = Mid$(str, 1, Len(str) - 1)
  4543.     End If
  4544.     
  4545.     strStripQuotes = str
  4546. End Function
  4547.  
  4548. '-----------------------------------------------------------
  4549. ' SUB: TreatAsWin95
  4550. '
  4551. ' Returns True iff either we're running under Windows 95
  4552. ' or we are treating this version of NT as if it were
  4553. ' Windows 95 for registry and application loggin and
  4554. ' removal purposes.  (Note:  for this version, the function
  4555. ' is true only when IsWindows95() is true.)
  4556. '-----------------------------------------------------------
  4557. '
  4558. #If Win32 And LOGGING Then
  4559. Function TreatAsWin95() As Boolean
  4560.     If IsWindows95() Then
  4561.         TreatAsWin95 = True
  4562.     ElseIf fNTWithShell() Then
  4563.         TreatAsWin95 = True
  4564.     Else
  4565.         TreatAsWin95 = False
  4566.     End If
  4567. End Function
  4568. #End If
  4569.  
  4570. '-----------------------------------------------------------
  4571. ' SUB: UpdateStatus
  4572. '
  4573. ' "Fill" (by percentage) inside the PictureBox and also
  4574. ' display the percentage filled
  4575. '
  4576. ' IN: [pic] - PictureBox used to bound "fill" region
  4577. '     [sngPercent] - Percentage of the shape to fill
  4578. '     [fBorderCase] - Indicates whether the percentage
  4579. '        specified is a "border case", i.e. exactly 0%
  4580. '        or exactly 100%.  Unless fBorderCase is True,
  4581. '        the values 0% and 100% will be assumed to be
  4582. '        "close" to these values, and 1% and 99% will
  4583. '        be used instead.
  4584. '
  4585. ' Notes: Set AutoRedraw property of the PictureBox to True
  4586. '        so that the status bar and percentage can be auto-
  4587. '        matically repainted if necessary
  4588. '-----------------------------------------------------------
  4589. '
  4590. Sub UpdateStatus(pic As PictureBox, ByVal sngPercent As Single, Optional ByVal fBorderCase)
  4591.     Dim strPercent As String
  4592.     Dim intX As Integer
  4593.     Dim intY As Integer
  4594.     Dim intWidth As Integer
  4595.     Dim intHeight As Integer
  4596.  
  4597.     If IsMissing(fBorderCase) Then fBorderCase = False
  4598.     
  4599.     'For this to work well, we need a white background and any color foreground (blue)
  4600.     Const colBackground = &HFFFFFF ' white
  4601.     Const colForeground = &H800000 ' dark blue
  4602.  
  4603.     pic.ForeColor = colForeground
  4604.     pic.BackColor = colBackground
  4605.     
  4606.     '
  4607.     'Format percentage and get attributes of text
  4608.     '
  4609.     Dim intPercent
  4610.     intPercent = Int(100 * sngPercent + 0.5)
  4611.     
  4612.     'Never allow the percentage to be 0 or 100 unless it is exactly that value.  This
  4613.     'prevents, for instance, the status bar from reaching 100% until we are entirely done.
  4614.     If intPercent = 0 Then
  4615.         If Not fBorderCase Then
  4616.             intPercent = 1
  4617.         End If
  4618.     ElseIf intPercent = 100 Then
  4619.         If Not fBorderCase Then
  4620.             intPercent = 99
  4621.         End If
  4622.     End If
  4623.     
  4624.     strPercent = Format$(intPercent) & "%"
  4625.     intWidth = pic.TextWidth(strPercent)
  4626.     intHeight = pic.TextHeight(strPercent)
  4627.  
  4628.     '
  4629.     'Now set intX and intY to the starting location for printing the percentage
  4630.     '
  4631.     intX = pic.Width / 2 - intWidth / 2
  4632.     intY = pic.Height / 2 - intHeight / 2
  4633.  
  4634.     '
  4635.     'Need to draw a filled box with the pics background color to wipe out previous
  4636.     'percentage display (if any)
  4637.     '
  4638.     pic.DrawMode = 13 ' Copy Pen
  4639.     pic.Line (intX, intY)-Step(intWidth, intHeight), pic.BackColor, BF
  4640.  
  4641.     '
  4642.     'Back to the center print position and print the text
  4643.     '
  4644.     pic.CurrentX = intX
  4645.     pic.CurrentY = intY
  4646.     pic.Print strPercent
  4647.  
  4648.     '
  4649.     'Now fill in the box with the ribbon color to the desired percentage
  4650.     'If percentage is 0, fill the whole box with the background color to clear it
  4651.     'Use the "Not XOR" pen so that we change the color of the text to white
  4652.     'wherever we touch it, and change the color of the background to blue
  4653.     'wherever we touch it.
  4654.     '
  4655.     pic.DrawMode = 10 ' Not XOR Pen
  4656.     If sngPercent > 0 Then
  4657.         pic.Line (0, 0)-(pic.Width * sngPercent, pic.Height), pic.ForeColor, BF
  4658.     Else
  4659.         pic.Line (0, 0)-(pic.Width, pic.Height), pic.BackColor, BF
  4660.     End If
  4661.  
  4662.     pic.Refresh
  4663. End Sub
  4664.  
  4665. '-----------------------------------------------------------
  4666. ' FUNCTION: WriteAccess
  4667. '
  4668. ' Determines whether there is write access to the specified
  4669. ' directory.
  4670. '
  4671. ' IN: [strDirName] - directory to check for write access
  4672. '
  4673. ' Returns: True if write access, False otherwise
  4674. '-----------------------------------------------------------
  4675. '
  4676. Function WriteAccess(ByVal strDirName As String) As Integer
  4677.     Dim intFileNum As Integer
  4678.  
  4679.     On Error Resume Next
  4680.  
  4681.     AddDirSep strDirName
  4682.  
  4683.     intFileNum = FreeFile
  4684.     Open strDirName & mstrCONCATFILE For Output As intFileNum
  4685.  
  4686.     WriteAccess = IIf(Err, False, True)
  4687.     
  4688.     Close intFileNum
  4689.  
  4690.     Kill strDirName & mstrCONCATFILE
  4691.  
  4692.     Err = 0
  4693. End Function
  4694.  
  4695. 'Adds or replaces an HKEY to the list of HKEYs in cache.
  4696. 'Note that it is not necessary to remove keys from
  4697. 'this list.
  4698. Private Sub AddHkeyToCache(ByVal hkey As Long, ByVal strHkey As String)
  4699.     Dim intIdx As Integer
  4700.     
  4701.     intIdx = intGetHKEYIndex(hkey)
  4702.     If intIdx < 0 Then
  4703.         'The key does not already exist.  Add it to the end.
  4704.         On Error Resume Next
  4705.         ReDim Preserve hkeyCache(0 To UBound(hkeyCache) + 1)
  4706.         If Err Then
  4707.             'If there was an error, it means the cache was empty.
  4708.             On Error GoTo 0
  4709.             ReDim hkeyCache(0 To 0)
  4710.         End If
  4711.         On Error GoTo 0
  4712.  
  4713.         intIdx = UBound(hkeyCache)
  4714.     Else
  4715.         'The key already exists.  It will be replaced.
  4716.     End If
  4717.  
  4718.     hkeyCache(intIdx).hkey = hkey
  4719.     hkeyCache(intIdx).strHkey = strHkey
  4720. End Sub
  4721.  
  4722. 'Given a predefined HKEY, return the text string representing that
  4723. 'key, or else return "".
  4724. Private Function strGetPredefinedHKEYString(ByVal hkey As Long) As String
  4725.     Select Case hkey
  4726.     Case HKEY_CLASSES_ROOT
  4727.         strGetPredefinedHKEYString = "HKEY_CLASSES_ROOT"
  4728.     #If Win32 Then
  4729.     Case HKEY_CURRENT_USER
  4730.         strGetPredefinedHKEYString = "HKEY_CURRENT_USER"
  4731.     Case HKEY_LOCAL_MACHINE
  4732.         strGetPredefinedHKEYString = "HKEY_LOCAL_MACHINE"
  4733.     Case HKEY_USERS
  4734.         strGetPredefinedHKEYString = "HKEY_USERS"
  4735.     #End If
  4736.     End Select
  4737. End Function
  4738.  
  4739. 'Given an HKEY, return the text string representing that
  4740. 'key.
  4741. Private Function strGetHKEYString(ByVal hkey As Long) As String
  4742.     Dim strKey As String
  4743.  
  4744.     'Is the hkey predefined?
  4745.     strKey = strGetPredefinedHKEYString(hkey)
  4746.     If strKey <> "" Then
  4747.         strGetHKEYString = strKey
  4748.         Exit Function
  4749.     End If
  4750.     
  4751.     'It is not predefined.  Look in the cache.
  4752.     Dim intIdx As Integer
  4753.     intIdx = intGetHKEYIndex(hkey)
  4754.     If intIdx >= 0 Then
  4755.         strGetHKEYString = hkeyCache(intIdx).strHkey
  4756.     Else
  4757.         strGetHKEYString = ""
  4758.     End If
  4759. End Function
  4760.  
  4761. 'Searches the cache for the index of the given HKEY.
  4762. 'Returns the index if found, else returns -1.
  4763. Private Function intGetHKEYIndex(ByVal hkey As Long) As Integer
  4764.     Dim intUBound As Integer
  4765.     
  4766.     On Error Resume Next
  4767.     intUBound = UBound(hkeyCache)
  4768.     If Err Then
  4769.         'If there was an error accessing the ubound of the array,
  4770.         'then the cache is empty
  4771.         GoTo NotFound
  4772.     End If
  4773.     On Error GoTo 0
  4774.  
  4775.     Dim intIdx As Integer
  4776.     For intIdx = 0 To intUBound
  4777.         If hkeyCache(intIdx).hkey = hkey Then
  4778.             intGetHKEYIndex = intIdx
  4779.             Exit Function
  4780.         End If
  4781.     Next intIdx
  4782.     
  4783. NotFound:
  4784.     intGetHKEYIndex = -1
  4785. End Function
  4786.  
  4787. 'Returns the location of the Program Files\Common Files path, if
  4788. 'it is present in the registry.  Otherwise, returns "".
  4789. Private Function strGetCommonFilesPath() As String
  4790.     Dim hkey As Long
  4791.     Dim strPath As String
  4792.     
  4793.     #If Win32 Then
  4794.         If TreatAsWin95() Then
  4795.             Const strCommonFilesKey = "CommonFilesDir"
  4796.  
  4797.             If RegOpenKey(HKEY_LOCAL_MACHINE, RegPathWinCurrentVersion(), hkey) Then
  4798.                 RegQueryStringValue hkey, strCommonFilesKey, strPath
  4799.                 RegCloseKey hkey
  4800.             End If
  4801.         End If
  4802.     #End If
  4803.  
  4804.     If strPath <> "" Then
  4805.         AddDirSep strPath
  4806.     End If
  4807.     
  4808.     strGetCommonFilesPath = strPath
  4809. End Function
  4810.  
  4811. 'Returns the directory where DAO is or should be installed.  If the
  4812. 'key does not exist in the registry, it is created.  For instance, under
  4813. 'NT 3.51 this location is normally 'C:\WINDOWS\MSAPPS\DAO'
  4814. Private Function strGetDAOPath() As String
  4815.     Const strMSAPPS$ = "MSAPPS\"
  4816.     Const strDAO3032$ = "DAO3032.DLL"
  4817.     
  4818.     #If Win16 Then
  4819.         'For 16-bits, DAO is always in windows\MSAPPS\DAO
  4820.         strGetDAOPath = gstrWinDir & strMSAPPS & "DAO"
  4821.         Exit Function
  4822.     #Else
  4823.         'For Win32, first look in the registry
  4824.         Const strKey = "SOFTWARE\Microsoft\Shared Tools\DAO"
  4825.         Const strValueName = "Path"
  4826.         Dim hkey As Long
  4827.         Dim strPath As String
  4828.  
  4829.         If RegOpenKey(HKEY_LOCAL_MACHINE, strKey, hkey) Then
  4830.             RegQueryStringValue hkey, strValueName, strPath
  4831.             RegCloseKey hkey
  4832.         End If
  4833.  
  4834.         If strPath <> "" Then
  4835.             strPath = GetPathName(strPath)
  4836.             AddDirSep strPath
  4837.             strGetDAOPath = strPath
  4838.             Exit Function
  4839.         End If
  4840.         
  4841.         'It's not yet in the registry, so we need to decide
  4842.         'where the directory should be, and then need to place
  4843.         'that location in the registry.
  4844.     
  4845.         If TreatAsWin95() Then
  4846.             'For Win95, use "Common Files\Microsoft Shared\DAO"
  4847.             strPath = strGetCommonFilesPath() & ResolveResString(resMICROSOFTSHARED) & "DAO\"
  4848.         Else
  4849.             'Otherwise use Windows\MSAPPS\DAO
  4850.             strPath = gstrWinDir & strMSAPPS & "DAO\"
  4851.         End If
  4852.         
  4853.         'Place this information in the registry (note that we point to DAO3032.DLL
  4854.         'itself, not just to the directory)
  4855.         If RegCreateKey(HKEY_LOCAL_MACHINE, strKey, "", hkey) Then
  4856.             RegSetStringValue hkey, strValueName, strPath & strDAO3032, False
  4857.             RegCloseKey hkey
  4858.         End If
  4859.  
  4860.         strGetDAOPath = strPath
  4861.     #End If
  4862. End Function
  4863.  
  4864. ' Replace all double quotes with single quotes
  4865. Public Sub ReplaceDoubleQuotes(str As String)
  4866.     Dim i As Integer
  4867.     
  4868.     For i = 1 To Len(str)
  4869.         If Mid$(str, i, 1) = """" Then
  4870.             Mid$(str, i, 1) = "'"
  4871.         End If
  4872.     Next i
  4873. End Sub
  4874.  
  4875. 'Get the path portion of a filename
  4876. Function GetPathName(ByVal strFileName As String) As String
  4877.     Dim intPos As Integer
  4878.     Dim strPathOnly As String
  4879.     Dim dirTmp As DirListBox
  4880.     Dim i As Integer
  4881.  
  4882.     On Error Resume Next
  4883.  
  4884.     Err = 0
  4885.     
  4886.     intPos = Len(strFileName)
  4887.  
  4888.     '
  4889.     'Change all '/' chars to '\'
  4890.     '
  4891.  
  4892.     For i = 1 To Len(strFileName)
  4893.         If Mid$(strFileName, i, 1) = gstrSEP_DIRALT Then
  4894.             Mid$(strFileName, i, 1) = gstrSEP_DIR
  4895.         End If
  4896.     Next i
  4897.  
  4898.     If InStr(strFileName, gstrSEP_DIR) = intPos Then
  4899.         If intPos > 1 Then
  4900.             intPos = intPos - 1
  4901.         End If
  4902.     Else
  4903.         Do While intPos > 0
  4904.             If Mid$(strFileName, intPos, 1) <> gstrSEP_DIR Then
  4905.                 intPos = intPos - 1
  4906.             Else
  4907.                 Exit Do
  4908.             End If
  4909.         Loop
  4910.     End If
  4911.  
  4912.     If intPos > 0 Then
  4913.         strPathOnly = Left$(strFileName, intPos)
  4914.         If Right$(strPathOnly, 1) = gstrCOLON Then
  4915.             strPathOnly = strPathOnly & gstrSEP_DIR
  4916.         End If
  4917.     Else
  4918.         strPathOnly = CurDir$
  4919.     End If
  4920.  
  4921.     If Right$(strPathOnly, 1) = gstrSEP_DIR Then
  4922.         strPathOnly = Left$(strPathOnly, Len(strPathOnly) - 1)
  4923.     End If
  4924.  
  4925.     GetPathName = UCase16(strPathOnly)
  4926.     
  4927.     Err = 0
  4928. End Function
  4929.  
  4930. 'Returns the path to the root of the first fixed disk
  4931. Function strRootDrive() As String
  4932.     Dim intDriveNum As Integer
  4933.     
  4934.     For intDriveNum = 0 To Asc("Z") - Asc("A") - 1
  4935.         If GetDriveType(intDriveNum) = intDRIVE_FIXED Then
  4936.             strRootDrive = Chr$(Asc("A") + intDriveNum) & gstrCOLON & gstrSEP_DIR
  4937.             Exit Function
  4938.         End If
  4939.     Next intDriveNum
  4940.     
  4941.     strRootDrive = "C:\"
  4942. End Function
  4943.  
  4944. 'Returns "" if the path is not complete, or is a UNC pathname
  4945. Function strGetDriveFromPath(ByVal strPath As String) As String
  4946.     If Len(strPath) < 2 Then
  4947.         Exit Function
  4948.     End If
  4949.     
  4950.     If Mid$(strPath, 2, 1) <> gstrCOLON Then
  4951.         Exit Function
  4952.     End If
  4953.     
  4954.     strGetDriveFromPath = Mid$(strPath, 1, 1) & gstrCOLON & gstrSEP_DIR
  4955. End Function
  4956.  
  4957. #If Win16 Then
  4958. 'Searches for a file in the current directory, in Windows, in
  4959. 'Windows\System, or on the path.  Returns the full path if
  4960. 'found, else returns the empty string ("").
  4961. Function strFindFile16(ByVal strFileName As String) As String
  4962.     Dim openBuf As OFSTRUCT
  4963.     
  4964.     If OpenFile(strFileName, openBuf, OF_EXIST Or OF_SEARCH) <> HFILE_ERROR Then
  4965.         strFindFile16 = StripTerminator(openBuf.szPathName)
  4966.     End If
  4967. End Function
  4968. #End If
  4969.  
  4970. #If Win16 Then
  4971. Sub InstallRpcRegFile()
  4972.     Const RPCREG_KEY = "RPC_REG_DATA_FILE"
  4973.     Const RPCREG_WIN_INI_SECTION = "Rpc Runtime Configuration"
  4974.     Const WIN_INI_FILE_NAME = "win.ini"
  4975.     Const PROTOCOL_INI_FILE_NAME = "protocol.ini"
  4976.     Const LANA_SECTION = "network.setup"
  4977.     Const LANA_PREFIX = "lana"
  4978.     Const RPC_REG_NB_PATH = "\Hkey_Local_Machine\Software\Microsoft\Rpc\NetBios\"
  4979.     Const RPC_DAT_NB_PREFIX = "\Root\Software\Microsoft\Rpc\NetBios\"
  4980.     Const RPC_REG_NB_IPX = "ncacn_nb_ipx0"
  4981.     Const RPC_REG_NB_NB = "ncacn_nb_nb0"
  4982.     Const RPC_REG_NB_TCP = "ncacn_nb_tcp0"
  4983.     Const RPC_REG_NB_DECNET = "ncacn_nb_dnet0"
  4984.     Const RPC_REG_NB_XNS = "ncacn_nb_xns0"
  4985.  
  4986.     Dim bSuccess As Integer
  4987.     Dim strBuf As String
  4988.     Dim nBufSize As Integer
  4989.     Dim i As Integer
  4990.     Dim bFileOpen As Integer
  4991.     Dim nFile As Integer
  4992.     Dim bShouldCreate As Integer
  4993.     Dim strError As String
  4994.     
  4995. RetryAll:
  4996.     On Error GoTo irrfErr
  4997.     strError = ""
  4998.  
  4999.     strBuf = Space$(gintMAX_SIZE)
  5000.  
  5001.     'If rpcreg.dat file already exists, do nothing and return
  5002.  
  5003.     If FileExists(GetWindowsDir() & mstrFILE_RPCREG) Then Exit Sub
  5004.     If FileExists(GetWindowsSysDir() & mstrFILE_RPCREG) Then Exit Sub
  5005.     If strFindFile16(mstrFILE_RPCREG) <> "" Then Exit Sub
  5006.     If FileExists(strRootDrive() & mstrFILE_RPCREG) Then Exit Sub
  5007.     If FileExists(strGetDriveFromPath(GetWindowsDir()) & mstrFILE_RPCREG) Then Exit Sub
  5008.     If FileExists(strGetDriveFromPath(GetWindowsSysDir()) & mstrFILE_RPCREG) Then Exit Sub
  5009.  
  5010.     'If RPC_REG_DATA_FILE is set in win.ini, do nothing and return
  5011.  
  5012.     If ReadIniFile(WIN_INI_FILE_NAME, RPCREG_WIN_INI_SECTION, RPCREG_KEY) <> "" Then Exit Sub
  5013.  
  5014.     'If RPC_REG_DATA_FILE is in the DOS environment, do nothing and return
  5015.  
  5016.     If Environ(RPCREG_KEY) <> "" Then Exit Sub
  5017.  
  5018.     bShouldCreate = True
  5019.  
  5020. RetryCreate:
  5021.     On Error GoTo irrfErr
  5022.     strError = ""
  5023.  
  5024.     'Search for PROTOCOL.INI, first in Windows and Windows\System, and then in the path
  5025.     Dim strProtocolINI As String
  5026.     strProtocolINI = GetWindowsDir() & PROTOCOL_INI_FILE_NAME
  5027.     If Not FileExists(strProtocolINI) Then
  5028.         strProtocolINI = GetWindowsSysDir() & PROTOCOL_INI_FILE_NAME
  5029.         If Not FileExists(strProtocolINI) Then
  5030.             strProtocolINI = strFindFile16(PROTOCOL_INI_FILE_NAME)
  5031.             If Not FileExists(strProtocolINI) Then
  5032.                 strProtocolINI = PROTOCOL_INI_FILE_NAME
  5033.             End If
  5034.         End If
  5035.     End If
  5036.             
  5037.     'Get any existing LANA indexes for netbios protocols from protocol.ini and write them to rpcreg.dat
  5038.     nFile = FreeFile
  5039.     Open strRootDrive() & mstrFILE_RPCREG For Output As nFile 'create rpcreg.dat file in root dir
  5040.     bFileOpen = True
  5041.     i = 0
  5042.     strBuf = ReadIniFile(strProtocolINI, LANA_SECTION, LANA_PREFIX & Format(i))
  5043.     While strBuf <> ""
  5044.         If InStr(strBuf, "ipx") > 0 Then
  5045.             Print #nFile, RPC_DAT_NB_PREFIX & RPC_REG_NB_IPX & "=" & Format(i)
  5046.         ElseIf InStr(strBuf, "tcp") > 0 Then
  5047.             Print #nFile, RPC_DAT_NB_PREFIX & RPC_REG_NB_TCP & "=" & Format(i)
  5048.         ElseIf InStr(strBuf, "netbeui") > 0 Then
  5049.             Print #nFile, RPC_DAT_NB_PREFIX & RPC_REG_NB_NB & "=" & Format(i)
  5050.         ElseIf InStr(strBuf, "dnet") > 0 Then
  5051.             Print #nFile, RPC_DAT_NB_PREFIX & RPC_REG_NB_DECNET & "=" & Format(i)
  5052.         ElseIf InStr(strBuf, "decnet") > 0 Then
  5053.             Print #nFile, RPC_DAT_NB_PREFIX & RPC_REG_NB_DECNET & "=" & Format(i)
  5054.         End If
  5055.         
  5056.         i = i + 1
  5057.         strBuf = ReadIniFile(strProtocolINI, LANA_SECTION, LANA_PREFIX & Format(i))
  5058.     Wend
  5059.     GoTo irrfExit
  5060.  
  5061. irrfErr:
  5062.     strError = Error$
  5063.     Resume irrfRecover
  5064.  
  5065. irrfRecover:
  5066.     On Error Resume Next
  5067.     If bFileOpen Then Close nFile
  5068.     bFileOpen = False
  5069.     On Error GoTo 0
  5070.     
  5071.     Select Case MsgError(ResolveResString(resUNEXPECTEDRPCREGDAT, "|1", mstrFILE_RPCREG) & LS$ & strError, vbExclamation Or vbAbortRetryIgnore, gstrTitle)
  5072.     Case vbAbort
  5073.         ExitSetup frmSetup1, gintRET_ABORT
  5074.         GoTo irrfRecover
  5075.     Case vbRetry
  5076.         If bShouldCreate Then
  5077.             GoTo RetryCreate
  5078.         Else
  5079.             GoTo RetryAll
  5080.         End If
  5081.     Case vbIgnore
  5082.         Exit Sub
  5083.     End Select
  5084.     
  5085. irrfExit:
  5086.     If bFileOpen Then Close nFile
  5087. End Sub
  5088. #End If
  5089.  
  5090.