/*++ Copyright (c) 1997 Microsoft Corporation Abstract: This module provides functionality for publishing printers Author: Steve Wilson (NT) November 1997 Revision History: --*/ #include "precomp.h" #pragma hdrstop #include "pubprn.hxx" #include "varconv.hxx" #include "property.hxx" #include "dsutil.hxx" #include "client.h" #define PPM_FACTOR 48 BOOL PublishPrinterW( HWND hwnd, PCWSTR pszUNCName, PCWSTR pszDN, PCWSTR pszCN, PWSTR *ppszDN, DWORD dwAction ) { PRINTER_DEFAULTS Defaults; HANDLE hPrinter = NULL; HANDLE hServer = NULL; PWSTR pszServerName = NULL; PWSTR pszPrinterName = NULL; PPRINTER_INFO_2 pInfo2 = NULL; DWORD dwRet = ERROR_SUCCESS; DWORD dwType; DWORD dwMajorVersion; DWORD dwDsPresent; DWORD cbNeeded; DWORD dwLength; HRESULT hr; WCHAR szDNSMachineName[INTERNET_MAX_HOST_NAME_LENGTH + 1]; WCHAR szFullUNCName[MAX_UNC_PRINTER_NAME]; WCHAR szShortServerName[MAX_PATH+1]; PWSTR pszFullUNCName; PWSTR pszShortServerName; PWSTR pszFullServerName; if (InCSRProcess()) { SetLastError(ERROR_NOT_SUPPORTED); return FALSE; } hr = CoInitialize(NULL); if (hr != S_OK && hr != S_FALSE) { SetLastError((DWORD)((HRESULT_FACILITY(hr) == FACILITY_WIN32) ? HRESULT_CODE(hr) : hr)); return FALSE; } if (ppszDN) *ppszDN = NULL; // // Get server name // if (dwRet = UNC2Server(pszUNCName, &pszServerName)) goto error; if(!OpenPrinter(pszServerName, &hServer, NULL)) { dwMajorVersion = 0; } else { dwRet = GetPrinterData( hServer, SPLREG_MAJOR_VERSION, &dwType, (PBYTE) &dwMajorVersion, sizeof dwMajorVersion, &cbNeeded); if (dwRet != ERROR_SUCCESS) { // // ignore errors and assume lowest version // dwMajorVersion = 0; dwRet = ERROR_SUCCESS; } if (dwMajorVersion >= WIN2000_SPOOLER_VERSION) { hr = MachineIsInMyForest(pszServerName); if (FAILED(hr)) { dwRet = HRESULT_CODE(hr); goto error; } else if(HRESULT_CODE(hr) == 1) { // // Machine is in my forest and is NT5+ // dwRet = ERROR_INVALID_LEVEL; goto error; } else { // // Downgrade the version for NT5+ printers published in a non-DS domain // dwMajorVersion = WIN2000_SPOOLER_VERSION; } } } Defaults.pDatatype = NULL; Defaults.pDevMode = NULL; Defaults.DesiredAccess = PRINTER_ACCESS_USE; if (!OpenPrinter((PWSTR) pszUNCName, &hPrinter, &Defaults)) { dwRet = GetLastError(); goto error; } hr = GetPrinterInfo2(hPrinter, &pInfo2); if (FAILED(hr)) { dwRet = HRESULT_CODE(hr); goto error; } if (dwRet = UNC2Printer(pInfo2->pPrinterName, &pszPrinterName)) goto error; if( dwMajorVersion >= WIN2000_SPOOLER_VERSION){ if(dwRet = GetPrinterData( hServer, SPLREG_DNS_MACHINE_NAME, &dwType, (PBYTE) szDNSMachineName, (INTERNET_MAX_HOST_NAME_LENGTH + 1) * sizeof(WCHAR), &cbNeeded) != ERROR_SUCCESS ) { goto error; } if (FAILED(hr = StringCchPrintf( szFullUNCName, COUNTOF(szFullUNCName), L"\\\\%s\\%s", szDNSMachineName, pszPrinterName ))) { dwRet = HRESULT_CODE(hr); goto error; } dwLength = MAX_PATH + 1; if (!DnsHostnameToComputerName(pszServerName, szShortServerName, &dwLength)) { dwRet = GetLastError(); goto error; } pszFullUNCName = szFullUNCName; pszFullServerName = szDNSMachineName; pszShortServerName = szShortServerName+2; } else { pszFullUNCName = (PWSTR)pszUNCName; pszFullServerName = pszServerName+2; pszShortServerName = pszServerName+2; } // // Verify PrintQueue doesn't already exist // if (dwAction != PUBLISHPRINTER_IGNORE_DUPLICATES) { if(dwRet = PrintQueueExists(hwnd, hPrinter, pszFullUNCName, dwAction, (PWSTR) pszDN, (PWSTR *) ppszDN)) goto error; } if (dwRet = PublishDownlevelPrinter(hPrinter, (PWSTR) pszDN, (PWSTR) pszCN, pszFullServerName, pszShortServerName, pszFullUNCName, pszPrinterName, dwMajorVersion, ppszDN)) goto error; error: if (hPrinter != NULL) ClosePrinter(hPrinter); if (hServer != NULL) ClosePrinter(hServer); if (pszServerName) FreeSplMem(pszServerName); if (pszPrinterName) FreeSplMem(pszPrinterName); FreeSplMem(pInfo2); if (dwRet != ERROR_SUCCESS) { SetLastError(dwRet); return FALSE; } CoUninitialize(); return TRUE; } DWORD PublishDownlevelPrinter( HANDLE hPrinter, PWSTR pszDN, PWSTR pszCN, PWSTR pszServerName, PWSTR pszShortServerName, PWSTR pszUNCName, PWSTR pszPrinterName, DWORD dwVersion, PWSTR *ppszObjectDN ) { HRESULT hr = S_OK; DWORD dwRet = ERROR_SUCCESS; IADs *pPrintQueue = NULL; IADsContainer *pADsContainer = NULL; IDispatch *pDispatch = NULL; PWSTR pszCommonName = pszCN; BSTR bstrADsPath = NULL; PWSTR pszDNWithDC = NULL; if (ppszObjectDN) *ppszObjectDN = NULL; // // If pszCN is not supplied, generate default common name // if (!pszCommonName || !*pszCommonName) { dwRet = GetCommonName(hPrinter, &pszCommonName); if (dwRet != ERROR_SUCCESS) { hr = dw2hr(dwRet); BAIL_ON_FAILURE(hr); } } // // Stick DC in DN // if (!(pszDNWithDC = GetDNWithServer(pszDN))) { pszDNWithDC = pszDN; } // // Get container // hr = ADsGetObject(pszDNWithDC, IID_IADsContainer, (void **) &pADsContainer); BAIL_ON_FAILURE(hr); // // Create printqueue // hr = pADsContainer->Create(SPLDS_PRINTER_CLASS, pszCommonName, &pDispatch); BAIL_ON_FAILURE(hr); hr = pDispatch->QueryInterface(IID_IADs, (void **) &pPrintQueue); BAIL_ON_FAILURE(hr); // // Set properties // hr = SetProperties( hPrinter, pszServerName, pszShortServerName, pszUNCName, pszPrinterName, dwVersion, pPrintQueue); BAIL_ON_FAILURE(hr); // // Get ADsPath to printQueue // if (ppszObjectDN) { hr = pPrintQueue->get_ADsPath(&bstrADsPath); BAIL_ON_FAILURE(hr); if (!(*ppszObjectDN = AllocGlobalStr(bstrADsPath))) { dwRet = GetLastError(); hr = dw2hr(dwRet); BAIL_ON_FAILURE(hr); } } error: if (pszDNWithDC != pszDN) FreeSplMem(pszDNWithDC); if (bstrADsPath) SysFreeString(bstrADsPath); if (pszCommonName != pszCN) FreeSplMem(pszCommonName); if (pADsContainer) pADsContainer->Release(); if (pDispatch) pDispatch->Release(); if (pPrintQueue) pPrintQueue->Release(); if (FAILED(hr) && ppszObjectDN && *ppszObjectDN) FreeGlobalStr(*ppszObjectDN); return hr2dw(hr); } HRESULT SetProperties( HANDLE hPrinter, PWSTR pszServerName, PWSTR pszShortServerName, PWSTR pszUNCName, PWSTR pszPrinterName, DWORD dwVersion, IADs *pPrintQueue ) { HRESULT hr; hr = SetMandatoryProperties(pszServerName, pszShortServerName, pszUNCName, pszPrinterName, dwVersion, pPrintQueue); BAIL_ON_FAILURE(hr); SetSpoolerProperties(hPrinter, pPrintQueue, dwVersion); SetDriverProperties(hPrinter, pPrintQueue); error: return hr; } HRESULT SetMandatoryProperties( PWSTR pszServerName, PWSTR pszShortServerName, PWSTR pszUNCName, PWSTR pszPrinterName, DWORD dwVersion, IADs *pPrintQueue ) { HRESULT hr; // // ServerName // hr = put_BSTR_Property(pPrintQueue, SPLDS_SERVER_NAME, pszServerName); BAIL_ON_FAILURE(hr); // // ShortServerName // hr = put_BSTR_Property(pPrintQueue, SPLDS_SHORT_SERVER_NAME, pszShortServerName); BAIL_ON_FAILURE(hr); // // UNC Name // hr = put_BSTR_Property(pPrintQueue, SPLDS_UNC_NAME, pszUNCName); BAIL_ON_FAILURE(hr); // // PrinterName // hr = put_BSTR_Property(pPrintQueue, SPLDS_PRINTER_NAME, pszPrinterName); BAIL_ON_FAILURE(hr); // // versionNumber // hr = put_DWORD_Property(pPrintQueue, SPLDS_VERSION_NUMBER, &dwVersion); BAIL_ON_FAILURE(hr); hr = pPrintQueue->SetInfo(); if (FAILED(hr)) pPrintQueue->GetInfo(); error: return hr; } HRESULT SetSpoolerProperties( HANDLE hPrinter, IADs *pPrintQueue, DWORD dwVersion ) { HRESULT hr = E_FAIL; PPRINTER_INFO_2 pInfo2 = NULL; DWORD cbNeeded; BYTE Byte; PWSTR psz; // // Get PRINTER_INFO_2 properties // if (!GetPrinter(hPrinter, 2, (PBYTE) pInfo2, 0, &cbNeeded)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { hr = dw2hr(GetLastError()); goto error; } if (!(pInfo2 = (PPRINTER_INFO_2) AllocSplMem(cbNeeded))) { hr = dw2hr(GetLastError()); goto error; } if (!GetPrinter(hPrinter, 2, (PBYTE) pInfo2, cbNeeded, &cbNeeded)) { hr = dw2hr(GetLastError()); goto error; } // // Description // hr = PublishDsData( pPrintQueue, SPLDS_DESCRIPTION, REG_SZ, (PBYTE) pInfo2->pComment); // // Driver-Name // hr = PublishDsData( pPrintQueue, SPLDS_DRIVER_NAME, REG_SZ, (PBYTE) pInfo2->pDriverName); // // Location // hr = PublishDsData( pPrintQueue, SPLDS_LOCATION, REG_SZ, (PBYTE) pInfo2->pLocation); // // portName (Port1,Port2,Port3) // if (pInfo2->pPortName) { PWSTR pszPortName; // // copy comma delimited strings to Multi-sz format // pszPortName = DelimString2MultiSz(pInfo2->pPortName, L','); if (pszPortName) { hr = PublishDsData( pPrintQueue, SPLDS_PORT_NAME, REG_MULTI_SZ, (PBYTE) pszPortName); FreeSplMem(pszPortName); } } // // startTime // hr = PublishDsData( pPrintQueue, SPLDS_PRINT_START_TIME, REG_DWORD, (PBYTE) &pInfo2->StartTime); // // endTime // hr = PublishDsData( pPrintQueue, SPLDS_PRINT_END_TIME, REG_DWORD, (PBYTE) &pInfo2->UntilTime); // keepPrintedJobs Byte = pInfo2->Attributes & PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS ? 1 : 0; hr = PublishDsData( pPrintQueue, SPLDS_PRINT_KEEP_PRINTED_JOBS, REG_BINARY, (PBYTE) &Byte ); // // printSeparatorFile // hr = PublishDsData( pPrintQueue, SPLDS_PRINT_SEPARATOR_FILE, REG_SZ, (PBYTE) pInfo2->pSepFile); // // printShareName // hr = PublishDsData( pPrintQueue, SPLDS_PRINT_SHARE_NAME, REG_SZ, (PBYTE) pInfo2->pShareName); // // printSpooling // if (pInfo2->Attributes & PRINTER_ATTRIBUTE_DIRECT) { psz = L"PrintDirect"; } else if (pInfo2->Attributes & PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST) { psz = L"PrintAfterSpooled"; } else { psz = L"PrintWhileSpooling"; } hr = PublishDsData( pPrintQueue, SPLDS_PRINT_SPOOLING, REG_SZ, (PBYTE) psz); // // priority // hr = PublishDsData( pPrintQueue, SPLDS_PRIORITY, REG_DWORD, (PBYTE) &pInfo2->Priority); // // Non-Info2 properties // URL - downlevel machines don't support http printers, so don't publish useless url // if (dwVersion >= WIN2000_SPOOLER_VERSION) { DWORD dwRet, dwType; PWSTR pszUrl = NULL; // // Get the url from the print server // dwRet = GetPrinterDataEx( hPrinter, SPLDS_SPOOLER_KEY, SPLDS_URL, &dwType, (PBYTE) pszUrl, 0, &cbNeeded); if (dwRet == ERROR_MORE_DATA) { if ((pszUrl = (PWSTR) AllocSplMem(cbNeeded))) { dwRet = GetPrinterDataEx( hPrinter, SPLDS_SPOOLER_KEY, SPLDS_URL, &dwType, (PBYTE) pszUrl, cbNeeded, &cbNeeded); if (dwRet == ERROR_SUCCESS && dwType == REG_SZ) { hr = PublishDsData( pPrintQueue, SPLDS_URL, REG_SZ, (PBYTE) pszUrl); } FreeSplMem(pszUrl); } } } } error: FreeSplMem(pInfo2); return hr; } HRESULT SetDriverProperties( HANDLE hPrinter, IADs *pPrintQueue ) { DWORD i, cbBytes, dwCount; LPWSTR pStr; DWORD dwResult; LPWSTR pOutput = NULL, pTemp = NULL, pTemp1 = NULL; DWORD cOutputBytes, cTempBytes; POINTS point; WCHAR pBuf[100]; BOOL bInSplSem = TRUE; DWORD dwTemp, dwPrintRate, dwPrintRateUnit, dwPrintPPM; HRESULT hr = S_OK; PPRINTER_INFO_2 pInfo2 = NULL; PWSTR pszUNCName; // // Get UNCName // hr = GetPrinterInfo2(hPrinter, &pInfo2); BAIL_ON_FAILURE(hr); if (!pInfo2) { hr = dw2hr(GetLastError()); goto error; } pszUNCName = pInfo2->pPrinterName; // // DeviceCapability properties // pOutput = (PWSTR) AllocSplMem(cOutputBytes = 200); if (!pOutput) { hr = dw2hr(GetLastError()); goto error; } pTemp = (PWSTR) AllocSplMem(cTempBytes = 200); if (!pTemp) { hr = dw2hr(GetLastError()); goto error; } // // printBinNames // DevCapMultiSz( pszUNCName, pPrintQueue, DC_BINNAMES, 24, SPLDS_PRINT_BIN_NAMES); // // printCollate (awaiting DC_COLLATE) // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_COLLATE, NULL, NULL); if (dwResult != GDI_ERROR) { hr = PublishDsData( pPrintQueue, SPLDS_PRINT_COLLATE, REG_BINARY, (PBYTE) &dwResult); } // // printColor // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_COLORDEVICE, NULL, NULL); if (dwResult == GDI_ERROR) { // // Try alternative method // dwResult = ThisIsAColorPrinter(pszUNCName); } hr = PublishDsData( pPrintQueue, SPLDS_PRINT_COLOR, REG_BINARY, (PBYTE) &dwResult); // // printDuplexSupported // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_DUPLEX, NULL, NULL); if (dwResult != GDI_ERROR) { hr = PublishDsData( pPrintQueue, SPLDS_PRINT_DUPLEX_SUPPORTED, REG_BINARY, (PBYTE) &dwResult); } // // printStaplingSupported // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_STAPLE, NULL, NULL); if (dwResult != GDI_ERROR) { hr = PublishDsData( pPrintQueue, SPLDS_PRINT_STAPLING_SUPPORTED, REG_BINARY, (PBYTE) &dwResult); } // // printMaxXExtent & printMaxYExtent // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_MAXEXTENT, NULL, NULL); if (dwResult != GDI_ERROR) { *((DWORD *) &point) = dwResult; dwTemp = (DWORD) point.x; hr = PublishDsData( pPrintQueue, SPLDS_PRINT_MAX_X_EXTENT, REG_DWORD, (PBYTE) &dwTemp); dwTemp = (DWORD) point.y; hr = PublishDsData( pPrintQueue, SPLDS_PRINT_MAX_Y_EXTENT, REG_DWORD, (PBYTE) &dwTemp); } // // printMinXExtent & printMinYExtent // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_MINEXTENT, NULL, NULL); if (dwResult != GDI_ERROR) { *((DWORD *) &point) = dwResult; dwTemp = (DWORD) point.x; hr = PublishDsData( pPrintQueue, SPLDS_PRINT_MIN_X_EXTENT, REG_DWORD, (PBYTE) &dwTemp); dwTemp = (DWORD) point.y; hr = PublishDsData( pPrintQueue, SPLDS_PRINT_MIN_Y_EXTENT, REG_DWORD, (PBYTE) &dwTemp); } // // printMediaSupported // DevCapMultiSz( pszUNCName, pPrintQueue, DC_PAPERNAMES, 64, SPLDS_PRINT_MEDIA_SUPPORTED); // // printMediaReady // DevCapMultiSz( pszUNCName, pPrintQueue, DC_MEDIAREADY, 64, SPLDS_PRINT_MEDIA_READY); // // printNumberUp // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_NUP, NULL, NULL); if (dwResult != GDI_ERROR) { // // DS NUp is boolean // dwResult = !!dwResult; hr = PublishDsData( pPrintQueue, SPLDS_PRINT_DUPLEX_SUPPORTED, REG_DWORD, (PBYTE) &dwResult); } // // printMemory // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_PRINTERMEM, NULL, NULL); if (dwResult != GDI_ERROR) { hr = PublishDsData( pPrintQueue, SPLDS_PRINT_MEMORY, REG_DWORD, (PBYTE) &dwResult); } // // printOrientationsSupported // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_ORIENTATION, NULL, NULL); if (dwResult != GDI_ERROR) { StringCchCopy(pStr = pBuf, COUNTOF(pBuf), L"PORTRAIT"); if (dwResult == 90 || dwResult == 270) { ULONG cchBuf = wcslen(pBuf) + 1; StringCchCopy(pStr = pBuf + cchBuf, COUNTOF(pBuf) - cchBuf, L"LANDSCAPE"); } else { pStr = pBuf; } pStr += wcslen(pStr) + 1; *pStr++ = L'\0'; hr = PublishDsData( pPrintQueue, SPLDS_PRINT_ORIENTATIONS_SUPPORTED, REG_MULTI_SZ, (PBYTE) pBuf); } // // printMaxResolutionSupported // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_ENUMRESOLUTIONS, NULL, NULL); if (dwResult != GDI_ERROR) { if (cOutputBytes < dwResult*2*sizeof(DWORD)) { if(!(pTemp1 = (PWSTR) ReallocSplMem(pOutput, 0, cOutputBytes = dwResult*2*sizeof(DWORD)))) goto error; pOutput = pTemp1; } dwResult = DeviceCapabilities( pszUNCName, NULL, DC_ENUMRESOLUTIONS, pOutput, NULL); if (dwResult == GDI_ERROR) goto error; // // Find the maximum resolution: we have dwResult*2 resolutions to check // for(i = dwTemp = 0 ; i < dwResult*2 ; ++i) { if (((DWORD *) pOutput)[i] > dwTemp) dwTemp = ((DWORD *) pOutput)[i]; } hr = PublishDsData( pPrintQueue, SPLDS_PRINT_MAX_RESOLUTION_SUPPORTED, REG_DWORD, (PBYTE) &dwTemp); } // // printLanguage // DevCapMultiSz( pszUNCName, pPrintQueue, DC_PERSONALITY, 32, SPLDS_PRINT_LANGUAGE); // // printRate // NOTE: If PrintRate is 0, no value is published // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_PRINTRATE, NULL, NULL); dwPrintRate = dwResult ? dwResult : GDI_ERROR; if (dwPrintRate != GDI_ERROR) { hr = PublishDsData( pPrintQueue, SPLDS_PRINT_RATE, REG_DWORD, (PBYTE) &dwPrintRate); } // // printRateUnit // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_PRINTRATEUNIT, NULL, NULL); dwPrintRateUnit = dwResult; if (dwPrintRateUnit != GDI_ERROR) { switch (dwPrintRateUnit) { case PRINTRATEUNIT_PPM: pStr = L"PagesPerMinute"; break; case PRINTRATEUNIT_CPS: pStr = L"CharactersPerSecond"; break; case PRINTRATEUNIT_LPM: pStr = L"LinesPerMinute"; break; case PRINTRATEUNIT_IPM: pStr = L"InchesPerMinute"; break; default: pStr = L""; break; } hr = PublishDsData( pPrintQueue, SPLDS_PRINT_RATE_UNIT, REG_SZ, (PBYTE) pStr); } // // printPagesPerMinute // DevCap returns 0 if there is no entry in GPD // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_PRINTRATEPPM, NULL, NULL); if (dwResult == GDI_ERROR) dwResult = 0; dwPrintPPM = dwResult; // // If dwPrintPPM == 0, then calculate PPM from PrintRate // if (dwPrintPPM == 0) { if (dwPrintRate == GDI_ERROR) { dwPrintPPM = GDI_ERROR; } else { switch (dwPrintRateUnit) { case PRINTRATEUNIT_PPM: dwPrintPPM = dwPrintRate; break; case PRINTRATEUNIT_CPS: case PRINTRATEUNIT_LPM: dwPrintPPM = dwPrintRate/PPM_FACTOR; if (dwPrintPPM == 0) { // // min PPM is 1 // dwPrintPPM = 1; } break; default: dwPrintPPM = GDI_ERROR; break; } } } if (dwPrintPPM != GDI_ERROR) { hr = PublishDsData( pPrintQueue, SPLDS_PRINT_PAGES_PER_MINUTE, REG_DWORD, (PBYTE) &dwPrintPPM); } // // printDriverVersion // dwResult = DeviceCapabilities( pszUNCName, NULL, DC_VERSION, NULL, NULL); if (dwResult != GDI_ERROR) { hr = PublishDsData( pPrintQueue, SPLDS_DRIVER_VERSION, REG_DWORD, (PBYTE) &dwResult); } error: FreeSplMem(pInfo2); if (pOutput) FreeSplMem(pOutput); if (pTemp) FreeSplMem(pTemp); return hr; } HRESULT PublishDsData( IADs *pADs, PWSTR pValue, DWORD dwType, PBYTE pData ) { HRESULT hr; BOOL bCreated = FALSE; switch (dwType) { case REG_SZ: hr = put_BSTR_Property(pADs, pValue, (LPWSTR) pData); break; case REG_MULTI_SZ: hr = put_MULTISZ_Property(pADs, pValue, (LPWSTR) pData); break; case REG_DWORD: hr = put_DWORD_Property(pADs, pValue, (DWORD *) pData); break; case REG_BINARY: hr = put_BOOL_Property(pADs, pValue, (BOOL *) pData); break; default: hr = dw2hr(ERROR_INVALID_PARAMETER); } BAIL_ON_FAILURE(hr); hr = pADs->SetInfo(); if (FAILED(hr)) pADs->GetInfo(); error: return hr; }