2025-11-28 00:35:46 +09:00

604 lines
27 KiB
C++

//+-------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// File: msistuff.cpp
//
//--------------------------------------------------------------------------
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <stdlib.h>
#include "..\setup.exe\common.h"
#include "strsafe.h"
//=======================================================
// command line options and matching properties
// ORDER is IMPORTANT!
//=======================================================
TCHAR rgszCommandOptions[]= TEXT("udnviwpmo");
////////////////////////// 012345678
TCHAR* rgszResName[] = {
/* BaseURL */ ISETUPPROPNAME_BASEURL,
/* Msi Package */ ISETUPPROPNAME_DATABASE,
/* Product Name */ ISETUPPROPNAME_PRODUCTNAME,
/* Minimum Msi Version */ ISETUPPROPNAME_MINIMUM_MSI,
/* Update URL Location */ ISETUPPROPNAME_UPDATELOCATION,
/* InstMsiW */ ISETUPPROPNAME_UPDATE,
/* Properties */ ISETUPPROPNAME_PROPERTIES,
/* Patch */ ISETUPPROPNAME_PATCH,
/* Operation */ ISETUPPROPNAME_OPERATION
};
const int cStandardProperties = sizeof(rgszResName)/sizeof(TCHAR*);
TCHAR rgchResSwitch[] ={TEXT('u'),TEXT('d'),TEXT('n'),TEXT('v'),TEXT('i'),TEXT('w'),TEXT('p'),TEXT('m'),TEXT('o')};
const DWORD lcidLOCALE_INVARIANT = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
//=======================================================
// special options
//=======================================================
const TCHAR chProperties = TEXT('p');
const TCHAR chMinMsiVer = TEXT('v');
const TCHAR chOperation = TEXT('o');
const TCHAR szInstall[] = TEXT("INSTALL");
const TCHAR szInstallUpd[] = TEXT("INSTALLUPD");
const TCHAR szMinPatch[] = TEXT("MINPATCH");
const TCHAR szMajPatch[] = TEXT("MAJPATCH");
const int iMinMsiAllowedVer = 150;
//=======================================================
// function prototypes
//=======================================================
void DisplayHelp();
bool ParseCommandLine(__in_opt LPTSTR lpCommandLine);
TCHAR SkipWhiteSpace(__inout_opt TCHAR*& rpch);
bool SkipValue(__inout_opt TCHAR*& rpch);
bool RemoveQuotes(LPCTSTR lpOriginal, __out_ecount(cchStripped) LPTSTR lpStripped, DWORD cchStripped);
bool DisplayResources(LPCTSTR lpExecutable);
bool DisplayInstallResource(HMODULE hExeModule, LPCTSTR lpszType, LPCTSTR lpszName);
BOOL CALLBACK EnumResNamesProc(HMODULE hExeModule, LPCTSTR lpszType, __in LPTSTR lpszName, LONG_PTR lParam);
//=======================================================
// global constants
//=======================================================
int g_cResources = 0; // count of resources in setup.exe; information purposes only
//_____________________________________________________________________________________________________
//
// main
//_____________________________________________________________________________________________________
extern "C" int __cdecl _tmain(int argc, __in_ecount(argc) TCHAR* argv[])
{
if (argc <= 2)
{
if (1 == argc
|| 0 == lstrcmp(argv[1], TEXT("/?"))
|| 0 == lstrcmp(argv[1], TEXT("-?")))
{
//
// display help
DisplayHelp();
}
else
{
//
// display setup resources
TCHAR szExecutable[MAX_PATH] = {0};
if (FAILED(StringCchCopy(szExecutable, sizeof(szExecutable)/sizeof(szExecutable[0]), argv[1])))
return -1;
if (!DisplayResources(szExecutable))
return -1;
}
}
else
{
//
// set resource properties
TCHAR *szCommandLine = GetCommandLine();
if (!ParseCommandLine(szCommandLine))
return -1;
}
return 0;
}
//_____________________________________________________________________________________________________
//
// DisplayHelp
//_____________________________________________________________________________________________________
void DisplayHelp()
{
TCHAR szHelp[] =
TEXT("Copyright (c) Microsoft Corporation. All rights reserved.\n")
TEXT("\n")
TEXT("MsiStuff will display or update the resources \n")
TEXT(" in the setup.exe boot strap executable\n")
TEXT("\n")
TEXT("[MsiStuff Command Line Syntax]\n")
TEXT(" Display Properties->> msistuff setup.exe \n")
TEXT(" Set Properties ->> msistuff setup.exe option {data} ... \n")
TEXT("\n")
TEXT("[MsiStuff Options -- Multiple specifications are allowed]\n")
TEXT(" BaseURL - /u {value} \n")
TEXT(" Msi - /d {value} \n")
TEXT(" Product Name - /n {value} \n")
TEXT(" Minimum Msi Version - /v {value} \n")
TEXT(" Update URL Location - /i {value} \n")
TEXT(" Windows Installer Update - /w {value} \n")
TEXT(" Patch - /m {value} \n")
TEXT(" Operation - /o {value} \n")
TEXT(" Properties (PROPERTY=VALUE strings) - /p {value} \n")
TEXT("\n")
TEXT("If an option is specified multiple times, the last one wins\n")
TEXT("\n")
TEXT("/p must be last on the command line. The remainder of\n")
TEXT("the command line is considered a part of the {value}\n")
TEXT("This also means that /p cannot be specified multiple times\n");
_tprintf(TEXT("%s"), szHelp);
}
//_____________________________________________________________________________________________________
//
// ParseCommandLine
//
// If a property has a value that contains spaces, the value must be enclosed in quotation marks
//_____________________________________________________________________________________________________
bool ParseCommandLine(__in_opt LPTSTR lpCommandLine)
{
if (!lpCommandLine)
return false;
TCHAR szSetupEXE[MAX_PATH] = {0};
TCHAR szFullPath[2*MAX_PATH] = {0};
TCHAR chNextCommand;
TCHAR *pchCommandLine = lpCommandLine;
// skip over module name and subsequent white space
if (!SkipValue(pchCommandLine))
return false;
chNextCommand = SkipWhiteSpace(pchCommandLine);
TCHAR* pchCommandData = pchCommandLine;
if (!SkipValue(pchCommandLine))
return false;
if (!RemoveQuotes(pchCommandData, szSetupEXE, sizeof(szSetupEXE)/sizeof(szSetupEXE[0])))
return false;
// handle possibility of a relative path
LPTSTR lpszFilePart = 0;
int cchFullPath = 0;
if (0 == (cchFullPath = GetFullPathName(szSetupEXE, sizeof(szFullPath)/sizeof(TCHAR), szFullPath, &lpszFilePart)))
{
// error
_tprintf(TEXT("Unable to obtain fullpath for %s\n"), szSetupEXE);
return false;
}
else if (cchFullPath > sizeof(szFullPath)/sizeof(TCHAR))
{
// szFullPath buffer is too small; we could resize, but we'll simply fail here
_tprintf(TEXT("szFullPath buffer is insufficiently sized for obtaining the full path for %s\n"), szSetupEXE);
return false;
}
_tprintf(TEXT("\nModifying setup properties in:\n\t<%s>\n\n"), szFullPath);
// make sure the EXE is not already loaded,in use, or read-only
HANDLE hInUse = CreateFile(szFullPath, GENERIC_WRITE, (DWORD)0, (LPSECURITY_ATTRIBUTES)0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE)0);
if (INVALID_HANDLE_VALUE == hInUse)
{
// error
_tprintf(TEXT("Unable to obtain file handle for %s. The file probably does not exist, is marked read-only, or is in use. LastError = %d\n"), szFullPath, GetLastError());
return false;
}
CloseHandle(hInUse);
// begin update resource
HANDLE hUpdate = BeginUpdateResource(szFullPath, /* bDeleteExistingResources = */ FALSE);
if ( !hUpdate )
{
// error
_tprintf(TEXT("Unable to update resources in %s. LastError = %d\n"), szFullPath, GetLastError());
return false;
}
while ((chNextCommand = SkipWhiteSpace(pchCommandLine)) != 0)
{
if (chNextCommand == TEXT('/') || chNextCommand == TEXT('-'))
{
TCHAR *szOption = pchCommandLine++; // save for error msg
TCHAR chOption = (TCHAR)(*pchCommandLine++ | 0x20); // lower case flag
chNextCommand = SkipWhiteSpace(pchCommandLine);
pchCommandData = pchCommandLine;
const TCHAR* pchOptions;
for (pchOptions = rgszCommandOptions; *pchOptions; pchOptions++)
{
if (*pchOptions == chOption)
break;
}
if (*pchOptions)
{
bool fSkipValue = true; // whether or not to look for next option in command line; (true = look)
bool fDeleteValue = false;// whether to delete the value
// option is recognized
const TCHAR chIndex = (TCHAR)(pchOptions - rgszCommandOptions);
if (chIndex >= cStandardProperties)
{
// error
_tprintf(TEXT("Invalid index (chIndex = %d, chOption = %c)!!!\n"), chIndex, chOption);
return false;
}
if (chOption == chProperties)
{
fSkipValue = false;
// special for /p -- remainder of command line is part of property
TCHAR chNext = *pchCommandData;
if (chNext == 0 || chNext == TEXT('/') || chNext == TEXT('-'))
{
// no value present
fDeleteValue = true;
}
else
{
// set value to remainder of command line, i.e. all of pchCommandData
// with enclosing quotes stripped -- we do this by telling the command
// line processor to not attempt to look for other options on the
// command line; the remainder is part of this property
fSkipValue = false;
}
}
if (fSkipValue)
fDeleteValue = (SkipValue(pchCommandLine)) ? false : true;
if (fDeleteValue)
{
// delete value and reset to ensure resource is properly removed
if (!UpdateResource(hUpdate, RT_INSTALL_PROPERTY, rgszResName[chIndex], MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), NULL, 0)
|| !EndUpdateResource(hUpdate, /* fDiscard = */ FALSE)
|| (!(hUpdate = BeginUpdateResource(szFullPath, /* bDeleteExistingResources = */ FALSE))))
{
// error
_tprintf(TEXT("Unable to delete resource %s in %s. LastError = %d\n"), rgszResName[chIndex], szFullPath, GetLastError());
return false;
}
_tprintf(TEXT("Removing '%s' . . .\n"), rgszResName[chIndex]);
}
else
{
TCHAR szValueBuf[1024] = {0};
if (!RemoveQuotes(pchCommandData, szValueBuf, sizeof(szValueBuf)/sizeof(szValueBuf[0])))
return false;
if (chOption == chMinMsiVer && (_ttoi(szValueBuf) < iMinMsiAllowedVer))
{
// extra validation: must be >= iMinAllowedVer
_tprintf(TEXT("Skipping option %c with data %s. Data value must be >= %d. . .\n"), chOption, szValueBuf, iMinMsiAllowedVer);
continue;
}
if (chOption == chOperation
&& CSTR_EQUAL != CompareString(lcidLOCALE_INVARIANT, NORM_IGNORECASE, szValueBuf, -1, szInstall, -1)
&& CSTR_EQUAL != CompareString(lcidLOCALE_INVARIANT, NORM_IGNORECASE, szValueBuf, -1, szInstallUpd, -1)
&& CSTR_EQUAL != CompareString(lcidLOCALE_INVARIANT, NORM_IGNORECASE, szValueBuf, -1, szMinPatch, -1)
&& CSTR_EQUAL != CompareString(lcidLOCALE_INVARIANT, NORM_IGNORECASE, szValueBuf, -1, szMajPatch, -1))
{
// extra validation: must be one of those values
_tprintf(TEXT("Skipping option %c with data %s. Data value must be INSTALL, INSTALLUPD, MINPATCH, or MAJPATCH...\n"), chOption, szValueBuf);
continue;
}
// update value -- this is our own custom resource, so to make it easier on ourselves, we will pack in the NULL as well
#ifdef UNICODE
if (!UpdateResource(hUpdate, RT_INSTALL_PROPERTY, rgszResName[chIndex], MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), szValueBuf, (lstrlen(szValueBuf) + 1)*sizeof(TCHAR)))
{
// error
_tprintf(TEXT("Unable to update resource %s in %s with value %s. LastError = %d\n"), rgszResName[chIndex], szFullPath, szValueBuf, GetLastError());
return false;
}
#else // !UNICODE
// must convert value to Unicode
int cchWide = MultiByteToWideChar(CP_ACP, 0, szValueBuf, -1, NULL, 0);
WCHAR* wszValueBuf = new WCHAR[cchWide];
if (!wszValueBuf)
return false; // out of memory
ZeroMemory(wszValueBuf, cchWide*sizeof(WCHAR));
if (0 == MultiByteToWideChar(CP_ACP, 0, szValueBuf, -1, wszValueBuf, cchWide))
{
// error
_tprintf(TEXT("Unable to convert value to Unicode. LastError = %d\n"), GetLastError());
delete [] wszValueBuf;
return false;
}
if (!UpdateResource(hUpdate, RT_INSTALL_PROPERTY, rgszResName[chIndex], MAKELANGID(LANG_NEUTRAL,SUBLANG_NEUTRAL), wszValueBuf, (lstrlenW(wszValueBuf) +1)*sizeof(WCHAR)))
{
// error
_tprintf(TEXT("Unable to update resource %s in %s with value %s. LastError = %d\n"), rgszResName[chIndex], szFullPath, szValueBuf, GetLastError());
delete [] wszValueBuf;
return false;
}
delete [] wszValueBuf;
#endif // UNICODE
_tprintf(TEXT("Setting '%s' to '%s' . . .\n"), rgszResName[chIndex], szValueBuf);
if (!fSkipValue)
break; // done processing
}
}
else
{
// invalid option
_tprintf(TEXT("Skipping invalid option %c . . .\n"), chOption);
SkipValue(pchCommandLine);
continue;
}
}
else
{
// error
_tprintf(TEXT("Switch is missing\n"));
return false;
}
}
// persist changes
if (!EndUpdateResource(hUpdate, /* fDiscard = */ FALSE))
{
// error
_tprintf(TEXT("Unable to update resources in %s. LastError = %d\n"), szFullPath, GetLastError());
return false;
}
return true;
}
//_____________________________________________________________________________________________________
//
// SkipWhiteSpace
//
// Skips whitespace in the string and returns next non-tab non-whitespace charcter
//_____________________________________________________________________________________________________
TCHAR SkipWhiteSpace(__inout_opt TCHAR*& rpch)
{
TCHAR ch = 0;
if (rpch)
{
for (; (ch = *rpch) == TEXT(' ') || ch == TEXT('\t'); rpch++)
;
}
return ch;
}
//_____________________________________________________________________________________________________
//
// SkipValue
//
// Skips over the value of a switch and returns true if a value was present. Handles value enclosed
// in quotation marks
//_____________________________________________________________________________________________________
bool SkipValue(__inout_opt TCHAR*& rpch)
{
if (!rpch)
return false;
TCHAR ch = *rpch;
if (ch == 0 || ch == TEXT('/') || ch == TEXT('-'))
return false; // no value present
for (int i = 0; (ch = *rpch) != TEXT(' ') && ch != TEXT('\t') && ch != 0; rpch = CharNext(rpch), i++)
{
if (0 == i && *rpch == TEXT('"'))
{
rpch++; // for '"'
for (; (ch = *rpch) != TEXT('"') && ch != 0; rpch++)
;
ch = *(++rpch);
break;
}
}
if (ch != 0)
{
TCHAR* pch = rpch;
rpch = CharNext(rpch);
*pch = 0;
}
return true;
}
//_____________________________________________________________________________________________________
//
// RemoveQuotes
//
// Removes enclosing quotation marks for the value. Returns copy of original string
// if no enclosing quotation mark at front of string. Returns false on error.
//_____________________________________________________________________________________________________
bool RemoveQuotes(LPCTSTR lpOriginal, __out_ecount(cchStripped) LPTSTR lpStripped, DWORD cchStripped)
{
if (!lpOriginal)
return false;
bool fEnclosedInQuotes = false;
const TCHAR *pchOrig = lpOriginal;
// check for "
if (*pchOrig == TEXT('"'))
{
fEnclosedInQuotes = true;
pchOrig++;
}
if (FAILED(StringCchCopy(lpStripped, cchStripped, pchOrig)))
return false;
if (!fEnclosedInQuotes)
return true;
TCHAR *pch = lpStripped + lstrlen(lpStripped) + 1; // start at NULL
pch = CharPrev(lpStripped, pch);
// look for trailing "
while (pch != lpStripped)
{
if (*pch == TEXT('"'))
{
*pch = 0;
break; // only care about trailing ", and not quotes in middle
}
pch = CharPrev(lpStripped, pch);
}
return true;
}
//_____________________________________________________________________________________________________
//
// DisplayInstallResource
//_____________________________________________________________________________________________________
bool DisplayInstallResource(HMODULE hExeModule, LPCTSTR lpszType, LPCTSTR lpszName)
{
HRSRC hRsrc = 0;
HGLOBAL hGlobal = 0;
WCHAR *pch = 0;
if ((hRsrc = FindResource(hExeModule, lpszName, lpszType)) != 0
&& (hGlobal = LoadResource(hExeModule, hRsrc)) != 0
&& (pch = (WCHAR*)LockResource(hGlobal)) != 0)
{
// resource exists
g_cResources++;
if (!pch)
_tprintf(TEXT("%s = NULL\n"), lpszName);
else
{
#ifdef UNICODE
_tprintf(TEXT("%s = %s\n"), lpszName, pch);
#else // !UNICODE
unsigned int cch = WideCharToMultiByte(CP_ACP, 0, pch, -1, NULL, 0, NULL, NULL);
char *szValue = new char[cch];
if (!szValue)
{
_tprintf(TEXT("Error -- out of memory\n"));
return false;
}
ZeroMemory(szValue, cch * sizeof(char));
if (0 == WideCharToMultiByte(CP_ACP, 0, pch, -1, szValue, cch, NULL, NULL))
{
// error -- could not convert
_tprintf(TEXT("Unable to convert value. LastError = %d\n"), GetLastError());
delete [] szValue;
return false;
}
_tprintf(TEXT("%s = %s\n"), lpszName, szValue);
delete [] szValue;
#endif // UNICODE
}
}
return true;
}
//_____________________________________________________________________________________________________
//
// EnumResNamesProc
//_____________________________________________________________________________________________________
BOOL CALLBACK EnumResNamesProc(HMODULE hExeModule, LPCTSTR lpszType, __in LPTSTR lpszName, LONG_PTR /*lParam*/)
{
if (!DisplayInstallResource(hExeModule, lpszType, lpszName))
return FALSE;
return TRUE;
}
//_____________________________________________________________________________________________________
//
// DisplayResources
//_____________________________________________________________________________________________________
bool DisplayResources(LPCTSTR szExecutable)
{
// handle possibility of a relative path
TCHAR szFullPath[2*MAX_PATH] = {0};
LPTSTR lpszFilePart = 0;
int cchFullPath = 0;
if (0 == (cchFullPath = GetFullPathName(szExecutable, sizeof(szFullPath)/sizeof(TCHAR), szFullPath, &lpszFilePart)))
{
// error
_tprintf(TEXT("Unable to obtain full file path for %s. LastError = %d\n"), szExecutable, GetLastError());
return false;
}
else if (cchFullPath > sizeof(szFullPath)/sizeof(TCHAR))
{
// szFullPath buffer is too small; we could resize, but we'll simply fail here
_tprintf(TEXT("szFullPath buffer is insufficiently sized for obtaining the full path for %s\n"), szExecutable);
return false;
}
_tprintf(TEXT("\n<%s>\n\n"), szFullPath);
HMODULE hExeModule = LoadLibraryEx(szFullPath, NULL, LOAD_LIBRARY_AS_DATAFILE);
if (NULL == hExeModule)
{
// error
_tprintf(TEXT("Unable to load %s. LastError = %d\n"), szFullPath, GetLastError());
return false;
}
// only enumerate on RT_INSTALL_PROPERTY type
if (!EnumResourceNames(hExeModule, RT_INSTALL_PROPERTY, EnumResNamesProc, (LPARAM)0))
{
DWORD dwLastErr = GetLastError();
if (ERROR_RESOURCE_TYPE_NOT_FOUND == dwLastErr)
_tprintf(TEXT("No RT_INSTALL_PROPERTY resources were found.\n"));
else if (ERROR_RESOURCE_DATA_NOT_FOUND == dwLastErr)
_tprintf(TEXT("This file does not have a resource section.\n"));
else
{
// error
_tprintf(TEXT("Failed to enumerate all resources in %s. LastError = %d\n"), szFullPath, GetLastError());
FreeLibrary(hExeModule);
return false;
}
}
if (g_cResources)
{
if (1 == g_cResources)
_tprintf(TEXT("\n\n 1 RT_INSTALL_PROPERTY resource was found.\n"));
else
_tprintf(TEXT("\n\n %d RT_INSTALL_PROPERTY resources were found.\n"), g_cResources);
}
if (!FreeLibrary(hExeModule))
{
// error
_tprintf(TEXT("Failed to unload %s. LastError = %d\n"), szFullPath, GetLastError());
return false;
}
return true;
}