mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2044 lines
56 KiB
2044 lines
56 KiB
/****************************************************************************
|
|
*
|
|
* icfg32.cpp
|
|
*
|
|
* Microsoft Confidential
|
|
* Copyright (c) 1992-1998 Microsoft Corporation
|
|
* All rights reserved
|
|
*
|
|
* This module provides the implementation of the methods for
|
|
* the NT specific functionality of inetcfg
|
|
*
|
|
* 6/5/97 ChrisK Inherited from AmnonH
|
|
*
|
|
***************************************************************************/
|
|
|
|
#define INITGUID
|
|
#include <windows.h>
|
|
#include <wtypes.h>
|
|
#include <cfgapi.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <setupapi.h>
|
|
#include <basetyps.h>
|
|
#include <devguid.h>
|
|
#include <lmsname.h>
|
|
#include "debug.h"
|
|
|
|
const DWORD INFINSTALL_PRIMARYINSTALL = 0x00000001;
|
|
const DWORD INFINSTALL_INPROCINTERP = 0x00000002;
|
|
|
|
#define REG_DATA_EXTRA_SPACE 255
|
|
|
|
DWORD (WINAPI *pfnNetSetupReviewBindings)(HWND hwndParent,
|
|
DWORD dwBindFlags);
|
|
DWORD (WINAPI *pfnNetSetupComponentInstall)(HWND hwndParent,
|
|
PCWSTR pszInfOption,
|
|
PCWSTR pszInfName,
|
|
PCWSTR pszInstallPath,
|
|
PCWSTR plszInfSymbols,
|
|
DWORD dwInstallFlags,
|
|
PDWORD dwReturn);
|
|
DWORD (WINAPI *pfnNetSetupComponentRemove)(HWND hwndParent,
|
|
PCWSTR pszInfOption,
|
|
DWORD dwInstallFlags,
|
|
PDWORD pdwReturn);
|
|
DWORD (WINAPI *pfnNetSetupComponentProperties)(HWND hwndParent,
|
|
PCWSTR pszInfOption,
|
|
DWORD dwInstallFlags,
|
|
PDWORD pdwReturn);
|
|
DWORD (WINAPI *pfnNetSetupFindHardwareComponent)(PCWSTR pszInfOption,
|
|
PWSTR pszInfName,
|
|
PDWORD pcchInfName,
|
|
PWSTR pszRegBase, // optional, may be NULL
|
|
PDWORD pcchRegBase ); // optional, NULL if pszRegBase is NULL
|
|
DWORD (WINAPI *pfnNetSetupFindSoftwareComponent)(PCWSTR pszInfOption,
|
|
PWSTR pszInfName,
|
|
PDWORD pcchInfName,
|
|
PWSTR pszRegBase = NULL,
|
|
PDWORD pcchRegBase = NULL);
|
|
DWORD (WINAPI *pfnRegCopyTree)();
|
|
|
|
HINSTANCE g_hNetcfgInst = NULL;
|
|
LPWSTR g_wszInstallPath = 0;
|
|
DWORD g_dwLastError = ERROR_SUCCESS;
|
|
extern DWORD EnumerateTapiModemPorts(DWORD dwBytes, LPSTR szPortsBuf,
|
|
BOOL bWithDelay = FALSE);
|
|
|
|
typedef struct tagFunctionTableEntry {
|
|
LPVOID *pfn;
|
|
LPSTR szEntryPoint;
|
|
} FunctionTableEntry;
|
|
|
|
#define REGISTRY_NT_CURRENTVERSION "SOFTWARE\\MICROSOFT\\WINDOWS NT\\CurrentVersion"
|
|
|
|
FunctionTableEntry NetcfgTable[] = {
|
|
{ (LPVOID *) &pfnNetSetupComponentInstall, "NetSetupComponentInstall" },
|
|
{ (LPVOID *) &pfnNetSetupFindSoftwareComponent, "NetSetupFindSoftwareComponent" },
|
|
{ (LPVOID *) &pfnNetSetupReviewBindings, "NetSetupReviewBindings" },
|
|
{ (LPVOID *) &pfnNetSetupComponentRemove, "NetSetupComponentRemove" },
|
|
{ (LPVOID *) &pfnNetSetupComponentProperties, "NetSetupComponentProperties" },
|
|
{ (LPVOID *) &pfnNetSetupFindHardwareComponent, "NetSetupFindHardwareComponent" },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
typedef struct tagNetSetup
|
|
{
|
|
WCHAR szOption[16];
|
|
WCHAR szInfName[16];
|
|
} NETSETUP;
|
|
|
|
NETSETUP g_netsetup[] = { L"WKSTA", L"OEMNSVWK.INF",
|
|
L"SRV", L"OEMNSVSV.INF",
|
|
L"NETBIOS", L"OEMNSVNB.INF",
|
|
L"RPCLOCATE", L"OEMNSVRP.INF" };
|
|
|
|
#define NSERVICES (sizeof g_netsetup / sizeof g_netsetup[0])
|
|
|
|
inline stricmp(LPSTR s1, LPSTR s2) {
|
|
while(*s1 && *s2) {
|
|
char c1, c2;
|
|
c1 = islower(*s1) ? toupper(*s1) : *s1;
|
|
c2 = islower(*s2) ? toupper(*s2) : *s2;
|
|
if(c1 != c2)
|
|
{
|
|
break;
|
|
}
|
|
s1++; s2++;
|
|
}
|
|
|
|
return(*s1 - *s2);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: LoadLibraryToFunctionTable
|
|
//
|
|
// Synopsis: Load structure with function pointers from FunctionTable
|
|
//
|
|
// Arguments: pTab - array of function to be loaded
|
|
// szDLL - name of DLL to load function from
|
|
//
|
|
// Returns: Handle to szDLL (NULL indicates failure)
|
|
//
|
|
// History: 6/5/97 Chrisk Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HINSTANCE
|
|
LoadLibraryToFunctionTable(FunctionTableEntry *pTab, LPSTR szDLL)
|
|
{
|
|
HINSTANCE hInst;
|
|
|
|
Dprintf("ICFGNT: LoadLibraryToFunctionTable\n");
|
|
hInst = LoadLibrary(szDLL);
|
|
if(hInst == 0)
|
|
{
|
|
return(hInst);
|
|
}
|
|
|
|
while(pTab->pfn) {
|
|
*pTab->pfn = (LPVOID) GetProcAddress(hInst, pTab->szEntryPoint);
|
|
if(*pTab->pfn == 0)
|
|
{
|
|
FreeLibrary(hInst);
|
|
return(0);
|
|
}
|
|
pTab++;
|
|
}
|
|
|
|
return(hInst);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: LoadNetcfg
|
|
//
|
|
// Synopsis: Load netcfg.dll and function poiners
|
|
//
|
|
// Arguments: none
|
|
//
|
|
// Returns: ERROR_SUCCESS if sucessfull and !ERROR_SUCCESS otherwise
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD
|
|
LoadNetcfg()
|
|
{
|
|
if(g_hNetcfgInst == NULL)
|
|
{
|
|
g_hNetcfgInst = LoadLibraryToFunctionTable(NetcfgTable,
|
|
"NETCFG.DLL");
|
|
}
|
|
|
|
if(g_hNetcfgInst == NULL)
|
|
{
|
|
return(!ERROR_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: IcfgSetInstallSourcePath
|
|
//
|
|
// Synopsis: Set the path that will be used to install system components
|
|
//
|
|
// Arguments: lpszSourcePath - path to be used as install source (ANSI)
|
|
//
|
|
// Returns: HRESULT - S_OK is success
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT WINAPI
|
|
IcfgSetInstallSourcePath(LPSTR lpszSourcePath)
|
|
{
|
|
Dprintf("ICFGNT: IcfgSetInstallSourcePath\n");
|
|
if(g_wszInstallPath)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, (LPVOID) g_wszInstallPath);
|
|
}
|
|
|
|
DWORD dwLen = lstrlen(lpszSourcePath);
|
|
g_wszInstallPath = (LPWSTR) HeapAlloc(GetProcessHeap(), 0, dwLen * 2 + 2);
|
|
if(g_wszInstallPath == 0)
|
|
{
|
|
return(g_dwLastError = ERROR_OUTOFMEMORY);
|
|
}
|
|
|
|
mbstowcs(g_wszInstallPath, lpszSourcePath, dwLen + 1);
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: GetLocationOfSetupFiles
|
|
//
|
|
// Synopsis: Get the location of the files used to install windows.
|
|
//
|
|
// Arguments: hwndParent - handle of parent window
|
|
//
|
|
// Returns: win32 error code
|
|
//
|
|
// History: ChrisK 6/30/97 Created
|
|
//-----------------------------------------------------------------------------
|
|
DWORD GetLocationOfSetupFiles(HWND hwndParent)
|
|
{
|
|
DWORD dwRC = ERROR_SUCCESS;
|
|
HKEY hkey = NULL;
|
|
HINF hInf = INVALID_HANDLE_VALUE;
|
|
UINT DiskId = 0;
|
|
CHAR TagFile[128];
|
|
CHAR lpBuffer[MAX_PATH];
|
|
DWORD dwLen = MAX_PATH;
|
|
lpBuffer[0] = '\0';
|
|
|
|
if( ERROR_SUCCESS == RegOpenKey( HKEY_LOCAL_MACHINE,
|
|
REGISTRY_NT_CURRENTVERSION,
|
|
&hkey))
|
|
{
|
|
|
|
hInf = SetupOpenMasterInf();
|
|
if (hInf == INVALID_HANDLE_VALUE)
|
|
{
|
|
dwRC = GetLastError();
|
|
goto GetLocationOfSetupFilesExit;
|
|
}
|
|
|
|
if (!SetupGetSourceFileLocation(hInf,NULL,"RASCFG.DLL",&DiskId,NULL,0,NULL))
|
|
{
|
|
dwRC = GetLastError();
|
|
goto GetLocationOfSetupFilesExit;
|
|
}
|
|
|
|
if (!SetupGetSourceInfo(hInf,DiskId,SRCINFO_TAGFILE,TagFile,MAX_PATH,NULL))
|
|
{
|
|
dwRC = GetLastError();
|
|
goto GetLocationOfSetupFilesExit;
|
|
}
|
|
|
|
SetupCloseInfFile(hInf);
|
|
hInf = INVALID_HANDLE_VALUE;
|
|
|
|
if( RegQueryValueEx( hkey,
|
|
"SourcePath",
|
|
NULL,
|
|
NULL,
|
|
(LPBYTE)lpBuffer,
|
|
&dwLen) == 0)
|
|
{
|
|
RegCloseKey( hkey );
|
|
hkey = NULL;
|
|
|
|
// Ask the user to provide the drive\path of the sources. We pass this information
|
|
// down to NetSetupComponentInstall so that the user is not prompted several times
|
|
// for the same information. If the path is correct (IDF_CHECKFIRST) then the user
|
|
// is not prompted at all.
|
|
|
|
if( (dwRC = SetupPromptForDisk(hwndParent,
|
|
NULL,
|
|
NULL,
|
|
lpBuffer,
|
|
"RASCFG.DLL",
|
|
TagFile, // tag file
|
|
IDF_CHECKFIRST,
|
|
lpBuffer,
|
|
MAX_PATH,
|
|
&dwLen
|
|
)) != DPROMPT_SUCCESS )
|
|
{
|
|
Dprintf("ICFG: Install: SetupPromptForDisk failed.\n");
|
|
dwRC = GetLastError();
|
|
goto GetLocationOfSetupFilesExit;
|
|
}
|
|
}
|
|
|
|
// If we failed to get SourcePath from registry, then prompt the user once and use
|
|
// this information for subsequent installs.
|
|
|
|
else
|
|
{
|
|
if( (dwRC = SetupPromptForDisk(hwndParent,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
"RASCFG.DLL",
|
|
TagFile, // tag file
|
|
IDF_CHECKFIRST,
|
|
lpBuffer,
|
|
MAX_PATH,
|
|
&dwLen
|
|
)) != DPROMPT_SUCCESS )
|
|
{
|
|
Dprintf("ICFG: Install: SetupPromptForDisk failed.\n");
|
|
dwRC = GetLastError();
|
|
goto GetLocationOfSetupFilesExit;
|
|
}
|
|
}
|
|
}
|
|
GetLocationOfSetupFilesExit:
|
|
if (ERROR_SUCCESS == dwRC)
|
|
{
|
|
IcfgSetInstallSourcePath(lpBuffer);
|
|
}
|
|
|
|
if (INVALID_HANDLE_VALUE != hInf)
|
|
{
|
|
SetupCloseInfFile(hInf);
|
|
hInf = NULL;
|
|
}
|
|
|
|
if (NULL != hkey)
|
|
{
|
|
RegCloseKey(hkey);
|
|
}
|
|
return dwRC;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: InstallNTNetworking
|
|
//
|
|
// Synopsis: Install NT Server, workstation, netbios, and RPC locator
|
|
// services as needed
|
|
//
|
|
// Arguemtns: hwndParent - parent window
|
|
//
|
|
// Returns: win32 error code
|
|
//
|
|
// History: ChrisK 6/27/97 Created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD InstallNTNetworking(HWND hwndParent)
|
|
{
|
|
DWORD dwRC = ERROR_SUCCESS;
|
|
UINT index = 0;
|
|
DWORD cchInfName = MAX_PATH;
|
|
WCHAR pszInfName[MAX_PATH+1];
|
|
SC_HANDLE hscman, hsvc;
|
|
DWORD dwReturn;
|
|
|
|
Dprintf("ICFGNT.DLL: InstallNTNetworking.\n");
|
|
|
|
Assert(NULL == hwndParent || IsWindow(hwndParent));
|
|
|
|
if(ERROR_SUCCESS != (dwRC = LoadNetcfg()))
|
|
{
|
|
Dprintf("ICFGNT.DLL: Failed load Netcfg API's, error %d\n",dwRC);
|
|
goto InstallNTNetworkingExit;
|
|
}
|
|
|
|
//
|
|
// Check for and install services
|
|
//
|
|
for (index = 0; index < NSERVICES; index++)
|
|
{
|
|
Dprintf("ICFGNT.DLL: Check service %d\n",index);
|
|
|
|
//
|
|
// Install service if it is not installed
|
|
//
|
|
if(pfnNetSetupFindSoftwareComponent(
|
|
g_netsetup[index].szOption, // OPTION
|
|
pszInfName, // INF Name
|
|
&cchInfName,
|
|
NULL,
|
|
NULL) != ERROR_SUCCESS )
|
|
{
|
|
|
|
if (0 == g_wszInstallPath || 0 == lstrlenW(g_wszInstallPath))
|
|
{
|
|
GetLocationOfSetupFiles(hwndParent);
|
|
}
|
|
|
|
Dprintf("ICFGNT.DLL: Need service %d.\n",index);
|
|
if((dwRC = pfnNetSetupComponentInstall(
|
|
hwndParent,
|
|
g_netsetup[index].szOption, // OPTION
|
|
g_netsetup[index].szInfName, // INF Name
|
|
g_wszInstallPath, // Install path optional
|
|
NULL, // symbols, optional
|
|
2, // INFINSTALL_INPROCINTERP
|
|
&dwReturn)) != ERROR_SUCCESS )
|
|
{
|
|
Dprintf("ICFGNT.DLL: Installing service %d failed with error %d.\n",
|
|
index,
|
|
dwRC);
|
|
goto InstallNTNetworkingExit;
|
|
}
|
|
|
|
if (!lstrcmpiW(g_netsetup[index].szOption, L"WKSTA"))
|
|
{
|
|
// if we installed the Workstation service, then we should disable
|
|
// Netlogon service. We need to do this because netlogon service should
|
|
// not be set to autostart if the user has not joined a domain.
|
|
//
|
|
|
|
hscman = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS | GENERIC_WRITE );
|
|
if( hscman == NULL)
|
|
{
|
|
dwRC = GetLastError();
|
|
Dprintf("ICFGNT.DLL: Failed to open serviceManager, error %d\n",dwRC);
|
|
goto InstallNTNetworkingExit;
|
|
}
|
|
|
|
hsvc = OpenService( hscman, SERVICE_NETLOGON, SERVICE_CHANGE_CONFIG );
|
|
if ( hsvc == NULL)
|
|
{
|
|
dwRC = GetLastError();
|
|
Dprintf("ICFGNT.DLL: Failed to open service, error %d\n",dwRC);
|
|
goto InstallNTNetworkingExit;
|
|
}
|
|
ChangeServiceConfig( hsvc, SERVICE_NO_CHANGE, SERVICE_DEMAND_START, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
CloseServiceHandle(hsvc);
|
|
CloseServiceHandle(hscman);
|
|
}
|
|
}
|
|
}
|
|
InstallNTNetworkingExit:
|
|
|
|
return dwRC;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: GetRegValue
|
|
//
|
|
// Synopsis: Dynamically allocate memory and read value from registry
|
|
//
|
|
// Arguments: hKey - handle to key to be read
|
|
// lpValueName - pointer to value name to be read
|
|
// lpData - pointer to pointer to data
|
|
//
|
|
// Returns: Win32 error, ERROR_SUCCESS is it worked
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
inline LONG GetRegValue(HKEY hKey, LPSTR lpValueName, LPBYTE *lpData)
|
|
{
|
|
LONG dwError;
|
|
DWORD cbData;
|
|
|
|
Dprintf("ICFGNT: GetRegValue\n");
|
|
dwError = RegQueryValueExA(hKey,
|
|
lpValueName,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&cbData);
|
|
if(dwError != ERROR_SUCCESS)
|
|
{
|
|
return(dwError);
|
|
}
|
|
|
|
//
|
|
// Allocate space and buffer incase we need to add more info later
|
|
// see turn off the printing binding
|
|
//
|
|
*lpData = (LPBYTE) GlobalAlloc(GPTR,cbData + REG_DATA_EXTRA_SPACE);
|
|
if(*lpData == 0)
|
|
{
|
|
return(ERROR_OUTOFMEMORY);
|
|
}
|
|
|
|
dwError = RegQueryValueExA(hKey,
|
|
lpValueName,
|
|
NULL,
|
|
NULL,
|
|
*lpData,
|
|
&cbData);
|
|
if(dwError != ERROR_SUCCESS)
|
|
{
|
|
GlobalFree(*lpData);
|
|
}
|
|
|
|
return(dwError);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: ParseNetSetupReturn
|
|
//
|
|
// Synopsis: Interprit return values from NetSetup* functions
|
|
//
|
|
// Arguments: dwReturn - return value from NetSetup* function
|
|
//
|
|
// Returns: fReboot - TRUE means reboot required
|
|
// fBindReview - TRUE means binding review is required
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
inline void
|
|
ParseNetSetupReturn(DWORD dwReturn, BOOL &fReboot, BOOL &fBindReview)
|
|
{
|
|
Dprintf("ICFGNT: ParseNetSetupReturn\n");
|
|
if(dwReturn == 0 || dwReturn == 4)
|
|
{
|
|
fBindReview = TRUE;
|
|
}
|
|
if(dwReturn == 0 || dwReturn == 5)
|
|
{
|
|
fReboot = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: ReviewBindings
|
|
//
|
|
// Synopsis: Force WinNT to review network bindings
|
|
//
|
|
// Arguments: hwndParent - handle to parent window
|
|
//
|
|
// Returns: win32 error code (ERROR_SUCCESS means it worked)
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD
|
|
ReviewBindings(HWND hwndParent)
|
|
{
|
|
DWORD dwErr;
|
|
|
|
Dprintf("ICFGNT: ReviewBindings\n");
|
|
dwErr = LoadNetcfg();
|
|
if(dwErr != ERROR_SUCCESS)
|
|
{
|
|
return(dwErr);
|
|
}
|
|
|
|
return(pfnNetSetupReviewBindings(hwndParent, 0));
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: CallModemInstallWizard
|
|
//
|
|
// Synopsis: Invoke modem install wizard via SetupDi interfaces
|
|
//
|
|
// Arguments: hwnd - handle to parent window
|
|
//
|
|
// Returns: TRUE - success, FALSE - failed
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// The following code was stolen from RAS
|
|
//
|
|
|
|
BOOL
|
|
CallModemInstallWizard(HWND hwnd)
|
|
/* call the Modem.Cpl install wizard to enable the user to install one or more modems
|
|
**
|
|
** Return TRUE if the wizard was successfully invoked, FALSE otherwise
|
|
**
|
|
*/
|
|
{
|
|
HDEVINFO hdi;
|
|
BOOL fReturn = FALSE;
|
|
// Create a modem DeviceInfoSet
|
|
|
|
Dprintf("ICFGNT: CallModemInstallWizard\n");
|
|
hdi = SetupDiCreateDeviceInfoList((LPGUID)&GUID_DEVCLASS_MODEM, hwnd);
|
|
if (hdi)
|
|
{
|
|
SP_INSTALLWIZARD_DATA iwd;
|
|
|
|
// Initialize the InstallWizardData
|
|
|
|
ZeroMemory(&iwd, sizeof(iwd));
|
|
iwd.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
|
|
iwd.ClassInstallHeader.InstallFunction = DIF_INSTALLWIZARD;
|
|
iwd.hwndWizardDlg = hwnd;
|
|
|
|
// Set the InstallWizardData as the ClassInstallParams
|
|
|
|
if (SetupDiSetClassInstallParams(hdi, NULL, (PSP_CLASSINSTALL_HEADER)&iwd, sizeof(iwd)))
|
|
{
|
|
// Call the class installer to invoke the installation
|
|
// wizard.
|
|
if (SetupDiCallClassInstaller(DIF_INSTALLWIZARD, hdi, NULL))
|
|
{
|
|
// Success. The wizard was invoked and finished.
|
|
// Now cleanup.
|
|
fReturn = TRUE;
|
|
|
|
SetupDiCallClassInstaller(DIF_DESTROYWIZARDDATA, hdi, NULL);
|
|
}
|
|
}
|
|
|
|
// Clean up
|
|
SetupDiDestroyDeviceInfoList(hdi);
|
|
}
|
|
return fReturn;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: IsDialableISDNAdapters
|
|
//
|
|
// Synopsis: Some ISDN adapters can be installed as RAS devices, but not as
|
|
// unimodem devices, so we have to walk through the rest of the
|
|
// TAPI devices looking for these.
|
|
//
|
|
// Arguments: None
|
|
//
|
|
// Returns: TRUE - there is a device available
|
|
//
|
|
// History: 7/22/97 ChrisK Created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
#define REG_TAPIDEVICES "software\\microsoft\\ras\\tapi devices"
|
|
LPSTR szAddress = "Address";
|
|
LPSTR szUsage = "Usage";
|
|
LPSTR szMediaType = "Media Type";
|
|
BOOL IsDialableISDNAdapters()
|
|
{
|
|
BOOL bRC = FALSE;
|
|
|
|
HKEY hkey = NULL, hsubkey = NULL;
|
|
DWORD dwIdx = 0;
|
|
CHAR szBuffer[MAX_PATH + 1];
|
|
CHAR szSubKey[MAX_PATH + 1];
|
|
LPBYTE lpData = NULL;
|
|
LPSTR lpsUsage = NULL;
|
|
szBuffer[0] = '\0';
|
|
|
|
//
|
|
// Open TAPI device key
|
|
//
|
|
if (ERROR_SUCCESS != RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
REG_TAPIDEVICES,
|
|
&hkey))
|
|
{
|
|
Dprintf("ICFGNT Can not open TAPI key.\n");
|
|
goto IsDialableISDNAdaptersExit;
|
|
}
|
|
|
|
//
|
|
// Scan for non unimodem device
|
|
//
|
|
|
|
while (FALSE == bRC)
|
|
{
|
|
szBuffer[0] = '\0';
|
|
if (ERROR_SUCCESS != RegEnumKey(hkey,dwIdx,szBuffer,MAX_PATH))
|
|
{
|
|
goto IsDialableISDNAdaptersExit;
|
|
}
|
|
Dprintf("ICFGNT sub key (%s) found.\n",szBuffer);
|
|
|
|
if (0 != lstrcmpi(szBuffer,"unimodem"))
|
|
{
|
|
//
|
|
// Open other TAPI device reg key
|
|
//
|
|
szSubKey[0] = '\0';
|
|
wsprintf(szSubKey,"%s\\%s",REG_TAPIDEVICES,szBuffer);
|
|
Dprintf("ICFGNT opening (%s).\n",szSubKey);
|
|
if (ERROR_SUCCESS != RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
szSubKey,
|
|
&hsubkey))
|
|
{
|
|
Dprintf("ICFGNT Can not open TAPI SUB key.\n");
|
|
goto IsDialableISDNAdaptersExit;
|
|
}
|
|
|
|
if (ERROR_SUCCESS != GetRegValue(hsubkey,szUsage,&lpData))
|
|
{
|
|
Dprintf("ICFGNT Can not get TAPI SUB key.\n");
|
|
goto IsDialableISDNAdaptersExit;
|
|
}
|
|
|
|
//
|
|
// Scan for "client"
|
|
//
|
|
lpsUsage = (LPSTR)lpData;
|
|
while (*lpsUsage != '\0')
|
|
{
|
|
if(NULL != strstr(lpsUsage, "Client"))
|
|
{
|
|
Dprintf("ICFGNT client found for device.\n");
|
|
//
|
|
// We found a client device, now check that it is ISDN
|
|
//
|
|
GlobalFree(lpData);
|
|
lpData = NULL;
|
|
if (ERROR_SUCCESS != GetRegValue(hsubkey,szMediaType,&lpData))
|
|
{
|
|
Dprintf("ICFGNT Can not get TAPI SUB value key.\n");
|
|
goto IsDialableISDNAdaptersExit;
|
|
}
|
|
|
|
if (0 == lstrcmpi((LPSTR)lpData,"ISDN"))
|
|
{
|
|
Dprintf("ICFGNT ISDN media type found.\n");
|
|
//
|
|
// This is a valid dial-out ISDN device!!! Wahoo!!
|
|
//
|
|
bRC = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Dprintf("ICFGNT ISDN media type NOT found.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lpsUsage += lstrlen(lpsUsage)+1;
|
|
}
|
|
}
|
|
|
|
if (lpData)
|
|
{
|
|
GlobalFree(lpData);
|
|
lpData = NULL;
|
|
lpsUsage = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Move to the next REG key
|
|
//
|
|
dwIdx++;
|
|
}
|
|
|
|
IsDialableISDNAdaptersExit:
|
|
if (hkey)
|
|
{
|
|
RegCloseKey(hkey);
|
|
hkey = NULL;
|
|
}
|
|
if (hsubkey)
|
|
{
|
|
RegCloseKey(hsubkey);
|
|
hsubkey = NULL;
|
|
}
|
|
if (lpData)
|
|
{
|
|
GlobalFree(lpData);
|
|
lpData = NULL;
|
|
lpsUsage = NULL;
|
|
}
|
|
|
|
return bRC;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: IcfgNeedModem
|
|
//
|
|
// Synopsis: Check system configuration to determine if there is at least
|
|
// one physical modem installed
|
|
//
|
|
// Arguments: dwfOptions - currently not used
|
|
//
|
|
// Returns: HRESULT - S_OK if successfull
|
|
// lpfNeedModem - TRUE if no modems are available
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
LPSTR szRasUnimodemSubKey =
|
|
"Software\\Microsoft\\ras\\TAPI DEVICES\\Unimodem";
|
|
|
|
HRESULT WINAPI
|
|
IcfgNeedModemNT4 (DWORD dwfOptions, LPBOOL lpfNeedModem)
|
|
{
|
|
//
|
|
// Ras is insatlled, and ICW wants to know if it needs to
|
|
// install a modem.
|
|
//
|
|
*lpfNeedModem = TRUE;
|
|
|
|
//
|
|
// ChrisK 7/22/97
|
|
// Added return code in order to provide centralized place to check
|
|
// for ISDN installations
|
|
//
|
|
HRESULT hRC = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Check what modems are available to RAS
|
|
//
|
|
|
|
HKEY hUnimodem;
|
|
LONG dwError;
|
|
|
|
Dprintf("ICFGNT: IcfgNeedModem\n");
|
|
|
|
dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
|
szRasUnimodemSubKey,
|
|
0,
|
|
KEY_READ,
|
|
&hUnimodem);
|
|
|
|
if(dwError != ERROR_SUCCESS)
|
|
{
|
|
goto IcfgNeedModemExit;
|
|
}
|
|
else
|
|
{
|
|
LPBYTE lpData;
|
|
|
|
dwError = GetRegValue(hUnimodem, szUsage, &lpData);
|
|
if(dwError != ERROR_SUCCESS)
|
|
goto IcfgNeedModemExit;
|
|
|
|
LPBYTE lpData2;
|
|
dwError = GetRegValue(hUnimodem, szAddress, &lpData2);
|
|
if(dwError != ERROR_SUCCESS)
|
|
{
|
|
hRC = dwError;
|
|
goto IcfgNeedModemExit;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Try finding a Client or ClientAndServer Modem
|
|
// Also, make sure all modems have corresponding TAPI devices
|
|
//
|
|
|
|
LPSTR pUsage = (LPSTR) lpData;
|
|
LPSTR pAddress = (LPSTR) lpData2;
|
|
char portsbuf[1000];
|
|
|
|
dwError = EnumerateTapiModemPorts(sizeof(portsbuf), portsbuf);
|
|
if(dwError)
|
|
{
|
|
hRC = dwError;
|
|
goto IcfgNeedModemExit;
|
|
}
|
|
|
|
while(*pUsage != '\0') {
|
|
if(lstrcmp(pUsage, "Client") == 0 ||
|
|
lstrcmp(pUsage, "ClientAndServer") == 0 ||
|
|
lstrcmp(pUsage, "ClientAndServerAndRouter") == 0)
|
|
|
|
{
|
|
*lpfNeedModem = FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure a corresponding TAPI port exists
|
|
//
|
|
|
|
LPSTR pPorts = portsbuf;
|
|
while(*pPorts != '\0')
|
|
if(stricmp(pAddress, pPorts) == 0)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
pPorts += lstrlen(pPorts) + 1;
|
|
}
|
|
|
|
if(*pPorts == '\0')
|
|
{
|
|
hRC = ERROR_INTERNAL_ERROR;
|
|
goto IcfgNeedModemExit;
|
|
}
|
|
|
|
pUsage += lstrlen(pUsage) + 1;
|
|
pAddress += lstrlen(pAddress) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
IcfgNeedModemExit:
|
|
//
|
|
// If there was some problem finding a typical dial out device,
|
|
// then try again and check for dialing ISDN devices.
|
|
//
|
|
if (ERROR_SUCCESS != hRC ||
|
|
FALSE != *lpfNeedModem)
|
|
{
|
|
if (IsDialableISDNAdapters())
|
|
{
|
|
hRC = ERROR_SUCCESS;
|
|
*lpfNeedModem = FALSE;
|
|
}
|
|
}
|
|
|
|
return(hRC);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: IcfgInstallModem
|
|
//
|
|
// Synopsis:
|
|
// This function is called when ICW verified that RAS is installed,
|
|
// but no modems are avilable. It needs to make sure a modem is availble.
|
|
// There are two possible scenarios:
|
|
//
|
|
// a. There are no modems installed. This happens when someone deleted
|
|
// a modem after installing RAS. In this case we need to run the modem
|
|
// install wizard, and configure the newly installed modem to be a RAS
|
|
// dialout device.
|
|
//
|
|
// b. There are modems installed, but non of them is configured as a dial out
|
|
// device. In this case, we silently convert them to be DialInOut devices,
|
|
// so ICW can use them.
|
|
//
|
|
// Arguments: hwndParent - handle to parent window
|
|
// dwfOptions - not used
|
|
//
|
|
// Returns: lpfNeedsStart - not used
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT WINAPI
|
|
IcfgInstallModemNT4 (HWND hwndParent, DWORD dwfOptions, LPBOOL lpfNeedsStart)
|
|
{
|
|
//
|
|
// Check what modems are available to RAS
|
|
//
|
|
|
|
HKEY hUnimodem;
|
|
LONG dwError;
|
|
BOOL fInstallModem = FALSE;
|
|
|
|
Dprintf("ICFGNT: IcfgInstallModem\n");
|
|
|
|
dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
|
szRasUnimodemSubKey,
|
|
0,
|
|
KEY_READ,
|
|
&hUnimodem);
|
|
|
|
if(dwError != ERROR_SUCCESS)
|
|
{
|
|
fInstallModem = TRUE;
|
|
}
|
|
else
|
|
{
|
|
LPBYTE lpData;
|
|
|
|
dwError = GetRegValue(hUnimodem, szUsage, &lpData);
|
|
if(dwError != ERROR_SUCCESS)
|
|
{
|
|
fInstallModem = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// Make sure at least one modem exists
|
|
if(*lpData == '\0')
|
|
{
|
|
fInstallModem = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(fInstallModem)
|
|
{
|
|
//
|
|
// Fire up the modem install wizard
|
|
//
|
|
|
|
if(!CallModemInstallWizard(hwndParent))
|
|
{
|
|
return(g_dwLastError = GetLastError());
|
|
}
|
|
|
|
//
|
|
// Now configure the new modem to be a dial out device.
|
|
//
|
|
|
|
//
|
|
// Install ras again with unattneded file!
|
|
//
|
|
|
|
return(ERROR_SUCCESS);
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We need to reconfigure dial in devices to be dialinout
|
|
//
|
|
|
|
//
|
|
// install ras again with unattended file!
|
|
//
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: IcfgNeedInetComponets
|
|
//
|
|
// Synopsis: Check to see if the components marked in the options are
|
|
// installed on the system
|
|
//
|
|
// Arguements: dwfOptions - set of bit flag indicating which components to
|
|
// check for
|
|
//
|
|
// Returns; HRESULT - S_OK if successfull
|
|
// lpfNeedComponents - TRUE is some components are not installed
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT WINAPI
|
|
IcfgNeedInetComponentsNT4(DWORD dwfOptions, LPBOOL lpfNeedComponents) {
|
|
DWORD dwErr;
|
|
|
|
//
|
|
// Assume need nothing
|
|
//
|
|
*lpfNeedComponents = FALSE;
|
|
|
|
Dprintf("ICFGNT: IcfgNeedInetComponents\n");
|
|
dwErr = LoadNetcfg();
|
|
if(dwErr != ERROR_SUCCESS)
|
|
{
|
|
return(g_dwLastError = dwErr); // Shouldn't we map to hResult?
|
|
}
|
|
|
|
WCHAR wszInfNameBuf[512];
|
|
DWORD cchInfName = sizeof(wszInfNameBuf) / sizeof(WCHAR);
|
|
|
|
if(dwfOptions & ICFG_INSTALLTCP)
|
|
{
|
|
dwErr = pfnNetSetupFindSoftwareComponent(L"TC",
|
|
wszInfNameBuf,
|
|
&cchInfName,
|
|
0,
|
|
0);
|
|
if(dwErr != ERROR_SUCCESS)
|
|
*lpfNeedComponents = TRUE;
|
|
}
|
|
|
|
if(dwfOptions & ICFG_INSTALLRAS)
|
|
{
|
|
dwErr = pfnNetSetupFindSoftwareComponent(L"RAS",
|
|
wszInfNameBuf,
|
|
&cchInfName,
|
|
0,
|
|
0);
|
|
if(dwErr != ERROR_SUCCESS)
|
|
*lpfNeedComponents = TRUE;
|
|
}
|
|
|
|
if(dwfOptions & ICFG_INSTALLMAIL)
|
|
{
|
|
// How do we do this?
|
|
}
|
|
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: GenerateRasUnattendedFile
|
|
//
|
|
// Synopsis: Create the file that will provide RAS setup the necessary
|
|
// setting to install in an unattended mode
|
|
//
|
|
// Arguments: wszTmpFile - name of file to create
|
|
// szPortsBuf
|
|
//
|
|
// Returns: FALSE - failure, TRUE - success
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
BOOL
|
|
GenerateRasUnattendedFile(LPWSTR wszTmpFile, LPSTR szPortsBuf)
|
|
{
|
|
WCHAR wszTmpPath[MAX_PATH+1];
|
|
WCHAR wszTmpShortPath[MAX_PATH+1];
|
|
|
|
//
|
|
// Create temporary file name and convert to non-wide form
|
|
//
|
|
|
|
Dprintf("ICFGNT: GenerateRasUnattendedFile\n");
|
|
|
|
if (GetTempPathW(MAX_PATH, wszTmpPath) == 0)
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// always attempt to create the temp dir as the temp dir may not exist if
|
|
// the user logs in with a roaming profile
|
|
//
|
|
CreateDirectoryW(wszTmpPath, NULL);
|
|
|
|
//
|
|
// need to convert this to a short path since pfnNetSetupComponentInstall()
|
|
// doesn't like to have a long path in the InfSymbols param.
|
|
//
|
|
if (!GetShortPathNameW(wszTmpPath, wszTmpShortPath, MAX_PATH))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (GetTempFileNameW(wszTmpPath, L"icw", 0, wszTmpFile) == 0)
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// need to convert the temp filename to shortpath too!
|
|
//
|
|
if (!GetShortPathNameW(wszTmpFile, wszTmpShortPath, MAX_PATH))
|
|
{
|
|
return FALSE;
|
|
}
|
|
wcscpy(wszTmpFile, wszTmpShortPath);
|
|
|
|
char szTmpFile[MAX_PATH+1];
|
|
wcstombs(szTmpFile, wszTmpFile, wcslen(wszTmpFile) + 1);
|
|
|
|
#if 0
|
|
/*
|
|
FILE *fp = fopen(szTmpFile, "w");
|
|
|
|
if(fp == 0)
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
fprintf(fp, "[RemoteAccessParameters]\n");
|
|
fprintf(fp, "PortSections = ");
|
|
|
|
LPSTR szPorts = szPortsBuf;
|
|
|
|
while(*szPorts) {
|
|
if(szPorts != szPortsBuf)
|
|
{
|
|
fprintf(fp, ",");
|
|
}
|
|
fprintf(fp, "%s", szPorts);
|
|
szPorts += lstrlen(szPorts) + 1;
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
fprintf(fp, "DialoutProtocols = TCP/IP\n");
|
|
fprintf(fp, "\n");
|
|
fprintf(fp, "[Modem]\n");
|
|
fprintf(fp, "InstallModem=ModemSection\n");
|
|
fprintf(fp, "\n");
|
|
|
|
szPorts = szPortsBuf;
|
|
|
|
while(*szPorts) {
|
|
fprintf(fp, "[%s]\n", szPorts);
|
|
fprintf(fp, "PortName = %s\n", szPorts);
|
|
fprintf(fp, "DeviceType = Modem\n");
|
|
fprintf(fp, "PortUsage = DialOut\n");
|
|
fprintf(fp, "\n");
|
|
szPorts += lstrlen(szPorts) + 1;
|
|
}
|
|
|
|
fprintf(fp, "[ModemSection]\n");
|
|
|
|
fclose(fp);
|
|
*/
|
|
|
|
#else
|
|
|
|
//
|
|
// Open the file for writing, bail on fail.
|
|
//
|
|
|
|
BOOL bRet = FALSE;
|
|
|
|
HANDLE hFile = CreateFile(szTmpFile,GENERIC_WRITE,0,NULL,OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,NULL);
|
|
|
|
if (INVALID_HANDLE_VALUE == hFile)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
LPSTR szPorts = szPortsBuf;
|
|
char szFileBuf[MAX_PATH];
|
|
DWORD dwWrite;
|
|
|
|
lstrcpy(szFileBuf, "[RemoteAccessParameters]\nPortSections = ");
|
|
|
|
if (!WriteFile(hFile, szFileBuf, lstrlen(szFileBuf), &dwWrite, NULL))
|
|
{
|
|
goto closefile;
|
|
}
|
|
|
|
while (*szPorts)
|
|
{
|
|
//
|
|
// Delimit each item with a comma
|
|
//
|
|
|
|
if (szPorts != szPortsBuf)
|
|
{
|
|
lstrcpy(szFileBuf, ",");
|
|
if (!WriteFile(hFile, szFileBuf, lstrlen(szFileBuf), &dwWrite, NULL))
|
|
{
|
|
goto closefile;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Write each port
|
|
//
|
|
|
|
wsprintf(szFileBuf, "%s", szPorts);
|
|
|
|
if (!WriteFile(hFile, szFileBuf, lstrlen(szFileBuf), &dwWrite, NULL))
|
|
{
|
|
goto closefile;
|
|
}
|
|
|
|
szPorts += lstrlen(szPorts) + 1;
|
|
}
|
|
|
|
//
|
|
// Write DialoutProtocol TCP/IP and InstallModem
|
|
//
|
|
|
|
lstrcpy(szFileBuf, "\nDialoutProtocols = TCP/IP\n\n[Modem]\nInstallModem=ModemSection\n\n");
|
|
|
|
if (!WriteFile(hFile, szFileBuf, lstrlen(szFileBuf), &dwWrite, NULL))
|
|
{
|
|
goto closefile;
|
|
}
|
|
|
|
//
|
|
// Enumerate ports again
|
|
//
|
|
|
|
szPorts = szPortsBuf;
|
|
|
|
while (*szPorts)
|
|
{
|
|
//
|
|
// Write PortName section and entry
|
|
//
|
|
|
|
wsprintf(szFileBuf, "[%s]\n", szPorts);
|
|
|
|
if (!WriteFile(hFile, szFileBuf, lstrlen(szFileBuf), &dwWrite, NULL))
|
|
{
|
|
goto closefile;
|
|
}
|
|
|
|
wsprintf(szFileBuf, "PortName = %s\n", szPorts);
|
|
|
|
if (!WriteFile(hFile, szFileBuf, lstrlen(szFileBuf), &dwWrite, NULL))
|
|
{
|
|
goto closefile;
|
|
}
|
|
|
|
//
|
|
// Write DeviceType and PortUsage entry for each port
|
|
//
|
|
|
|
lstrcpy(szFileBuf, "DeviceType = Modem\nPortUsage = DialOut\n\n");
|
|
|
|
if (!WriteFile(hFile, szFileBuf, lstrlen(szFileBuf), &dwWrite, NULL))
|
|
{
|
|
goto closefile;
|
|
}
|
|
|
|
szPorts += lstrlen(szPorts) + 1;
|
|
}
|
|
|
|
lstrcpy(szFileBuf, "[ModemSection]\n");
|
|
|
|
if (!WriteFile(hFile, szFileBuf, lstrlen(szFileBuf), &dwWrite, NULL))
|
|
{
|
|
goto closefile;
|
|
}
|
|
|
|
bRet = TRUE;
|
|
|
|
closefile:
|
|
|
|
CloseHandle(hFile);
|
|
|
|
#endif
|
|
|
|
return(bRet);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: InstallRAS
|
|
//
|
|
// Synopsis: Invoke unattended RAS installation
|
|
//
|
|
// Arguments: hwndParent - handle to parent window
|
|
// szFile - name of unattended settings file
|
|
// szSection -
|
|
//
|
|
// Returns: DWORD - win32 error
|
|
// pdwReturn - return code from last parameter of
|
|
// pfnNetSetupComponentInstall
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD
|
|
InstallRAS(HWND hwndParent, LPWSTR szFile, LPWSTR szSection, LPDWORD pdwReturn)
|
|
{
|
|
WCHAR InfSymbols[1024];
|
|
LPWSTR szInfSymbols = InfSymbols;
|
|
|
|
Dprintf("ICFGNT: InstallRAS\n");
|
|
|
|
DWORD dwRC = InstallNTNetworking(hwndParent);
|
|
if (ERROR_SUCCESS != dwRC)
|
|
{
|
|
return dwRC;
|
|
}
|
|
|
|
LPWSTR szString1 = L"!STF_GUI_UNATTENDED";
|
|
wcscpy(szInfSymbols, szString1);
|
|
szInfSymbols += wcslen(szString1) + 1;
|
|
|
|
LPWSTR szString2 = L"YES";
|
|
wcscpy(szInfSymbols, szString2);
|
|
szInfSymbols += wcslen(szString2) + 1;
|
|
|
|
LPWSTR szString3 = L"!STF_UNATTENDED";
|
|
wcscpy(szInfSymbols, szString3);
|
|
szInfSymbols += wcslen(szString3) + 1;
|
|
|
|
//
|
|
// Unattneded file.
|
|
//
|
|
|
|
wcscpy(szInfSymbols, szFile);
|
|
szInfSymbols += wcslen(szFile) + 1;
|
|
|
|
LPWSTR szString4 = L"!STF_UNATTENDED_SECTION";
|
|
wcscpy(szInfSymbols, szString4);
|
|
szInfSymbols += wcslen(szString4) + 1;
|
|
|
|
//
|
|
// Unattnded section
|
|
//
|
|
|
|
wcscpy(szInfSymbols, szSection);
|
|
szInfSymbols += wcslen(szSection) + 1;
|
|
|
|
*szInfSymbols++ = 0;
|
|
*szInfSymbols++ = 0;
|
|
|
|
return(pfnNetSetupComponentInstall(hwndParent,
|
|
L"RAS",
|
|
L"OEMNSVRA.INF",
|
|
g_wszInstallPath,
|
|
InfSymbols,
|
|
INFINSTALL_INPROCINTERP, // Install Flags
|
|
pdwReturn));
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: IcfgInstallInetComponents
|
|
//
|
|
// Synopsis: Install the components as specified by the dwfOptions values
|
|
//
|
|
// Arguments hwndParent - handle to parent window
|
|
// dwfOptions - set of bit flags indicating which components to
|
|
// install
|
|
//
|
|
// Returns: HRESULT - S_OK if success
|
|
// lpfNeedsReboot - TRUE if reboot is required
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT WINAPI
|
|
IcfgInstallInetComponentsNT4(HWND hwndParent, DWORD dwfOptions, LPBOOL lpfNeedsRestart)
|
|
{
|
|
DWORD dwErr;
|
|
DWORD dwReturn;
|
|
BOOL fNeedsReview;
|
|
BOOL fNeedsRestart;
|
|
BOOL fDoReview = FALSE;
|
|
WCHAR wszInfNameBuf[512];
|
|
DWORD cchInfName = sizeof(wszInfNameBuf) / sizeof(WCHAR);
|
|
|
|
Dprintf("ICFGNT: IcfgInstallInetComponents\n");
|
|
//
|
|
// Assume don't need restart
|
|
//
|
|
*lpfNeedsRestart = FALSE;
|
|
|
|
dwErr = LoadNetcfg();
|
|
if(dwErr != ERROR_SUCCESS)
|
|
{
|
|
return(g_dwLastError = dwErr); // Review: Shouldn't we map to hResult?
|
|
}
|
|
|
|
if(dwfOptions & ICFG_INSTALLTCP)
|
|
{
|
|
dwErr = pfnNetSetupFindSoftwareComponent(L"TC",
|
|
wszInfNameBuf,
|
|
&cchInfName,
|
|
0,
|
|
0);
|
|
if(dwErr != ERROR_SUCCESS)
|
|
{
|
|
dwErr = pfnNetSetupComponentInstall(hwndParent,
|
|
L"TC",
|
|
L"OEMNXPTC.INF",
|
|
g_wszInstallPath,
|
|
L"\0\0",
|
|
INFINSTALL_INPROCINTERP, // Install Flags
|
|
&dwReturn);
|
|
if(dwErr != ERROR_SUCCESS)
|
|
{
|
|
return(g_dwLastError = dwErr); // Review: Shouldn't we map to hResult?
|
|
}
|
|
|
|
ParseNetSetupReturn(dwReturn, fNeedsRestart, fNeedsReview);
|
|
if(fNeedsRestart)
|
|
{
|
|
*lpfNeedsRestart = TRUE;
|
|
}
|
|
if(fNeedsReview)
|
|
{
|
|
fDoReview = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(dwfOptions & ICFG_INSTALLRAS)
|
|
{
|
|
dwErr = pfnNetSetupFindSoftwareComponent(L"RAS",
|
|
wszInfNameBuf,
|
|
&cchInfName,
|
|
0,
|
|
0);
|
|
if(dwErr != ERROR_SUCCESS)
|
|
{
|
|
//
|
|
// Before we install RAS, we have to make have to make sure a modem
|
|
// is installed, because RAS will try to run the modem detection wizard
|
|
// in unattneded mode if there are no modems, and we don't want that.
|
|
//
|
|
// The way we do that is we enumerate devices through TAPI, and if there are
|
|
// no modems installed, we call the modem install wizard. Only after
|
|
// we make sure a modem was installed, we call ras install.
|
|
//
|
|
|
|
DWORD DoTapiModemsExist(LPBOOL pfTapiModemsExist);
|
|
char portsbuf[1000];
|
|
|
|
dwErr = EnumerateTapiModemPorts(sizeof(portsbuf), portsbuf);
|
|
if(dwErr)
|
|
return(g_dwLastError = dwErr);
|
|
|
|
if(*portsbuf == 0)
|
|
{
|
|
if(!CallModemInstallWizard(hwndParent))
|
|
{
|
|
//
|
|
// if CallModemInstallWizard returned FALSE and
|
|
// GetLastError() is ERROR_SUCCESS, it is actually
|
|
// a user cancelled case
|
|
//
|
|
if (ERROR_SUCCESS == (g_dwLastError = GetLastError()))
|
|
g_dwLastError = ERROR_CANCELLED;
|
|
return(g_dwLastError);
|
|
}
|
|
|
|
//
|
|
// In this invocation of EnumerateTapiModemPorts
|
|
// we have to wait for a 1 second before we start
|
|
// enumerating the modems - hence set the last parameter
|
|
// to TRUE -- VetriV
|
|
//
|
|
dwErr = EnumerateTapiModemPorts(sizeof(portsbuf), portsbuf,
|
|
TRUE);
|
|
if(dwErr)
|
|
{
|
|
return(g_dwLastError = dwErr);
|
|
}
|
|
|
|
if(*portsbuf == 0)
|
|
{
|
|
return(g_dwLastError = ERROR_CANCELLED);
|
|
}
|
|
}
|
|
|
|
WCHAR wszUnattFile[MAX_PATH];
|
|
|
|
if(!GenerateRasUnattendedFile(wszUnattFile, portsbuf))
|
|
{
|
|
return(g_dwLastError = GetLastError());
|
|
}
|
|
|
|
dwErr = InstallRAS(hwndParent,
|
|
wszUnattFile,
|
|
L"RemoteAccessParameters",
|
|
&dwReturn);
|
|
DeleteFileW(wszUnattFile);
|
|
|
|
if(dwErr != ERROR_SUCCESS)
|
|
{
|
|
return(g_dwLastError = dwErr); // Review: Shouldn't we map to hResult?
|
|
}
|
|
|
|
ParseNetSetupReturn(dwReturn, fNeedsRestart, fNeedsReview);
|
|
if(fNeedsRestart)
|
|
{
|
|
*lpfNeedsRestart = TRUE;
|
|
}
|
|
if(fNeedsReview)
|
|
{
|
|
fDoReview = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(fDoReview)
|
|
{
|
|
return(g_dwLastError = ReviewBindings(hwndParent)); // Review: Shouldn't we map to hresult?
|
|
}
|
|
else
|
|
{
|
|
return(ERROR_SUCCESS);
|
|
}
|
|
}
|
|
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: IcfgIsFileSharingTurnedOn
|
|
//
|
|
// Synopsis: Check network bindings to determine if "Server" service is
|
|
// bound to ndiswan adapter
|
|
//
|
|
// Arguments: dwfDriverType -
|
|
//
|
|
// Returns: HRESULT - S_OK is success
|
|
// lpfSharingOn - TRUE if sharing is bound
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
LPSTR szLanManServerSubKey = "SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Linkage";
|
|
LPSTR szBind = "Bind";
|
|
LPSTR szExport = "Export";
|
|
LPSTR szRoute = "Route";
|
|
LPSTR szLanManServerDisabledSubKey = "SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Linkage\\Disabled";
|
|
LPSTR szNdisWan4 = "NdisWan";
|
|
struct BINDDATA
|
|
{
|
|
CHAR *startb;
|
|
CHAR *currb;
|
|
CHAR *starte;
|
|
CHAR *curre;
|
|
CHAR *startr;
|
|
CHAR *currr;
|
|
} net_bindings;
|
|
|
|
HRESULT WINAPI
|
|
IcfgIsFileSharingTurnedOn(DWORD dwfDriverType, LPBOOL lpfSharingOn)
|
|
{
|
|
HRESULT hr = ERROR_SUCCESS;
|
|
HKEY hKey = NULL;
|
|
LPBYTE lpData = NULL;
|
|
CHAR *p;
|
|
|
|
Dprintf("ICFGNT: IcfgIsFileSharingTurnedOn\n");
|
|
Assert(lpfSharingOn);
|
|
if (NULL == lpfSharingOn)
|
|
{
|
|
hr = ERROR_INVALID_PARAMETER;
|
|
goto IcfgIsFileSharingTurnedOnExit;
|
|
}
|
|
|
|
*lpfSharingOn = FALSE;
|
|
|
|
//
|
|
// Open lanmanServer registry key
|
|
//
|
|
if (ERROR_SUCCESS != RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
szLanManServerSubKey,
|
|
&hKey))
|
|
{
|
|
Dprintf("ICFGNT: Failed to open lanmanServer key\n");
|
|
goto IcfgIsFileSharingTurnedOnExit;
|
|
}
|
|
|
|
if (ERROR_SUCCESS != GetRegValue(hKey, szBind, &lpData))
|
|
{
|
|
Dprintf("ICFGNT: Failed to read binding information\n");
|
|
goto IcfgIsFileSharingTurnedOnExit;
|
|
}
|
|
Assert(lpData);
|
|
|
|
//
|
|
// Look for a particular string in the data returned
|
|
// Note: data is terminiated with two NULLs
|
|
//
|
|
p = (CHAR *)lpData;
|
|
while (*p)
|
|
{
|
|
if (strstr( p, szNdisWan4))
|
|
{
|
|
Dprintf("ICFGNT: NdisWan4 binding found in %s\n",p);
|
|
*lpfSharingOn = TRUE;
|
|
break;
|
|
}
|
|
p += (lstrlen( p ) + 1);
|
|
}
|
|
|
|
|
|
IcfgIsFileSharingTurnedOnExit:
|
|
if (hKey)
|
|
{
|
|
RegCloseKey(hKey);
|
|
hKey = NULL;
|
|
}
|
|
|
|
if (lpData)
|
|
{
|
|
GlobalFree(lpData);
|
|
lpData = NULL;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: MoveNextBinding
|
|
//
|
|
// Synopsis: Move to the next string in a MULTISZ data buffer
|
|
//
|
|
// Arguments: lpcBinding - pointer to address of current buffer position
|
|
//
|
|
// Returns: lpcBinding - pointer to next string
|
|
//
|
|
// History: 6/5/97 ChrisK Created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
inline void MoveNextBinding(CHAR **lplpcBinding)
|
|
{
|
|
Dprintf("ICFGNT: MoveNextBinding\n");
|
|
Assert(lplpcBinding && *lplpcBinding);
|
|
if (lplpcBinding && *lplpcBinding)
|
|
{
|
|
*lplpcBinding += (lstrlen(*lplpcBinding)+1);
|
|
}
|
|
else
|
|
{
|
|
Dprintf("ICFGNT: MoveNextBinding received invalid parameter\n");
|
|
}
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: CompactLinkage
|
|
//
|
|
// Synopsis: Compact a list of Multi_SZ data
|
|
//
|
|
// Arguments: lpBinding - point to the string of an Multi_Sz list that should
|
|
// be over written
|
|
//
|
|
// Returns: none
|
|
//
|
|
// History: 6/5/97 ChrisK Created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
inline void CompactLinkage(CHAR *lpBinding)
|
|
{
|
|
Dprintf("ICFGNT: CompactLinkage\n");
|
|
Assert(lpBinding && *lpBinding);
|
|
CHAR *lpLast = lpBinding;
|
|
BOOL fNULLChar = FALSE;
|
|
|
|
MoveNextBinding(&lpBinding);
|
|
|
|
//
|
|
// ChrisK Olympus 6311 6/11/97
|
|
// Do not compact on a per string basis. This causes the data to become
|
|
// corrupted if the string being removed is shorter than the string being
|
|
// added. Instead compact on a per character basis, since those are always
|
|
// the same size (on a given machine).
|
|
//
|
|
while (!fNULLChar || *lpBinding)
|
|
{
|
|
if (NULL == *lpBinding)
|
|
{
|
|
fNULLChar = TRUE;
|
|
}
|
|
else
|
|
{
|
|
fNULLChar = FALSE;
|
|
}
|
|
*lpLast++ = *lpBinding++;
|
|
}
|
|
|
|
//while (*lpBinding)
|
|
//{
|
|
// lstrcpy(lpLast,lpBinding);
|
|
// lpLast = lpBinding;
|
|
// MoveNextBinding(&lpBinding);
|
|
//}
|
|
|
|
//
|
|
// Add second terminating NULL
|
|
//
|
|
*lpLast = '\0';
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: SizeOfMultiSz
|
|
//
|
|
// Synopsis: determine the total size of a Multi_sz list, including
|
|
// terminating NULLs
|
|
//
|
|
// Arguments: s - pointer to list
|
|
//
|
|
// Returns: DWORD - size of s
|
|
//
|
|
// History: 6/5/97 ChrisK created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD SizeOfMultiSz(CHAR *s)
|
|
{
|
|
Dprintf("ICFGNT: SizeOfMultiSz\n");
|
|
Assert(s);
|
|
DWORD dwLen = 0;
|
|
//
|
|
// total size of all strings
|
|
//
|
|
|
|
//
|
|
// ChrisK Olympus 6311 6/11/97
|
|
// Add special case for empty MultiSZ strings
|
|
//
|
|
|
|
//
|
|
// Special case for empty MultiSz.
|
|
// Note: even "empty" MultiSZ strings still have the two null terminating characters
|
|
//
|
|
if (!(*s))
|
|
{
|
|
//
|
|
// Make sure we actually have two terminating NULLs in this case.
|
|
//
|
|
Assert(s[1] == '\0');
|
|
//
|
|
// Count terminating NULL.
|
|
//
|
|
dwLen = 1;
|
|
}
|
|
|
|
while (*s)
|
|
{
|
|
dwLen += lstrlen(s) + 1;
|
|
s += lstrlen(s) + 1;
|
|
}
|
|
//
|
|
// plus one for the extra terminating NULL
|
|
//
|
|
dwLen++;
|
|
Dprintf("ICFGNT: SizeOfMultiSz returns %d\n", dwLen);
|
|
|
|
return dwLen;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: WriteBindings
|
|
//
|
|
// Synopsis: Write the data from a BINDDATA structure to the key given
|
|
//
|
|
// Arguments: bd - BINDDATA structure with data to be written
|
|
// hKey - handle of registry key to get data
|
|
//
|
|
// Returns: win32 error code
|
|
//
|
|
// History: 6/5/97 ChrisK created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD WriteBindings(BINDDATA bd, HKEY hKey)
|
|
{
|
|
DWORD dwRC = ERROR_SUCCESS;
|
|
DWORD dwSize;
|
|
|
|
Assert (hKey &&
|
|
bd.startb &&
|
|
bd.starte &&
|
|
bd.startr);
|
|
|
|
Dprintf("ICFGNT: WriteBindings\n");
|
|
|
|
//
|
|
// Bind
|
|
//
|
|
dwSize = SizeOfMultiSz(bd.startb);
|
|
if (ERROR_SUCCESS != (dwRC = RegSetValueEx(hKey,
|
|
szBind,
|
|
NULL,
|
|
REG_MULTI_SZ,
|
|
(LPBYTE)bd.startb,
|
|
dwSize)))
|
|
{
|
|
Dprintf("ICFGNT: Failed to write Bind key\n");
|
|
goto WriteBindingsExit;
|
|
}
|
|
|
|
//
|
|
// Export
|
|
//
|
|
dwSize = SizeOfMultiSz(bd.starte);
|
|
if (ERROR_SUCCESS != (dwRC = RegSetValueEx(hKey,
|
|
szExport,
|
|
NULL,
|
|
REG_MULTI_SZ,
|
|
(LPBYTE)bd.starte,
|
|
dwSize)))
|
|
{
|
|
Dprintf("ICFGNT: Failed to write export key\n");
|
|
goto WriteBindingsExit;
|
|
}
|
|
|
|
//
|
|
// Route
|
|
//
|
|
dwSize = SizeOfMultiSz(bd.startr);
|
|
if (ERROR_SUCCESS != (dwRC = RegSetValueEx(hKey,
|
|
szRoute,
|
|
NULL,
|
|
REG_MULTI_SZ,
|
|
(LPBYTE)bd.startr,
|
|
dwSize)))
|
|
{
|
|
Dprintf("ICFGNT: Failed to write route key\n");
|
|
goto WriteBindingsExit;
|
|
}
|
|
|
|
WriteBindingsExit:
|
|
return dwRC;
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------
|
|
//
|
|
// Function: IcfgTurnOffFileSharing
|
|
//
|
|
// Synopsis; Disable the binding between the "server" net service and the
|
|
// ndiswan4 device
|
|
//
|
|
// Arguments: dwfDriverType -
|
|
// hwndParent - parent window
|
|
//
|
|
// Returns: HRESULT - S_OK if success
|
|
//
|
|
// History: 6/5/97 ChrisK Inherited
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
HRESULT WINAPI
|
|
IcfgTurnOffFileSharing(DWORD dwfDriverType, HWND hwndParent)
|
|
{
|
|
BINDDATA LinkData = {NULL, NULL, NULL, NULL, NULL, NULL};
|
|
BINDDATA DisData = {NULL, NULL, NULL, NULL, NULL, NULL};
|
|
HKEY hKeyLink = NULL;
|
|
HKEY hKeyDis = NULL;
|
|
HRESULT hr = ERROR_SUCCESS;
|
|
BOOL bUpdateReg = FALSE;
|
|
|
|
Dprintf("ICFGNT: IcfgTurnOffFileSharing\n");
|
|
Assert(hwndParent);
|
|
if (NULL == hwndParent)
|
|
{
|
|
hr = ERROR_INVALID_PARAMETER;
|
|
goto IcfgTurnOffFileSharingExit;
|
|
}
|
|
|
|
//
|
|
// Open Keys and read binding data
|
|
//
|
|
if (ERROR_SUCCESS != RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
szLanManServerSubKey,
|
|
&hKeyLink))
|
|
{
|
|
Dprintf("ICFGNT: failed to open linkdata key\n");
|
|
goto IcfgTurnOffFileSharingExit;
|
|
}
|
|
|
|
if (ERROR_SUCCESS != RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
szLanManServerDisabledSubKey,
|
|
&hKeyDis))
|
|
{
|
|
Dprintf("ICFGNT: failed to open linkdata key\n");
|
|
goto IcfgTurnOffFileSharingExit;
|
|
}
|
|
|
|
GetRegValue(hKeyLink,szBind,(LPBYTE*)&LinkData.startb);
|
|
GetRegValue(hKeyLink,szExport,(LPBYTE*)&LinkData.starte);
|
|
GetRegValue(hKeyLink,szRoute,(LPBYTE*)&LinkData.startr);
|
|
GetRegValue(hKeyDis,szBind,(LPBYTE*)&DisData.startb);
|
|
GetRegValue(hKeyDis,szExport,(LPBYTE*)&DisData.starte);
|
|
GetRegValue(hKeyDis,szRoute,(LPBYTE*)&DisData.startr);
|
|
|
|
//
|
|
// Initialize all current pointers
|
|
//
|
|
LinkData.currb = LinkData.startb;
|
|
LinkData.curre = LinkData.starte;
|
|
LinkData.currr = LinkData.startr;
|
|
|
|
DisData.currb = DisData.startb;
|
|
while (*DisData.currb)
|
|
{
|
|
MoveNextBinding(&DisData.currb);
|
|
}
|
|
|
|
DisData.curre = DisData.starte;
|
|
while (*DisData.curre)
|
|
{
|
|
MoveNextBinding(&DisData.curre);
|
|
}
|
|
|
|
DisData.currr = DisData.startr;
|
|
while (*DisData.currr)
|
|
{
|
|
MoveNextBinding(&DisData.currr);
|
|
}
|
|
|
|
//
|
|
// Scan linkages for NdisWan4 bindings
|
|
//
|
|
|
|
while (*LinkData.currb)
|
|
{
|
|
if (strstr(LinkData.currb, szNdisWan4))
|
|
{
|
|
Dprintf("ICFGNT: server binding found in %s\n",LinkData.currb);
|
|
|
|
//
|
|
// move binding to disabled list
|
|
//
|
|
|
|
lstrcpy(DisData.currb,LinkData.currb);
|
|
lstrcpy(DisData.curre,LinkData.curre);
|
|
lstrcpy(DisData.currr,LinkData.currr);
|
|
|
|
//
|
|
// Advanve current pointers in DisData
|
|
//
|
|
MoveNextBinding(&DisData.currb);
|
|
MoveNextBinding(&DisData.curre);
|
|
MoveNextBinding(&DisData.currr);
|
|
|
|
//
|
|
// Compact remaining linkage
|
|
//
|
|
CompactLinkage(LinkData.currb);
|
|
CompactLinkage(LinkData.curre);
|
|
CompactLinkage(LinkData.currr);
|
|
|
|
bUpdateReg = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Advance to next binding
|
|
//
|
|
MoveNextBinding(&LinkData.currb);
|
|
MoveNextBinding(&LinkData.curre);
|
|
MoveNextBinding(&LinkData.currr);
|
|
}
|
|
}
|
|
|
|
if (bUpdateReg)
|
|
{
|
|
WriteBindings(LinkData,hKeyLink);
|
|
WriteBindings(DisData,hKeyDis);
|
|
|
|
RegCloseKey(hKeyDis);
|
|
hKeyDis = NULL;
|
|
|
|
RegCloseKey(hKeyLink);
|
|
hKeyLink = NULL;
|
|
|
|
#if defined(_DEBUG)
|
|
Dprintf("ICFGNT: ReviewBindings returnded %d\n",ReviewBindings(hwndParent));
|
|
#else
|
|
ReviewBindings(hwndParent);
|
|
#endif
|
|
}
|
|
|
|
IcfgTurnOffFileSharingExit:
|
|
if (hKeyDis)
|
|
{
|
|
RegCloseKey(hKeyDis);
|
|
hKeyDis = NULL;
|
|
}
|
|
|
|
if (hKeyLink)
|
|
{
|
|
RegCloseKey(hKeyLink);
|
|
hKeyLink = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|