windows-nt/Source/XPSP1/NT/base/ntsetup/ocmanage/sysocmgr/sysocmgr.c

967 lines
24 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
#include "precomp.h"
#pragma hdrstop
#include <tchar.h>
#include <stdlib.h>
#include <CRTDBG.H>
#include <winuserp.h>
#if DBG
VOID
AssertFail(
IN PSTR FileName,
IN UINT LineNumber,
IN PSTR Condition
)
{
int i;
CHAR Name[MAX_PATH];
PCHAR p;
CHAR Msg[4096];
//
// Use dll name as caption
//
GetModuleFileNameA(NULL,Name,MAX_PATH);
if(p = strrchr(Name,'\\')) {
p++;
} else {
p = Name;
}
wsprintfA(
Msg,
"Assertion failure at line %u in file %s: %s\n\nCall DebugBreak()?",
LineNumber,
FileName,
Condition
);
OutputDebugStringA(Msg);
i = MessageBoxA(
NULL,
Msg,
p,
MB_YESNO | MB_TASKMODAL | MB_ICONSTOP | MB_SETFOREGROUND
);
if(i == IDYES) {
DebugBreak();
}
}
#define MYASSERT(x) if(!(x)) { AssertFail(__FILE__,__LINE__,#x); }
#else
#define MYASSERT( exp )
#endif // DBG
//
// App instance.
//
HINSTANCE hInst;
//
// Global version information structure.
//
OSVERSIONINFO OsVersionInfo;
//
// Specification of master inf, from command line.
//
TCHAR InfPath[MAX_PATH];
TCHAR InfDir[MAX_PATH];
//
// Source path for installation files, etc.
//
TCHAR SourcePath[MAX_PATH];
TCHAR UnattendPath[MAX_PATH];
// If Unattened
BOOL bUnattendInstall;
//
// Whether to force the specified master inf to be treated as new
// (from command line)
//
BOOL ForceNewInf;
//
// whether we need to pass language callback to components
//
BOOL LanguageAware;
//
// Whether to run without UI
//
BOOL QuietMode;
//
// Whether to delete all subcomponent entries listed in the master inf
// (from command line)
//
BOOL KillSubcomponentEntries;
// If set and /U then reboot is suppressed
BOOL bNoReboot;
// if this is set and we're running /unattend, then warn on reboot
BOOL bWarnOnReboot;
// if this is set then we want sysocmgr.exe to enforce the admin check.
BOOL bDoAdminCheck = FALSE;
// Flag for Derminineing Starting or Ending message
BOOL bStarting;
//
// OC Manager context 'handle'
//
PVOID OcManagerContext;
//
// Generic app title string id.
//
UINT AppTitleStringId;
BOOL NeedToReboot;
BOOL SkipBillboard;
BOOL ForceExternalProgressIndicator;
BOOL AllowCancel = TRUE;
VOID
OcSetReboot(
VOID
);
//
// Callback routines.
//
OCM_CLIENT_CALLBACKS CallbackRoutines = {
OcFillInSetupDataA,
OcLogError,
OcSetReboot
#ifdef UNICODE
,OcFillInSetupDataW
#endif
,NULL, // No callback for show,hide wizard
NULL, // No callback for progress feedback, they are only needed for setup
NULL, // No callback to set the progress text
NULL // No logging callback.
};
BOOL
DoIt(
VOID
);
BOOL
ParseArgs(
IN int argc,
IN TCHAR *argv[]
);
DWORD
ExpandPath(
IN LPCTSTR lpFileName,
OUT LPTSTR lpBuffer,
OUT LPTSTR *lpFilePart
);
void
ShutDown()
{
extern void RestartDialogEx(VOID *, VOID *, DWORD, DWORD);
if (!bNoReboot) {
if ( bUnattendInstall && !bWarnOnReboot ) {
//
// NT is always UNICODE and W9x is alwasy Ansii
//
#ifdef UNICODE
HANDLE hToken; TOKEN_PRIVILEGES tkp; // Get a token for this process.
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &hToken))
sapiAssert("OpenProcessToken"); // Get the LUID for the shutdown privilege.
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1; // one privilege to set
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Get the shutdown privilege for this process.
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
// Cannot test the return value of AdjustTokenPrivileges.
if (GetLastError() == ERROR_SUCCESS) {
sapiAssert("AdjustTokenPrivileges");
}
#endif
//
// Shut down the system and force all applications to close.
//
if (! ExitWindowsEx(EWX_REBOOT|EWX_FORCE , 0) ) {
_RPT0(_CRT_WARN,"Sysocmgr:Failed to ExitWindows");
sapiAssert(FALSE);
}
} else {
RestartDialogEx(NULL,NULL,EWX_REBOOT, SHTDN_REASON_MAJOR_OPERATINGSYSTEM | SHTDN_REASON_MINOR_RECONFIG \
| SHTDN_REASON_FLAG_PLANNED );
}
}
}
VOID
__cdecl
#ifdef UNICODE
wmain(
#else
main(
#endif
IN int argc,
IN TCHAR *argv[]
)
{
INITCOMMONCONTROLSEX ControlInit;
//
// Preliminaries
//
ControlInit.dwSize = sizeof(INITCOMMONCONTROLSEX);
ControlInit.dwICC = ICC_LISTVIEW_CLASSES |
ICC_TREEVIEW_CLASSES |
ICC_BAR_CLASSES |
ICC_TAB_CLASSES |
ICC_UPDOWN_CLASS |
ICC_PROGRESS_CLASS |
ICC_HOTKEY_CLASS |
ICC_ANIMATE_CLASS |
ICC_WIN95_CLASSES |
ICC_DATE_CLASSES |
ICC_USEREX_CLASSES |
ICC_COOL_CLASSES;
#if (_WIN32_IE >= 0x0400)
ControlInit.dwICC = ControlInit.dwICC |
ICC_INTERNET_CLASSES |
ICC_PAGESCROLLER_CLASS;
#endif
InitCommonControlsEx( &ControlInit );
hInst = GetModuleHandle(NULL);
OsVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&OsVersionInfo);
AppTitleStringId = IS_NT() ? IDS_WINNT_SETUP : IDS_WIN9X_SETUP;
//
// Parse arguments and do it.
//
if (ParseArgs(argc,argv)) {
DoIt();
}
//
// If we need to reboot, do that now.
//
if (NeedToReboot) {
ShutDown();
}
}
BOOL
ParseArgs(
IN int argc,
IN TCHAR *argv[]
)
/*++
Routine Description:
Parse and syntactically validate arguments specified on the comment line.
The following arguments are valid:
/a forces the external progress indicator on the setup page
/c disallow cancel during final installation phase
/i:<master_oc_inf> specifies the master OC inf (required).
/n forces specified master_oc_inf to be treated as new.
/s:<master_oc_inf> specifies the source path (required).
/u:<unattend_spec> specifies unattended operation parameters.
/x supresses the 'initializing' banner
/q run wizard invisibly
/r supress reboot if need on unattended operation
/w warn on reboot on unattended operation
Arguments:
Standard main argc/argv.
Return Value:
Boolean value indicating whether the arguments specified are valid.
If successful, various global variables will have been filled in.
If not, the user will have been informed.
--*/
{
BOOL Valid;
LPCTSTR SourcePathSpec = NULL;
LPCTSTR InfSpec = NULL;
LPCTSTR UnattendSpec = NULL;
LPTSTR FilePart;
DWORD u;
//
// Skip program name.
//
if (argc) {
argc--;
}
Valid = TRUE;
ForceNewInf = FALSE;
QuietMode = FALSE;
KillSubcomponentEntries = FALSE;
while (Valid && argc--) {
argv++;
if ((argv[0][0] == TEXT('-')) || (argv[0][0] == TEXT('/'))) {
switch (argv[0][1]) {
case TEXT('a'):
case TEXT('A'):
if (!ForceExternalProgressIndicator && !argv[0][2]) {
ForceExternalProgressIndicator = TRUE;
} else {
Valid = FALSE;
}
break;
case TEXT('c'):
case TEXT('C'):
if (AllowCancel && !argv[0][2]) {
AllowCancel = FALSE;
} else {
Valid = FALSE;
}
break;
case TEXT('f'):
case TEXT('F'):
ForceNewInf = TRUE;
KillSubcomponentEntries = TRUE;
break;
case TEXT('i'):
case TEXT('I'):
if (!InfSpec && (argv[0][2] == TEXT(':')) && argv[0][3]) {
InfSpec = &(argv[0][3]);
} else {
Valid = FALSE;
}
break;
case TEXT('l'):
case TEXT('L'):
LanguageAware = TRUE;
break;
case TEXT('n'):
case TEXT('N'):
ForceNewInf = TRUE;
break;
case TEXT('q'):
case TEXT('Q'):
if (!QuietMode && !argv[0][2]) {
QuietMode = TRUE;
SkipBillboard = TRUE;
} else {
Valid = FALSE;
}
break;
case TEXT('r'):
case TEXT('R'):
bNoReboot = TRUE;
break;
case TEXT('s'):
case TEXT('S'):
if (!SourcePathSpec && (argv[0][2] == TEXT(':')) && argv[0][3]) {
SourcePathSpec = &argv[0][3];
} else {
Valid = FALSE;
}
break;
case TEXT('u'):
case TEXT('U'):
//
// accept unattend, unattended, u all as the same
//
if(!_tcsnicmp(&argv[0][1],TEXT("unattended"),10)) {
u = 11;
} else if(!_tcsnicmp(&argv[0][1],TEXT("unattend"),8)) {
u = 9;
} else if(!_tcsnicmp(&argv[0][1],TEXT("u"),1)) {
u = 2;
} else {
Valid = FALSE;
u = 0;
}
if (!UnattendSpec ) {
bUnattendInstall = TRUE;
// If you have the : then you must also have the arg
if (argv[0][u] == TEXT(':')) {
if ( argv[0][u+1]) {
UnattendSpec = &argv[0][u+1];
} else {
Valid = FALSE;
}
} else {
Valid = FALSE;
}
} else {
Valid = FALSE;
}
break;
case TEXT('w'):
case TEXT('W'):
bWarnOnReboot = TRUE;
break;
case TEXT('x'):
case TEXT('X'):
if (!SkipBillboard && !argv[0][2]) {
SkipBillboard = TRUE;
} else {
Valid = FALSE;
}
break;
// For ISSUE NTBUG9:295052 (389583): We want to do a top level admin check so we get a more friendly message.
// It is possible for people to have been using sysocmgr.exe with their own custom master oc.inf
// (the one passed in with the /i: switch) and they may not need this admin check. So, we did
// not want to do this admin check unconditionally. We will have the control panel applet that
// is launching sysocmgr.exe to pass in this /y switch.
//
case TEXT('y'):
case TEXT('Y'):
bDoAdminCheck = TRUE;
break;
case TEXT('z'):
case TEXT('Z'):
// Stop parsing Arguments All other args past this point are
// Component Arguments
argc = 0;
break;
default:
Valid = FALSE;
break;
}
} else {
Valid = FALSE;
}
}
if (Valid && !InfSpec) {
Valid = FALSE;
}
if (Valid) {
//
// Expand the inf spec to a full path.
//
ExpandPath(InfSpec,InfPath,&FilePart);
_tcscpy(InfDir, InfSpec);
if (_tcsrchr(InfDir, TEXT('\\')))
*_tcsrchr(InfDir,TEXT('\\')) = 0;
else
GetCurrentDirectory(MAX_PATH, InfDir);
// If the user specified /s then expand it too, otherwise
// use the dir in the /i as the /s arg.
if (SourcePathSpec) {
ExpandPath(SourcePathSpec,SourcePath,&FilePart);
} else {
lstrcpy(SourcePath,InfPath);
if (_tcsrchr(SourcePath,TEXT('\\'))) {
*_tcsrchr(SourcePath,TEXT('\\')) = 0;
}
}
SetCurrentDirectory(InfDir);
if (UnattendSpec) {
ExpandPath(UnattendSpec,UnattendPath,&FilePart);
}else{
// Allow /Q only if /U was specified
QuietMode = FALSE;
SkipBillboard = FALSE;
}
} else {
MessageBoxFromMessage(
NULL,
MSG_ARGS,
FALSE,
MAKEINTRESOURCE(AppTitleStringId),
MB_OK | MB_ICONERROR | MB_SETFOREGROUND | MB_SYSTEMMODAL
);
}
return (Valid);
}
INT_PTR
BillboardDlgProc(
IN HWND hdlg,
IN UINT msg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
BOOL b;
RECT rect1,rect2;
static HCURSOR hOldCursor;
switch (msg) {
case WM_INITDIALOG:
//
// Center on-screen.
//
GetWindowRect(hdlg,&rect1);
SystemParametersInfo(SPI_GETWORKAREA,0,&rect2,0);
MoveWindow(
hdlg,
rect2.left + (((rect2.right - rect2.left) - (rect1.right - rect1.left)) / 2),
rect2.top + (((rect2.bottom - rect2.top) - (rect1.bottom - rect1.top)) / 2),
rect1.right - rect1.left,
rect1.bottom - rect1.top,
FALSE
);
*(HWND *)lParam = hdlg;
b = TRUE;
hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
break;
case WM_APP:
EndDialog(hdlg,0);
SetCursor( hOldCursor );
b = TRUE;
break;
default:
b = FALSE;
break;
}
return (b);
}
DWORD
DisplayMessage(
IN LPVOID ThreadParameter
)
{
int i;
i = (int)DialogBoxParam(
hInst,
MAKEINTRESOURCE(bStarting?IDD_STARTING:IDD_FINISHING),
NULL,
BillboardDlgProc,
(LPARAM)ThreadParameter
);
if (i == -1) {
//
// Force caller out of wait loop
//
*(HWND *)ThreadParameter = (HWND)(-1);
}
return (0);
}
/*---------------------------------------------------------------------------*\
Function: RunningAsAdministrator()
|*---------------------------------------------------------------------------*|
Description: Checks whether we are running as administrator on the machine
or not.
Code taken from ntoc.dll
\*---------------------------------------------------------------------------*/
BOOL
RunningAsAdministrator(
VOID
)
{
BOOL fAdmin;
HANDLE hThread;
TOKEN_GROUPS *ptg = NULL;
DWORD cbTokenGroups;
DWORD dwGroup;
PSID psidAdmin;
SID_IDENTIFIER_AUTHORITY SystemSidAuthority= SECURITY_NT_AUTHORITY;
// First we must open a handle to the access token for this thread.
if ( !OpenThreadToken ( GetCurrentThread(), TOKEN_QUERY, FALSE, &hThread))
{
if ( GetLastError() == ERROR_NO_TOKEN)
{
// If the thread does not have an access token, we'll examine the
// access token associated with the process.
if (! OpenProcessToken ( GetCurrentProcess(), TOKEN_QUERY,
&hThread))
return ( FALSE);
}
else
return ( FALSE);
}
// Then we must query the size of the group information associated with
// the token. Note that we expect a FALSE result from GetTokenInformation
// because we've given it a NULL buffer. On exit cbTokenGroups will tell
// the size of the group information.
if ( GetTokenInformation ( hThread, TokenGroups, NULL, 0, &cbTokenGroups))
return ( FALSE);
// Here we verify that GetTokenInformation failed for lack of a large
// enough buffer.
if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER)
return ( FALSE);
// Now we allocate a buffer for the group information.
// Since _alloca allocates on the stack, we don't have
// to explicitly deallocate it. That happens automatically
// when we exit this function.
if ( ! ( ptg= (TOKEN_GROUPS *)malloc ( cbTokenGroups)))
return ( FALSE);
// Now we ask for the group information again.
// This may fail if an administrator has added this account
// to an additional group between our first call to
// GetTokenInformation and this one.
if ( !GetTokenInformation ( hThread, TokenGroups, ptg, cbTokenGroups,
&cbTokenGroups) )
{
free(ptg);
return ( FALSE);
}
// Now we must create a System Identifier for the Admin group.
if ( ! AllocateAndInitializeSid ( &SystemSidAuthority, 2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, &psidAdmin) )
{
free(ptg);
return ( FALSE);
}
// Finally we'll iterate through the list of groups for this access
// token looking for a match against the SID we created above.
fAdmin= FALSE;
for ( dwGroup= 0; dwGroup < ptg->GroupCount; dwGroup++)
{
if ( EqualSid ( ptg->Groups[dwGroup].Sid, psidAdmin))
{
fAdmin = TRUE;
break;
}
}
// Before we exit we must explicity deallocate the SID we created.
FreeSid ( psidAdmin);
free(ptg);
return ( fAdmin);
}
BOOL
DoIt(
VOID
)
{
BOOL ShowErr;
HANDLE hThread;
DWORD ThreadId;
HANDLE hMutex;
TCHAR Fname[MAX_PATH];
TCHAR MutexName[MAX_PATH];
DWORD Flags;
HWND StartingMsgWindow = NULL;
HCURSOR hOldCursor;
if (bDoAdminCheck && !RunningAsAdministrator()) {
MessageBoxFromMessage(
StartingMsgWindow,
MSG_NOT_ADMIN,
FALSE,
MAKEINTRESOURCE(AppTitleStringId),
MB_OK | MB_ICONERROR | MB_SETFOREGROUND | MB_SYSTEMMODAL
);
return FALSE;
}
//
// Create a Mutex from the Base Name of the Inf file
// This will prevent OCM from running on the same inf file
// in two or more instances
//
_tsplitpath( InfPath, NULL, NULL, Fname, NULL );
lstrcpy( MutexName, TEXT("Global\\"));
lstrcat( MutexName, Fname );
hMutex = CreateMutex( NULL, TRUE, MutexName );
if (!hMutex || ERROR_ALREADY_EXISTS == GetLastError()) {
MessageBoxFromMessage(
NULL,
MSG_ONLY_ONE_INST,
FALSE,
MAKEINTRESOURCE(AppTitleStringId),
MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND | MB_SYSTEMMODAL
);
ReleaseMutex(hMutex);
return FALSE;
}
//
// Initialize the OC Manager. Show the user an "initializing setup"
// dialog while this is happening, as it can take a while.
//
if (!SkipBillboard) {
bStarting = TRUE;
StartingMsgWindow = NULL;
hThread = CreateThread(
NULL,
0,
DisplayMessage,
&StartingMsgWindow,
0,
&ThreadId
);
if (hThread) {
CloseHandle(hThread);
Sleep(50);
} else {
DisplayMessage(0);
}
}
//
// Make sure the window has actually been created,
// or we could have a timing window where the PostMessage fails
// and the billboard shows up on top of the wizard.
//
if (!SkipBillboard) {
while (!StartingMsgWindow) {
Sleep(50);
}
}
Flags = ForceNewInf ? OCINIT_FORCENEWINF : 0;
Flags |= KillSubcomponentEntries ? OCINIT_KILLSUBCOMPS : 0;
Flags |= QuietMode ? OCINIT_RUNQUIET : 0;
Flags |= LanguageAware ? OCINIT_LANGUAGEAWARE : 0 ;
hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
OcManagerContext = OcInitialize(
&CallbackRoutines,
InfPath,
Flags,
&ShowErr,
NULL
);
if (!OcManagerContext) {
SetCursor( hOldCursor );
if (ShowErr) {
MessageBoxFromMessage(
StartingMsgWindow,
MSG_CANT_INIT,
FALSE,
MAKEINTRESOURCE(AppTitleStringId),
MB_OK | MB_ICONERROR | MB_SETFOREGROUND | MB_SYSTEMMODAL
);
}
ReleaseMutex(hMutex);
return (FALSE);
}
//
// Do the wizard.
//
DoWizard(OcManagerContext,StartingMsgWindow, hOldCursor);
SetCursor( hOldCursor );
hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
// the Terminate can take a while too..
if (!SkipBillboard) {
bStarting = FALSE;
StartingMsgWindow = NULL;
hThread = CreateThread(
NULL,
0,
DisplayMessage,
&StartingMsgWindow,
0,
&ThreadId
);
if (hThread) {
CloseHandle(hThread);
Sleep(50);
} else {
DisplayMessage(0);
}
}
//
// Clean up, we're done.
//
OcTerminate(&OcManagerContext);
if (!SkipBillboard) {
//
// Make sure the window has actually been created,
// or we could have a timing window where the PostMessage fails
// and the billboard shows up on top of the wizard.
//
while (!StartingMsgWindow) {
Sleep(50);
}
SendMessage(StartingMsgWindow,WM_APP,0,0);
}
ReleaseMutex(hMutex);
SetCursor( hOldCursor );
return (TRUE);
}
VOID
OcSetReboot(
VOID
)
{
NeedToReboot = TRUE;
}
DWORD
ExpandPath(
IN LPCTSTR lpFileName,
OUT LPTSTR lpBuffer,
OUT LPTSTR *lpFilePart
)
{
TCHAR buf[MAX_PATH];
ExpandEnvironmentStrings(lpFileName, buf, MAX_PATH);
return GetFullPathName(buf, MAX_PATH, lpBuffer, lpFilePart);
}