500 lines
16 KiB
C++
500 lines
16 KiB
C++
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
// Copyright (C) Microsoft Corporation, 2000.
|
||
|
//
|
||
|
// File: Main.cpp
|
||
|
//
|
||
|
// Contents: Main wrapper file for SRDiag, this will call into cab.cpp,
|
||
|
// chglog.cpp, getreg.cpp, rpenum.cpp for getting changlog,
|
||
|
// restore points, registry information. This file also contains
|
||
|
// routines for getting file information, restore Guid, and generic
|
||
|
// logging imposed by cab.cpp
|
||
|
//
|
||
|
// Objects:
|
||
|
//
|
||
|
// Coupling:
|
||
|
//
|
||
|
// Notes:
|
||
|
//
|
||
|
// History: 9/21/00 SHeffner Created
|
||
|
// 10/5/00 SHeffner Moved file specific gathering to the header file.
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Common Includes
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <windows.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <cab.h>
|
||
|
#include <main.h>
|
||
|
#include <dbgtrace.h>
|
||
|
#include <winver.h>
|
||
|
#include "srrpcapi.h"
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function proto typing for rpenum.cpp, getreg.cpp, Chglog.cpp
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
bool GetSRRegistry(char *szFilename, WCHAR *szPath, bool bRecurse);
|
||
|
void GetChgLog(char *szLogfile);
|
||
|
void RPEnumDrive(HFCI hc, char *szLogFile);
|
||
|
|
||
|
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Global Variables used within main, and through cab.cpp
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
FILE *fLogStream = NULL; //For the Log, and Log2 routines
|
||
|
extern char g_szCabFileLocation[_MAX_PATH]; //This is actually defined in cab.cpp
|
||
|
extern char g_szCabFileName[_MAX_PATH]; //This is actually defined in cab.cpp
|
||
|
|
||
|
void __cdecl main(int argc, char *argv[])
|
||
|
{
|
||
|
HFCI hc = NULL;
|
||
|
int i=0;
|
||
|
bool bResult=false;
|
||
|
char szString[_MAX_PATH], szRestoreDirPath[_MAX_PATH];
|
||
|
char *szTest[1], *szArgCmd[200];
|
||
|
|
||
|
//Process any commandline arguments
|
||
|
memset(szArgCmd, 0, sizeof(char)*200); //Clean up before relying on this DS.
|
||
|
ArgParse(argc, argv, szArgCmd);
|
||
|
|
||
|
//Open up the log file, and start logging all activity
|
||
|
strcpy(szString, getenv("TEMP"));
|
||
|
strcat(szString, "\\SRDIAG.LOG");
|
||
|
fLogStream = fopen(szString, "w");
|
||
|
|
||
|
|
||
|
//Create a cab, if we fail then just skip,
|
||
|
// else gather all of the relevant files required.
|
||
|
if (NULL != (hc = create_cab()) )
|
||
|
{
|
||
|
//First add in any files that were specified on the command line, but first figuring out
|
||
|
// how many were really specified
|
||
|
for(i=0;i<200;i++) if(NULL == szArgCmd[i]) break;
|
||
|
bResult = test_fci(hc, i, szArgCmd, "");
|
||
|
|
||
|
|
||
|
//Get the Temp Location, ensure we have a new file, then
|
||
|
// get the registry keys, and dump into our text file
|
||
|
strcpy(szString, getenv("TEMP"));
|
||
|
strcat(szString, "\\SR-Reg.txt");
|
||
|
DeleteFileA(szString);
|
||
|
i = 0;
|
||
|
while (NULL != *wszRegKeys[i][0]) {
|
||
|
if(0 == wcscmp(wszRegKeys[i][1], TEXT("0")))
|
||
|
GetSRRegistry(szString, wszRegKeys[i][0], false);
|
||
|
else
|
||
|
GetSRRegistry(szString, wszRegKeys[i][0], true);
|
||
|
i++;
|
||
|
}
|
||
|
//Add the log to the Cab, and clean up
|
||
|
szTest[0] = szString;
|
||
|
bResult = test_fci(hc, 1, szTest, "");
|
||
|
DeleteFileA(szString);
|
||
|
|
||
|
|
||
|
//Add files Bases on WinDir relative root
|
||
|
i = 0;
|
||
|
while (NULL != *szWindirFileCollection[i]) {
|
||
|
strcpy(szString, getenv("WINDIR"));
|
||
|
strcat(szString, szWindirFileCollection[i]);
|
||
|
szTest[0] = szString;
|
||
|
bResult = test_fci(hc, 1, szTest, "");
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
//Get the restore directory on the system drive, and then Add in critial files
|
||
|
GetRestoreGuid(szString);
|
||
|
sprintf(szRestoreDirPath, "%s\\System Volume Information\\_Restore%s\\", getenv("SYSTEMDRIVE"), szString );
|
||
|
|
||
|
//Add files Bases on System Volume Information relative root
|
||
|
i = 0;
|
||
|
while (NULL != *szSysVolFileCollection[i]) {
|
||
|
strcpy(szString, szRestoreDirPath);
|
||
|
strcat(szString, szSysVolFileCollection[i]);
|
||
|
szTest[0] = szString;
|
||
|
bResult = test_fci(hc, 1, szTest, "");
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
//Get the Restore point enumeration, and then cab the file
|
||
|
strcpy(szString, getenv("TEMP"));
|
||
|
strcat(szString, "\\SR-RP.LOG");
|
||
|
RPEnumDrive(hc, szString);
|
||
|
szTest[0] = szString;
|
||
|
bResult = test_fci(hc, 1, szTest, "");
|
||
|
DeleteFileA(szString);
|
||
|
|
||
|
//Get the ChangeLog enumeration, and then cab and delete the file
|
||
|
//first we need to switch the log, then gather the log
|
||
|
SRSwitchLog();
|
||
|
strcpy(szString, getenv("TEMP"));
|
||
|
strcat(szString, "\\SR-CHGLog.LOG");
|
||
|
GetChgLog(szString);
|
||
|
szTest[0] = szString;
|
||
|
bResult = test_fci(hc, 1, szTest, "");
|
||
|
DeleteFileA(szString);
|
||
|
|
||
|
//Get the fileversion info for each of the Files
|
||
|
strcpy(szString, getenv("TEMP"));
|
||
|
strcat(szString, "\\SR-FileList.LOG");
|
||
|
SRGetFileInfo(szString);
|
||
|
szTest[0] = szString;
|
||
|
bResult = test_fci(hc, 1, szTest, "");
|
||
|
DeleteFileA(szString);
|
||
|
|
||
|
|
||
|
//Close out logging, and add the log to the cab
|
||
|
// (THIS SHOULD BE THE LAST THING WE ARE DOING!!)
|
||
|
fclose(fLogStream);
|
||
|
fLogStream = NULL;
|
||
|
strcpy(szString, getenv("TEMP"));
|
||
|
strcat(szString, "\\SRDIAG.LOG");
|
||
|
szTest[0] = szString;
|
||
|
bResult = test_fci(hc, 1, szTest, "");
|
||
|
DeleteFileA(szString);
|
||
|
|
||
|
}
|
||
|
|
||
|
//Completes cab file under construction
|
||
|
if (flush_cab(hc))
|
||
|
{
|
||
|
Log("Cabbing Process was Sucessful");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Log("Cabbing Process has failed");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: Log
|
||
|
//
|
||
|
// Synopsis: Will print a String to both our log file, and also the console
|
||
|
//
|
||
|
// Arguments: [szString] -- Simple ANSI string to log
|
||
|
//
|
||
|
// Returns: void
|
||
|
//
|
||
|
// History: 9/21/00 SHeffner Created
|
||
|
//
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
void Log(char *szString)
|
||
|
{
|
||
|
if( NULL != fLogStream)
|
||
|
fprintf(fLogStream, "%s\n", szString);
|
||
|
|
||
|
puts(szString);
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: Log2
|
||
|
//
|
||
|
// Synopsis: Takes two strings, and will print them back to back to both the
|
||
|
// log file, and also to the console
|
||
|
//
|
||
|
// Arguments: [szString] -- Simple ANSI string to log
|
||
|
// [szString2] -- Simple ANSI string to log
|
||
|
//
|
||
|
// Returns: void
|
||
|
//
|
||
|
// History: 9/21/00 SHeffner Created
|
||
|
//
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
void Log2(char *szString, char *szString2)
|
||
|
{
|
||
|
if( NULL != fLogStream)
|
||
|
fprintf(fLogStream,"%s %s\n", szString, szString2);
|
||
|
|
||
|
printf("%s %s\n", szString, szString2);
|
||
|
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: GetRestoreGuid
|
||
|
//
|
||
|
// Synopsis: Will retrieve from the registry what the GUID is for the current
|
||
|
// Restore directory, and return this in the string pointer passed
|
||
|
// to the function.
|
||
|
//
|
||
|
// Arguments: [szString] -- Simple ANSI string to receive the string
|
||
|
//
|
||
|
// Returns: void
|
||
|
//
|
||
|
// History: 9/21/00 SHeffner Created
|
||
|
//
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
void GetRestoreGuid(char *szString)
|
||
|
{
|
||
|
long lResult;
|
||
|
HKEY mHkey;
|
||
|
DWORD dwType, dwLength;
|
||
|
|
||
|
lResult = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SystemRestore\\Cfg", 0, KEY_READ, &mHkey);
|
||
|
dwLength = _MAX_PATH +1;
|
||
|
lResult = RegQueryValueExA(mHkey, "MachineGuid", NULL, &dwType, (unsigned char *)szString, &dwLength);
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: SRGetFileInfo
|
||
|
//
|
||
|
// Synopsis: This is the wrapper function for InfoPerFile, where I will assemble
|
||
|
// the file path for each of the relevant files that we need to get
|
||
|
// file statistics.
|
||
|
//
|
||
|
// Arguments: [szLogFile] -- The path to the file that I will log this information to.
|
||
|
//
|
||
|
// Returns: void
|
||
|
//
|
||
|
// History: 9/21/00 SHeffner Created
|
||
|
//
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
void SRGetFileInfo(char *szLogFile)
|
||
|
{
|
||
|
int iCount;
|
||
|
WCHAR szString[_MAX_PATH];
|
||
|
|
||
|
//Initialize counter, and walk through the filelist that we have
|
||
|
iCount = 0;
|
||
|
while(NULL != *wszFileVersionList[iCount])
|
||
|
{
|
||
|
//Assemble the path, since I just have the relative path from windir
|
||
|
wcscpy(szString, _wgetenv(L"WINDIR"));
|
||
|
wcscat(szString, wszFileVersionList[iCount]);
|
||
|
|
||
|
//Call function to do the work since we have full path, and log file name
|
||
|
InfoPerFile(szLogFile, szString);
|
||
|
iCount++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: InfoPerFile
|
||
|
//
|
||
|
// Synopsis: This function takes the log file path, and the filename, and then
|
||
|
// will put out the relevant information from the file to be logged.
|
||
|
//
|
||
|
//
|
||
|
// Arguments: [szLogFile] -- The path to the file that I will log this information to.
|
||
|
// [szFileName] -- The full path, and name of the file to get the information for.
|
||
|
//
|
||
|
// Returns: void
|
||
|
//
|
||
|
// History: 9/21/00 SHeffner Created
|
||
|
//
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
void InfoPerFile(char *szLogFile, WCHAR *szFileName)
|
||
|
{
|
||
|
BY_HANDLE_FILE_INFORMATION finfo;
|
||
|
SYSTEMTIME st;
|
||
|
HANDLE handle;
|
||
|
FILE *fStream;
|
||
|
WCHAR szString[_MAX_PATH];
|
||
|
VOID *pBuffer;
|
||
|
VS_FIXEDFILEINFO FixedFileInfo;
|
||
|
UINT uLen;
|
||
|
DWORD dSize, dResult, i;
|
||
|
|
||
|
struct LANGANDCODEPAGE {
|
||
|
WORD wLanguage;
|
||
|
WORD wCodePage;
|
||
|
} *lpTranslate;
|
||
|
|
||
|
WCHAR *szMonth[] = { L"January", L"Feburary", L"March", L"April", L"May", L"June", L"July", L"August", L"September", L"October", L"November", L"December" };
|
||
|
WCHAR *szDay[] = {L"Sunday", L"Monday", L"Tuesday", L"Wednesday", L"Thursday", L"Friday", L"Saturday" };
|
||
|
|
||
|
if ( NULL == *szFileName) return;
|
||
|
|
||
|
//Open up our log file, and log the file we are processing
|
||
|
fStream = fopen(szLogFile, "a");
|
||
|
if( NULL == fStream) return; //if we have an invalid handle just return back.
|
||
|
fprintf(fStream, "\n%S\n", szFileName);
|
||
|
|
||
|
//Open up the file so that we can get the information from the handle
|
||
|
// If we are unable to do this we will just log the generic not able to find file.
|
||
|
if( INVALID_HANDLE_VALUE != (handle = CreateFile(szFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL)) )
|
||
|
{
|
||
|
if (FALSE != GetFileInformationByHandle(handle, &finfo))
|
||
|
{
|
||
|
//FileCreation
|
||
|
FileTimeToSystemTime( &finfo.ftCreationTime, &st);
|
||
|
fprintf(fStream, "\tCreation Date=%S %S %lu, %lu %lu:%lu:%lu\n",
|
||
|
szDay[st.wDayOfWeek],szMonth[st.wMonth-1],st.wDay,st.wYear,st.wHour,st.wMinute,st.wSecond);
|
||
|
//FileLastAccess
|
||
|
FileTimeToSystemTime( &finfo.ftLastAccessTime, &st);
|
||
|
fprintf(fStream, "\tLast Access Date=%S %S %lu, %lu %lu:%lu:%lu\n",
|
||
|
szDay[st.wDayOfWeek],szMonth[st.wMonth-1],st.wDay,st.wYear,st.wHour,st.wMinute,st.wSecond);
|
||
|
|
||
|
//FileLastWrite
|
||
|
FileTimeToSystemTime( &finfo.ftLastWriteTime, &st);
|
||
|
fprintf(fStream, "\tLast Write Date=%S %S %lu, %lu %lu:%lu:%lu\n",
|
||
|
szDay[st.wDayOfWeek],szMonth[st.wMonth-1],st.wDay,st.wYear,st.wHour,st.wMinute,st.wSecond);
|
||
|
//File Attributes
|
||
|
wcscpy(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE ? L"ARCHIVE " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED ? L"COMPRESSED " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? L"DIRECTORY " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED ? L"ENCRYPTED " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ? L"HIDDEN " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_NORMAL ? L"NORMAL " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE ? L"OFFLINE " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY ? L"READONLY " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ? L"REPARSE_POINT " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE ? L"SPARSE_FILE " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM ? L"SYSTEM " : L"");
|
||
|
wcscat(szString, finfo.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY ? L"TEMPORARY " : L"");
|
||
|
fprintf(fStream, "\tAttributes=%S\n", szString);
|
||
|
//Get the VolumeSerialNumber, FileSize, and Number of Links
|
||
|
fprintf(fStream, "\tVolumeSerialNumber=%lu\n", finfo.dwVolumeSerialNumber);
|
||
|
fprintf(fStream, "\tFileSize=%lu%lu\n", finfo.nFileSizeHigh, finfo.nFileSizeLow);
|
||
|
fprintf(fStream, "\tNumberOfLinks=%lu\n", finfo.nNumberOfLinks);
|
||
|
|
||
|
if( 0 != (dSize = GetFileVersionInfoSize(szFileName, &dResult)) )
|
||
|
{
|
||
|
if( NULL != (pBuffer = malloc(dSize)) )
|
||
|
{
|
||
|
GetFileVersionInfo(szFileName, dResult, dSize, (LPVOID) pBuffer);
|
||
|
|
||
|
// Read the list of languages and code pages.
|
||
|
|
||
|
VerQueryValue(pBuffer,
|
||
|
TEXT("\\VarFileInfo\\Translation"),
|
||
|
(LPVOID*)&lpTranslate,
|
||
|
&uLen);
|
||
|
|
||
|
// Read the Version info for each language and code page.
|
||
|
for( i=0; i < (uLen/sizeof(struct LANGANDCODEPAGE)); i++ )
|
||
|
{
|
||
|
char *lpBuffer;
|
||
|
DWORD dwBytes, dwCount = 0;
|
||
|
|
||
|
fprintf(fStream, "\tLanguage=%x%x\n", lpTranslate[i].wLanguage, lpTranslate[i].wCodePage);
|
||
|
while (NULL != *wszVersionResource[dwCount] )
|
||
|
{
|
||
|
//Generate the string, for getting the resource based on the language, then
|
||
|
// retrieve this, and then put it to the log file.
|
||
|
wsprintf( szString, L"\\StringFileInfo\\%04x%04x\\%s",
|
||
|
lpTranslate[i].wLanguage,
|
||
|
lpTranslate[i].wCodePage,
|
||
|
wszVersionResource[dwCount]);
|
||
|
VerQueryValue(pBuffer,
|
||
|
szString,
|
||
|
(LPVOID *) &lpBuffer,
|
||
|
&uLen);
|
||
|
if( 0 != uLen )
|
||
|
fprintf(fStream, "\t%S=%S\n", wszVersionResource[dwCount], lpBuffer);
|
||
|
dwCount++;
|
||
|
} //While loop end, for each resource
|
||
|
} //for loop end for each language
|
||
|
|
||
|
//Clean up the allocated memory
|
||
|
free(pBuffer);
|
||
|
} //If check for getting memory
|
||
|
} //If check for getting the fileversioninfosize
|
||
|
} //if check for GetFileInformationByHandle on the file
|
||
|
CloseHandle(handle);
|
||
|
} //if check on can I open this file
|
||
|
|
||
|
//Cleanup
|
||
|
fclose(fStream);
|
||
|
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: ArgParse
|
||
|
//
|
||
|
// Synopsis: This function simply looks for the key word parameters, and will build
|
||
|
// an array pointing to each of the files that we want to include in the cab
|
||
|
// in addition to the normal files.
|
||
|
//
|
||
|
//
|
||
|
// Arguments: [argc] -- Count of the number of arguments
|
||
|
// [argv] -- Array of the arguments.
|
||
|
//
|
||
|
// Returns: void
|
||
|
//
|
||
|
// History: 9/21/00 SHeffner Created
|
||
|
//
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
void ArgParse(int argc, char *argv[], char *szArgCmd[])
|
||
|
{
|
||
|
int iCount, iWalk;
|
||
|
|
||
|
//If no command line specified then just do the normal cabbing, with core files
|
||
|
if(1 == argc)
|
||
|
return;
|
||
|
|
||
|
//walk through each of the arguments, and check to see if its a help, file, cabname, cabloc, or a ?
|
||
|
for(iCount = 1; iCount < argc; iCount++)
|
||
|
{
|
||
|
if( 0 == _strnicoll(&argv[iCount][1], "?", strlen("?")) )
|
||
|
Usage();
|
||
|
if( 0 == _strnicoll(&argv[iCount][1], "help", strlen("help")) )
|
||
|
Usage();
|
||
|
if( 0 == _strnicoll(&argv[iCount][1], "cabname", strlen("cabname")) )
|
||
|
strcpy(g_szCabFileName, (strstr(argv[iCount], ":") + 1));
|
||
|
if( 0 == _strnicoll(&argv[iCount][1], "cabloc", strlen("cabloc")) )
|
||
|
strcpy(g_szCabFileLocation, (strstr(argv[iCount], ":") + 1));
|
||
|
|
||
|
if( 0 == _strnicoll(&argv[iCount][1], "file", strlen("file")) )
|
||
|
{
|
||
|
//find the first spot where I can put the pointer to this filename
|
||
|
for( iWalk=0; iWalk < 200; iWalk++)
|
||
|
{
|
||
|
if( NULL == szArgCmd[iWalk] )
|
||
|
{
|
||
|
szArgCmd[iWalk] = strstr(argv[iCount], ":") + 1;
|
||
|
break;
|
||
|
}
|
||
|
} //end for loop, walking through DS
|
||
|
} //end of if for is this an added file
|
||
|
} //end of for loop walking through all of the arguments
|
||
|
}
|
||
|
|
||
|
//+---------------------------------------------------------------------------
|
||
|
//
|
||
|
// Function: Usage
|
||
|
//
|
||
|
// Synopsis: Displaies the command line usage
|
||
|
//
|
||
|
//
|
||
|
// Arguments:
|
||
|
//
|
||
|
// Returns: void
|
||
|
//
|
||
|
// History: 9/21/00 SHeffner Created
|
||
|
//
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
void Usage()
|
||
|
{
|
||
|
printf("Usage: SrDiag [/Cabname:test.cab] [/Cabloc:\"c:\\temp\\\"] [/file:\"c:\\boot.ini\"]\n");
|
||
|
printf(" /cabloc is pointing to the location to store the cab, this should have a \\ on the end\n");
|
||
|
printf(" /cabname is the full name of the cab file that you wish to use. \n");
|
||
|
printf(" /file is the name and path of a file that you wish to add to the cab, this can be used many times\n");
|
||
|
exit(0);
|
||
|
}
|