VirtualBox

Changeset 96722 in vbox for trunk/src/VBox/Additions


Ignore:
Timestamp:
Sep 14, 2022 1:17:09 AM (2 years ago)
Author:
vboxsync
Message:

Add/Nt/Installer: Added a crude hack to suppress the WQHL dialog on windows 2000 and XP. bugref:10261 bugref:8691

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Additions/WINNT/Installer/VBoxDrvInst.cpp

    r96605 r96722  
    4141#include <devguid.h>
    4242#include <RegStr.h>
     43#ifdef RT_ARCH_X86
     44# include <wintrust.h>
     45# include <softpub.h>
     46#endif
    4347
    4448#include <iprt/asm.h>
     
    111115} DIFXAPI_LOG;
    112116
    113 typedef void (__cdecl * DIFXAPILOGCALLBACK_W)(DIFXAPI_LOG Event, DWORD Error, PCWSTR EventDescription, PVOID CallbackContext);
    114 
    115 typedef DWORD (WINAPI *fnDriverPackageInstall)(PCTSTR DriverPackageInfPath, DWORD Flags, PCINSTALLERINFO pInstallerInfo, BOOL *pNeedReboot);
    116 fnDriverPackageInstall g_pfnDriverPackageInstall = NULL;
    117 
    118 typedef DWORD (WINAPI *fnDriverPackageUninstall)(PCTSTR DriverPackageInfPath, DWORD Flags, PCINSTALLERINFO pInstallerInfo, BOOL *pNeedReboot);
    119 fnDriverPackageUninstall g_pfnDriverPackageUninstall = NULL;
    120 
    121 typedef VOID (WINAPI *fnDIFXAPISetLogCallback)(DIFXAPILOGCALLBACK_W LogCallback, PVOID CallbackContext);
    122 fnDIFXAPISetLogCallback g_pfnDIFXAPISetLogCallback = NULL;
     117typedef void (__cdecl *DIFXAPILOGCALLBACK_W)(DIFXAPI_LOG Event, DWORD Error, PCWSTR EventDescription, PVOID CallbackContext);
     118typedef DWORD (WINAPI *PFN_DriverPackageInstall_T)(PCTSTR DriverPackageInfPath, DWORD Flags, PCINSTALLERINFO pInstallerInfo, BOOL *pNeedReboot);
     119typedef DWORD (WINAPI *PFN_DriverPackageUninstall_T)(PCTSTR DriverPackageInfPath, DWORD Flags, PCINSTALLERINFO pInstallerInfo, BOOL *pNeedReboot);
     120typedef VOID  (WINAPI *PFN_DIFXAPISetLogCallback_T)(DIFXAPILOGCALLBACK_W LogCallback, PVOID CallbackContext);
     121
     122
     123/*********************************************************************************************************************************
     124*   Global Variables                                                                                                             *
     125*********************************************************************************************************************************/
     126static PFN_DriverPackageInstall_T   g_pfnDriverPackageInstall   = NULL;
     127static PFN_DriverPackageUninstall_T g_pfnDriverPackageUninstall = NULL;
     128static PFN_DIFXAPISetLogCallback_T  g_pfnDIFXAPISetLogCallback  = NULL;
    123129
    124130
     
    229235
    230236    /*
    231      * Write to the log file if we have one (wide char format).
     237     * Write to the log file if we have one (wide char format, used to be ansi).
    232238     */
    233239    HANDLE const hLogFile = (HANDLE)pvCtx;
     
    250256}
    251257
     258
     259/**
     260 * Writes a header to the DIFx log file.
     261 */
     262static void VBoxDIFxWriteLogHeader(HANDLE hLogFile, char const *pszOperation, wchar_t const *pwszInfFile)
     263{
     264    /* Don't want to use RTUtf16Printf here as it drags in a lot of code, thus this tedium... */
     265    wchar_t wszBuf[168];
     266    RTUtf16CopyAscii(wszBuf, RT_ELEMENTS(wszBuf), "\r\n");
     267
     268    SYSTEMTIME SysTime = {0};
     269    GetSystemTime(&SysTime);
     270
     271    char szVal[128];
     272    RTStrFormatU32(szVal, sizeof(szVal), SysTime.wYear, 10, 4, 0, RTSTR_F_ZEROPAD | RTSTR_F_WIDTH);
     273    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), szVal);
     274    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), "-");
     275
     276    RTStrFormatU32(szVal, sizeof(szVal), SysTime.wMonth, 10, 2, 0, RTSTR_F_ZEROPAD | RTSTR_F_WIDTH);
     277    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), szVal);
     278    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), "-");
     279
     280    RTStrFormatU32(szVal, sizeof(szVal), SysTime.wDay, 10, 2, 0, RTSTR_F_ZEROPAD | RTSTR_F_WIDTH);
     281    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), szVal);
     282    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), "T");
     283
     284    RTStrFormatU32(szVal, sizeof(szVal), SysTime.wHour, 10, 2, 0, RTSTR_F_ZEROPAD | RTSTR_F_WIDTH);
     285    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), szVal);
     286    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), ":");
     287
     288    RTStrFormatU32(szVal, sizeof(szVal), SysTime.wMinute, 10, 2, 0, RTSTR_F_ZEROPAD | RTSTR_F_WIDTH);
     289    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), szVal);
     290    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), ":");
     291
     292    RTStrFormatU32(szVal, sizeof(szVal), SysTime.wSecond, 10, 2, 0, RTSTR_F_ZEROPAD | RTSTR_F_WIDTH);
     293    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), szVal);
     294    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), ".");
     295
     296    RTStrFormatU32(szVal, sizeof(szVal), SysTime.wMilliseconds, 10, 3, 0, RTSTR_F_ZEROPAD | RTSTR_F_WIDTH);
     297    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), szVal);
     298    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), "Z: Opened log file for ");
     299
     300    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), pszOperation);
     301    RTUtf16CatAscii(wszBuf, RT_ELEMENTS(wszBuf), " of '");
     302
     303    DWORD dwIgn;
     304    WriteFile(hLogFile, wszBuf, (DWORD)(RTUtf16Len(wszBuf) * sizeof(wchar_t)), &dwIgn, NULL);
     305    WriteFile(hLogFile, pwszInfFile, (DWORD)(RTUtf16Len(pwszInfFile) * sizeof(wchar_t)), &dwIgn, NULL);
     306    WriteFile(hLogFile, L"'\r\n", 3 * sizeof(wchar_t), &dwIgn, NULL);
     307}
     308
     309#ifdef RT_ARCH_X86
     310
     311/**
     312 * Interceptor WinVerifyTrust function for SetupApi.dll on Windows 2000, XP,
     313 * W2K3 and XP64.
     314 *
     315 * This crudely modifies the driver verification request from a WHQL/logo driver
     316 * check to a simple Authenticode check.
     317 */
     318static LONG WINAPI InterceptedWinVerifyTrust(HWND hwnd, GUID *pActionId, void *pvData)
     319{
     320    /*
     321     * Resolve the real WinVerifyTrust function.
     322     */
     323    static decltype(WinVerifyTrust) * volatile s_pfnRealWinVerifyTrust = NULL;
     324    decltype(WinVerifyTrust) *pfnRealWinVerifyTrust = s_pfnRealWinVerifyTrust;
     325    if (!pfnRealWinVerifyTrust)
     326    {
     327        HMODULE hmod = GetModuleHandleW(L"WINTRUST.DLL");
     328        if (!hmod)
     329            hmod = LoadLibraryW(L"WINTRUST.DLL");
     330        if (!hmod)
     331        {
     332            ErrorMsgLastErr("InterceptedWinVerifyTrust: Failed to load wintrust.dll");
     333            return TRUST_E_SYSTEM_ERROR;
     334        }
     335        pfnRealWinVerifyTrust = (decltype(WinVerifyTrust) *)GetProcAddress(hmod, "WinVerifyTrust");
     336        if (!pfnRealWinVerifyTrust)
     337        {
     338            ErrorMsg("InterceptedWinVerifyTrust: Failed to locate WinVerifyTrust in wintrust.dll");
     339            return TRUST_E_SYSTEM_ERROR;
     340        }
     341        s_pfnRealWinVerifyTrust = pfnRealWinVerifyTrust;
     342    }
     343
     344    /*
     345     * Modify the ID if appropriate.
     346     */
     347    static const GUID s_GuidDriverActionVerify       = DRIVER_ACTION_VERIFY;
     348    static const GUID s_GuidActionGenericChainVerify = WINTRUST_ACTION_GENERIC_CHAIN_VERIFY;
     349    static const GUID s_GuidActionGenericVerify2     = WINTRUST_ACTION_GENERIC_VERIFY_V2;
     350    if (pActionId)
     351    {
     352        if (memcmp(pActionId, &s_GuidDriverActionVerify, sizeof(*pActionId)) == 0)
     353        {
     354            /** @todo don't apply to obvious NT components... */
     355            PrintStr("DRIVER_ACTION_VERIFY: Changing it to WINTRUST_ACTION_GENERIC_VERIFY_V2\r\n");
     356            pActionId = (GUID *)&s_GuidActionGenericVerify2;
     357        }
     358        else if (memcmp(pActionId, &s_GuidActionGenericChainVerify, sizeof(*pActionId)) == 0)
     359            PrintStr("WINTRUST_ACTION_GENERIC_CHAIN_VERIFY\r\n");
     360        else if (memcmp(pActionId, &s_GuidActionGenericVerify2, sizeof(*pActionId)) == 0)
     361            PrintStr("WINTRUST_ACTION_GENERIC_VERIFY_V2\r\n");
     362        else
     363            PrintStr("WINTRUST_ACTION_UNKNOWN\r\n");
     364    }
     365
     366    /*
     367     * Log the data.
     368     */
     369    if (pvData)
     370    {
     371        WINTRUST_DATA *pData = (WINTRUST_DATA *)pvData;
     372        PrintSXS("                  cbStruct = ", pData->cbStruct, "\r\n");
     373# ifdef DEBUG
     374        PrintSXS("                dwUIChoice = ", pData->dwUIChoice, "\r\n");
     375        PrintSXS("       fdwRevocationChecks = ", pData->fdwRevocationChecks, "\r\n");
     376        PrintSXS("             dwStateAction = ", pData->dwStateAction, "\r\n");
     377        PrintSXS("             hWVTStateData = ", (uintptr_t)pData->hWVTStateData, "\r\n");
     378# endif
     379        if (pData->cbStruct >= 7*sizeof(uint32_t))
     380        {
     381            switch (pData->dwUnionChoice)
     382            {
     383                case WTD_CHOICE_FILE:
     384                    PrintSXS("                     pFile = ", (uintptr_t)pData->pFile, "\r\n");
     385                    if (RT_VALID_PTR(pData->pFile))
     386                    {
     387                        PrintSXS("           pFile->cbStruct = ", pData->pFile->cbStruct, "\r\n");
     388# ifndef DEBUG
     389                        if (pData->pFile->hFile)
     390# endif
     391                            PrintSXS("              pFile->hFile = ", (uintptr_t)pData->pFile->hFile, "\r\n");
     392                        if (RT_VALID_PTR(pData->pFile->pcwszFilePath))
     393                            PrintSWS("      pFile->pcwszFilePath = '", pData->pFile->pcwszFilePath, "'\r\n");
     394# ifdef DEBUG
     395                        else
     396                            PrintSXS("      pFile->pcwszFilePath = ", (uintptr_t)pData->pFile->pcwszFilePath, "\r\n");
     397                        PrintSXS("     pFile->pgKnownSubject = ", (uintptr_t)pData->pFile->pgKnownSubject, "\r\n");
     398# endif
     399                    }
     400                    break;
     401
     402                case WTD_CHOICE_CATALOG:
     403                    PrintSXS("                  pCatalog = ", (uintptr_t)pData->pCatalog, "\r\n");
     404                    if (RT_VALID_PTR(pData->pCatalog))
     405                    {
     406                        PrintSXS("            pCat->cbStruct = ", pData->pCatalog->cbStruct, "\r\n");
     407# ifdef DEBUG
     408                        PrintSXS("    pCat->dwCatalogVersion = ", pData->pCatalog->dwCatalogVersion, "\r\n");
     409# endif
     410                        if (RT_VALID_PTR(pData->pCatalog->pcwszCatalogFilePath))
     411                            PrintSWS("pCat->pcwszCatalogFilePath = '", pData->pCatalog->pcwszCatalogFilePath, "'\r\n");
     412# ifdef DEBUG
     413                        else
     414                            PrintSXS("pCat->pcwszCatalogFilePath = ", (uintptr_t)pData->pCatalog->pcwszCatalogFilePath, "\r\n");
     415# endif
     416                        if (RT_VALID_PTR(pData->pCatalog->pcwszMemberTag))
     417                            PrintSWS("      pCat->pcwszMemberTag = '", pData->pCatalog->pcwszMemberTag, "'\r\n");
     418# ifdef DEBUG
     419                        else
     420                            PrintSXS("      pCat->pcwszMemberTag = ", (uintptr_t)pData->pCatalog->pcwszMemberTag, "\r\n");
     421# endif
     422                        if (RT_VALID_PTR(pData->pCatalog->pcwszMemberFilePath))
     423                            PrintSWS(" pCat->pcwszMemberFilePath = '", pData->pCatalog->pcwszMemberFilePath, "'\r\n");
     424# ifdef DEBUG
     425                        else
     426                            PrintSXS(" pCat->pcwszMemberFilePath = ", (uintptr_t)pData->pCatalog->pcwszMemberFilePath, "\r\n");
     427# else
     428                        if (pData->pCatalog->hMemberFile)
     429# endif
     430                            PrintSXS("         pCat->hMemberFile = ", (uintptr_t)pData->pCatalog->hMemberFile, "\r\n");
     431# ifdef DEBUG
     432                        PrintSXS("pCat->pbCalculatedFileHash = ", (uintptr_t)pData->pCatalog->pbCalculatedFileHash, "\r\n");
     433                        PrintSXS("pCat->cbCalculatedFileHash = ", pData->pCatalog->cbCalculatedFileHash, "\r\n");
     434                        PrintSXS("    pCat->pcCatalogContext = ", (uintptr_t)pData->pCatalog->pcCatalogContext, "\r\n");
     435# endif
     436                    }
     437                    break;
     438
     439                case WTD_CHOICE_BLOB:
     440                    PrintSXS("                     pBlob = ", (uintptr_t)pData->pBlob, "\r\n");
     441                    break;
     442
     443                case WTD_CHOICE_SIGNER:
     444                    PrintSXS("                     pSgnr = ", (uintptr_t)pData->pSgnr, "\r\n");
     445                    break;
     446
     447                case WTD_CHOICE_CERT:
     448                    PrintSXS("                     pCert = ", (uintptr_t)pData->pCert, "\r\n");
     449                    break;
     450
     451                default:
     452                    PrintSXS("             dwUnionChoice = ", pData->dwUnionChoice, "\r\n");
     453                    break;
     454            }
     455        }
     456    }
     457
     458    /*
     459     * Make the call.
     460     */
     461    PrintStr("Calling WinVerifyTrust ...\r\n");
     462    LONG iRet = pfnRealWinVerifyTrust(hwnd, pActionId, pvData);
     463    PrintSXS("WinVerifyTrust returns ", (ULONG)iRet, "\r\n");
     464
     465    return iRet;
     466}
     467
     468
     469/**
     470 * Installs an WinVerifyTrust interceptor in setupapi.dll on Windows 2000, XP,
     471 * W2K3 and XP64.
     472 *
     473 * This is a very crude hack to lower the WHQL check to just require a valid
     474 * Authenticode signature by intercepting the verification call.
     475 *
     476 * @return Ignored, just a convenience for saving space in error paths.
     477 */
     478static int InstallWinVerifyTrustInterceptorInSetupApi(void)
     479{
     480    /* Check the version: */
     481    OSVERSIONINFOW VerInfo = { sizeof(VerInfo) };
     482    GetVersionExW(&VerInfo);
     483    if (VerInfo.dwMajorVersion != 5)
     484        return 1;
     485
     486    /* The the target module: */
     487    HMODULE hModSetupApi = GetModuleHandleW(L"SETUPAPI.DLL");
     488    if (!hModSetupApi)
     489        return ErrorMsgLastErr("Failed to locate SETUPAPI.DLL in the process");
     490
     491    /*
     492     * Find the delayed import table (at least that's how it's done in the RTM).
     493     */
     494    IMAGE_DOS_HEADER const *pDosHdr = (IMAGE_DOS_HEADER const *)hModSetupApi;
     495    IMAGE_NT_HEADERS const *pNtHdrs = (IMAGE_NT_HEADERS const *)(  (uintptr_t)hModSetupApi
     496                                                                 + (  pDosHdr->e_magic == IMAGE_DOS_SIGNATURE
     497                                                                    ? pDosHdr->e_lfanew : 0));
     498    if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
     499        return ErrorMsgSU("Failed to parse SETUPAPI.DLL for WinVerifyTrust interception: #", 1);
     500    if (pNtHdrs->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
     501        return ErrorMsgSU("Failed to parse SETUPAPI.DLL for WinVerifyTrust interception: #", 2);
     502    if (pNtHdrs->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT)
     503        return ErrorMsgSU("Failed to parse SETUPAPI.DLL for WinVerifyTrust interception: #", 3);
     504
     505    uint32_t const cbDir = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].Size;
     506    if (cbDir < sizeof(IMAGE_DELAYLOAD_DESCRIPTOR))
     507        return ErrorMsgSU("Failed to parse SETUPAPI.DLL for WinVerifyTrust interception: #", 4);
     508    uint32_t const cbImages = pNtHdrs->OptionalHeader.SizeOfImage;
     509    if (cbDir >= cbImages)
     510        return ErrorMsgSU("Failed to parse SETUPAPI.DLL for WinVerifyTrust interception: #", 5);
     511    uint32_t const offDir = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress;
     512    if (offDir > cbImages - cbDir)
     513        return ErrorMsgSU("Failed to parse SETUPAPI.DLL for WinVerifyTrust interception: #", 6);
     514
     515    /*
     516     * Scan the entries looking for wintrust.dll.
     517     */
     518    IMAGE_DELAYLOAD_DESCRIPTOR const * const paEntries = (IMAGE_DELAYLOAD_DESCRIPTOR const *)((uintptr_t)hModSetupApi + offDir);
     519    uint32_t const                           cEntries  = cbDir / sizeof(paEntries[0]);
     520    for (uint32_t iImp = 0; iImp < cEntries; iImp++)
     521    {
     522        const char * const pchRva2Ptr = paEntries[iImp].Attributes.RvaBased ? (const char *)hModSetupApi : (const char *)0;
     523        const char * const pszDllName = &pchRva2Ptr[paEntries[iImp].DllNameRVA];
     524        if (RTStrICmpAscii(pszDllName, "WINTRUST.DLL") == 0)
     525        {
     526            /*
     527             * Scan the symbol names.
     528             */
     529            uint32_t const    cbHdrs      = pNtHdrs->OptionalHeader.SizeOfHeaders;
     530            uint32_t * const  pauNameRvas = (uint32_t  *)&pchRva2Ptr[paEntries[iImp].ImportNameTableRVA];
     531            uintptr_t * const paIat       = (uintptr_t *)&pchRva2Ptr[paEntries[iImp].ImportAddressTableRVA];
     532            for (uint32_t iSym = 0; pauNameRvas[iSym] != NULL; iSym++)
     533            {
     534                IMAGE_IMPORT_BY_NAME const * const pName = (IMAGE_IMPORT_BY_NAME const *)&pchRva2Ptr[pauNameRvas[iSym]];
     535                if (RTStrCmp(pName->Name, "WinVerifyTrust") == 0)
     536                {
     537                    PrintSXS("Intercepting WinVerifyTrust for SETUPAPI.DLL (old: ", paIat[iSym], ")\r\n");
     538                    paIat[iSym] = (uintptr_t)InterceptedWinVerifyTrust;
     539                    return 0;
     540                }
     541            }
     542            return ErrorMsgSU("Failed to parse SETUPAPI.DLL for WinVerifyTrust interception: #", 9);
     543        }
     544    }
     545    return ErrorMsgSU("Failed to parse SETUPAPI.DLL for WinVerifyTrust interception: #", 10);
     546}
     547
     548#endif /* RT_ARCH_X86 */
    252549
    253550/**
     
    323620     * Windows 2000 and later.
    324621     */
    325     OSVERSIONINFO VerInfo = { sizeof(VerInfo) };
    326     GetVersionEx(&VerInfo);
     622    OSVERSIONINFOW VerInfo = { sizeof(VerInfo) };
     623    GetVersionExW(&VerInfo);
    327624    if (VerInfo.dwPlatformId != VER_PLATFORM_WIN32_NT)
    328625        return ErrorMsg("Platform not supported for driver (un)installation!");
     
    367664        hLogFile = CreateFileW(pwszLogFile, FILE_GENERIC_WRITE & ~FILE_WRITE_DATA /* append mode */, FILE_SHARE_READ,
    368665                               NULL /*pSecAttr*/, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
    369         if (hLogFile == INVALID_HANDLE_VALUE)
     666        if (hLogFile != INVALID_HANDLE_VALUE)
     667            VBoxDIFxWriteLogHeader(hLogFile, fInstall ? "install" : "uninstall", pwszDriverPath);
     668        else
    370669            ErrorMsgLastErrSWS("Failed to open/create log file '", pwszLogFile, "'");
    371670        g_pfnDIFXAPISetLogCallback(VBoxDIFxLogCallback, (void *)hLogFile);
     
    374673    PrintStr(fInstall ? "Installing driver ...\r\n" : "Uninstalling driver ...\r\n");
    375674    PrintSWS("INF-File: '", wszFullDriverInf, "'\r\n");
     675#ifdef RT_ARCH_X86
     676    InstallWinVerifyTrustInterceptorInSetupApi();
     677#endif
    376678
    377679    INSTALLERINFO InstInfo =
     
    589891{
    590892    PrintSWSWS("Installing from INF-File: '", pwszInf, "', Section: '", pwszSection, "' ...\r\n");
     893#ifdef RT_ARCH_X86
     894    InstallWinVerifyTrustInterceptorInSetupApi();
     895#endif
    591896
    592897    UINT uErrorLine = 0;
     
    9851290
    9861291    /* Make sure we're on NT4 before continuing: */
    987     OSVERSIONINFO VerInfo = { sizeof(VerInfo) };
    988     GetVersionEx(&VerInfo);
     1292    OSVERSIONINFOW VerInfo = { sizeof(VerInfo) };
     1293    GetVersionExW(&VerInfo);
    9891294    if (   VerInfo.dwPlatformId != VER_PLATFORM_WIN32_NT
    9901295        || VerInfo.dwMajorVersion != 4)
    991         return ErrorMsgSUSUS("This command is only for NT 4. GetVersionEx reports ", VerInfo.dwMajorVersion, ".",
     1296        return ErrorMsgSUSUS("This command is only for NT 4. GetVersionExW reports ", VerInfo.dwMajorVersion, ".",
    9921297                             VerInfo.dwMinorVersion, ".");
    9931298
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette