| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/win_util.h" | 5 #include "base/win/windows_version.h" |
| 6 | 6 |
| 7 #include <aclapi.h> | 7 #include <windows.h> |
| 8 #include <propvarutil.h> | |
| 9 #include <sddl.h> | |
| 10 #include <shlobj.h> | |
| 11 | 8 |
| 12 #include "base/logging.h" | 9 #include "base/logging.h" |
| 13 #include "base/registry.h" | |
| 14 #include "base/scoped_handle.h" | |
| 15 #include "base/scoped_ptr.h" | |
| 16 #include "base/string_util.h" | |
| 17 #include "base/stringprintf.h" | |
| 18 | 10 |
| 19 namespace win_util { | 11 namespace base { |
| 12 namespace win { |
| 20 | 13 |
| 21 #define SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(struct_name, member) \ | 14 Version GetWinVersion() { |
| 22 offsetof(struct_name, member) + \ | |
| 23 (sizeof static_cast<struct_name*>(NULL)->member) | |
| 24 #define NONCLIENTMETRICS_SIZE_PRE_VISTA \ | |
| 25 SIZEOF_STRUCT_WITH_SPECIFIED_LAST_MEMBER(NONCLIENTMETRICS, lfMessageFont) | |
| 26 | |
| 27 const PROPERTYKEY kPKEYAppUserModelID = | |
| 28 { { 0x9F4C2855, 0x9F79, 0x4B39, | |
| 29 { 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3, } }, 5 }; | |
| 30 | |
| 31 void GetNonClientMetrics(NONCLIENTMETRICS* metrics) { | |
| 32 DCHECK(metrics); | |
| 33 | |
| 34 static const UINT SIZEOF_NONCLIENTMETRICS = | |
| 35 (GetWinVersion() >= WINVERSION_VISTA) ? | |
| 36 sizeof(NONCLIENTMETRICS) : NONCLIENTMETRICS_SIZE_PRE_VISTA; | |
| 37 metrics->cbSize = SIZEOF_NONCLIENTMETRICS; | |
| 38 const bool success = !!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, | |
| 39 SIZEOF_NONCLIENTMETRICS, metrics, | |
| 40 0); | |
| 41 DCHECK(success); | |
| 42 } | |
| 43 | |
| 44 WinVersion GetWinVersion() { | |
| 45 static bool checked_version = false; | 15 static bool checked_version = false; |
| 46 static WinVersion win_version = WINVERSION_PRE_2000; | 16 static Version win_version = VERSION_PRE_2000; |
| 47 if (!checked_version) { | 17 if (!checked_version) { |
| 48 OSVERSIONINFOEX version_info; | 18 OSVERSIONINFOEX version_info; |
| 49 version_info.dwOSVersionInfoSize = sizeof version_info; | 19 version_info.dwOSVersionInfoSize = sizeof version_info; |
| 50 GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info)); | 20 GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&version_info)); |
| 51 if (version_info.dwMajorVersion == 5) { | 21 if (version_info.dwMajorVersion == 5) { |
| 52 switch (version_info.dwMinorVersion) { | 22 switch (version_info.dwMinorVersion) { |
| 53 case 0: | 23 case 0: |
| 54 win_version = WINVERSION_2000; | 24 win_version = VERSION_2000; |
| 55 break; | 25 break; |
| 56 case 1: | 26 case 1: |
| 57 win_version = WINVERSION_XP; | 27 win_version = VERSION_XP; |
| 58 break; | 28 break; |
| 59 case 2: | 29 case 2: |
| 60 default: | 30 default: |
| 61 win_version = WINVERSION_SERVER_2003; | 31 win_version = VERSION_SERVER_2003; |
| 62 break; | 32 break; |
| 63 } | 33 } |
| 64 } else if (version_info.dwMajorVersion == 6) { | 34 } else if (version_info.dwMajorVersion == 6) { |
| 65 if (version_info.wProductType != VER_NT_WORKSTATION) { | 35 if (version_info.wProductType != VER_NT_WORKSTATION) { |
| 66 // 2008 is 6.0, and 2008 R2 is 6.1. | 36 // 2008 is 6.0, and 2008 R2 is 6.1. |
| 67 win_version = WINVERSION_2008; | 37 win_version = VERSION_2008; |
| 68 } else { | 38 } else { |
| 69 if (version_info.dwMinorVersion == 0) { | 39 if (version_info.dwMinorVersion == 0) { |
| 70 win_version = WINVERSION_VISTA; | 40 win_version = VERSION_VISTA; |
| 71 } else { | 41 } else { |
| 72 win_version = WINVERSION_WIN7; | 42 win_version = VERSION_WIN7; |
| 73 } | 43 } |
| 74 } | 44 } |
| 75 } else if (version_info.dwMajorVersion > 6) { | 45 } else if (version_info.dwMajorVersion > 6) { |
| 76 win_version = WINVERSION_WIN7; | 46 win_version = VERSION_WIN7; |
| 77 } | 47 } |
| 78 checked_version = true; | 48 checked_version = true; |
| 79 } | 49 } |
| 80 return win_version; | 50 return win_version; |
| 81 } | 51 } |
| 82 | 52 |
| 83 void GetServicePackLevel(int* major, int* minor) { | 53 void GetServicePackLevel(int* major, int* minor) { |
| 84 DCHECK(major && minor); | 54 DCHECK(major && minor); |
| 85 static bool checked_version = false; | 55 static bool checked_version = false; |
| 86 static int service_pack_major = -1; | 56 static int service_pack_major = -1; |
| 87 static int service_pack_minor = -1; | 57 static int service_pack_minor = -1; |
| 88 if (!checked_version) { | 58 if (!checked_version) { |
| 89 OSVERSIONINFOEX version_info = {0}; | 59 OSVERSIONINFOEX version_info = {0}; |
| 90 version_info.dwOSVersionInfoSize = sizeof(version_info); | 60 version_info.dwOSVersionInfoSize = sizeof(version_info); |
| 91 GetVersionEx(reinterpret_cast<OSVERSIONINFOW*>(&version_info)); | 61 GetVersionEx(reinterpret_cast<OSVERSIONINFOW*>(&version_info)); |
| 92 service_pack_major = version_info.wServicePackMajor; | 62 service_pack_major = version_info.wServicePackMajor; |
| 93 service_pack_minor = version_info.wServicePackMinor; | 63 service_pack_minor = version_info.wServicePackMinor; |
| 94 checked_version = true; | 64 checked_version = true; |
| 95 } | 65 } |
| 96 | 66 |
| 97 *major = service_pack_major; | 67 *major = service_pack_major; |
| 98 *minor = service_pack_minor; | 68 *minor = service_pack_minor; |
| 99 } | 69 } |
| 100 | 70 |
| 101 bool AddAccessToKernelObject(HANDLE handle, WELL_KNOWN_SID_TYPE known_sid, | 71 } // namespace win |
| 102 ACCESS_MASK access) { | 72 } // namespace base |
| 103 PSECURITY_DESCRIPTOR descriptor = NULL; | |
| 104 PACL old_dacl = NULL; | |
| 105 PACL new_dacl = NULL; | |
| 106 | |
| 107 if (ERROR_SUCCESS != GetSecurityInfo(handle, SE_KERNEL_OBJECT, | |
| 108 DACL_SECURITY_INFORMATION, NULL, NULL, &old_dacl, NULL, | |
| 109 &descriptor)) | |
| 110 return false; | |
| 111 | |
| 112 BYTE sid[SECURITY_MAX_SID_SIZE] = {0}; | |
| 113 DWORD size_sid = SECURITY_MAX_SID_SIZE; | |
| 114 | |
| 115 if (known_sid == WinSelfSid) { | |
| 116 // We hijack WinSelfSid when we want to add the current user instead of | |
| 117 // a known sid. | |
| 118 HANDLE token = NULL; | |
| 119 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) { | |
| 120 LocalFree(descriptor); | |
| 121 return false; | |
| 122 } | |
| 123 | |
| 124 DWORD size = sizeof(TOKEN_USER) + size_sid; | |
| 125 scoped_array<BYTE> token_user_bytes(new BYTE[size]); | |
| 126 TOKEN_USER* token_user = | |
| 127 reinterpret_cast<TOKEN_USER*>(token_user_bytes.get()); | |
| 128 BOOL ret = GetTokenInformation(token, TokenUser, token_user, size, &size); | |
| 129 | |
| 130 CloseHandle(token); | |
| 131 | |
| 132 if (!ret) { | |
| 133 LocalFree(descriptor); | |
| 134 return false; | |
| 135 } | |
| 136 memcpy(sid, token_user->User.Sid, size_sid); | |
| 137 } else { | |
| 138 if (!CreateWellKnownSid(known_sid , NULL, sid, &size_sid)) { | |
| 139 LocalFree(descriptor); | |
| 140 return false; | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 EXPLICIT_ACCESS new_access = {0}; | |
| 145 new_access.grfAccessMode = GRANT_ACCESS; | |
| 146 new_access.grfAccessPermissions = access; | |
| 147 new_access.grfInheritance = NO_INHERITANCE; | |
| 148 | |
| 149 new_access.Trustee.pMultipleTrustee = NULL; | |
| 150 new_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; | |
| 151 new_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; | |
| 152 new_access.Trustee.ptstrName = reinterpret_cast<LPWSTR>(&sid); | |
| 153 | |
| 154 if (ERROR_SUCCESS != SetEntriesInAcl(1, &new_access, old_dacl, &new_dacl)) { | |
| 155 LocalFree(descriptor); | |
| 156 return false; | |
| 157 } | |
| 158 | |
| 159 DWORD result = SetSecurityInfo(handle, SE_KERNEL_OBJECT, | |
| 160 DACL_SECURITY_INFORMATION, NULL, NULL, | |
| 161 new_dacl, NULL); | |
| 162 | |
| 163 LocalFree(new_dacl); | |
| 164 LocalFree(descriptor); | |
| 165 | |
| 166 if (ERROR_SUCCESS != result) | |
| 167 return false; | |
| 168 | |
| 169 return true; | |
| 170 } | |
| 171 | |
| 172 bool GetUserSidString(std::wstring* user_sid) { | |
| 173 // Get the current token. | |
| 174 HANDLE token = NULL; | |
| 175 if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) | |
| 176 return false; | |
| 177 ScopedHandle token_scoped(token); | |
| 178 | |
| 179 DWORD size = sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE; | |
| 180 scoped_array<BYTE> user_bytes(new BYTE[size]); | |
| 181 TOKEN_USER* user = reinterpret_cast<TOKEN_USER*>(user_bytes.get()); | |
| 182 | |
| 183 if (!::GetTokenInformation(token, TokenUser, user, size, &size)) | |
| 184 return false; | |
| 185 | |
| 186 if (!user->User.Sid) | |
| 187 return false; | |
| 188 | |
| 189 // Convert the data to a string. | |
| 190 wchar_t* sid_string; | |
| 191 if (!::ConvertSidToStringSid(user->User.Sid, &sid_string)) | |
| 192 return false; | |
| 193 | |
| 194 *user_sid = sid_string; | |
| 195 | |
| 196 ::LocalFree(sid_string); | |
| 197 | |
| 198 return true; | |
| 199 } | |
| 200 | |
| 201 bool GetLogonSessionOnlyDACL(SECURITY_DESCRIPTOR** security_descriptor) { | |
| 202 // Get the current token. | |
| 203 HANDLE token = NULL; | |
| 204 if (!OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) | |
| 205 return false; | |
| 206 ScopedHandle token_scoped(token); | |
| 207 | |
| 208 // Get the size of the TokenGroups structure. | |
| 209 DWORD size = 0; | |
| 210 BOOL result = GetTokenInformation(token, TokenGroups, NULL, 0, &size); | |
| 211 if (result != FALSE && GetLastError() != ERROR_INSUFFICIENT_BUFFER) | |
| 212 return false; | |
| 213 | |
| 214 // Get the data. | |
| 215 scoped_array<char> token_groups_chars(new char[size]); | |
| 216 TOKEN_GROUPS* token_groups = | |
| 217 reinterpret_cast<TOKEN_GROUPS*>(token_groups_chars.get()); | |
| 218 | |
| 219 if (!GetTokenInformation(token, TokenGroups, token_groups, size, &size)) | |
| 220 return false; | |
| 221 | |
| 222 // Look for the logon sid. | |
| 223 SID* logon_sid = NULL; | |
| 224 for (unsigned int i = 0; i < token_groups->GroupCount ; ++i) { | |
| 225 if ((token_groups->Groups[i].Attributes & SE_GROUP_LOGON_ID) != 0) { | |
| 226 logon_sid = static_cast<SID*>(token_groups->Groups[i].Sid); | |
| 227 break; | |
| 228 } | |
| 229 } | |
| 230 | |
| 231 if (!logon_sid) | |
| 232 return false; | |
| 233 | |
| 234 // Convert the data to a string. | |
| 235 wchar_t* sid_string; | |
| 236 if (!ConvertSidToStringSid(logon_sid, &sid_string)) | |
| 237 return false; | |
| 238 | |
| 239 static const wchar_t dacl_format[] = L"D:(A;OICI;GA;;;%ls)"; | |
| 240 wchar_t dacl[SECURITY_MAX_SID_SIZE + arraysize(dacl_format) + 1] = {0}; | |
| 241 wsprintf(dacl, dacl_format, sid_string); | |
| 242 | |
| 243 LocalFree(sid_string); | |
| 244 | |
| 245 // Convert the string to a security descriptor | |
| 246 if (!ConvertStringSecurityDescriptorToSecurityDescriptor( | |
| 247 dacl, | |
| 248 SDDL_REVISION_1, | |
| 249 reinterpret_cast<PSECURITY_DESCRIPTOR*>(security_descriptor), | |
| 250 NULL)) { | |
| 251 return false; | |
| 252 } | |
| 253 | |
| 254 return true; | |
| 255 } | |
| 256 | |
| 257 #pragma warning(push) | |
| 258 #pragma warning(disable:4312 4244) | |
| 259 WNDPROC SetWindowProc(HWND hwnd, WNDPROC proc) { | |
| 260 // The reason we don't return the SetwindowLongPtr() value is that it returns | |
| 261 // the orignal window procedure and not the current one. I don't know if it is | |
| 262 // a bug or an intended feature. | |
| 263 WNDPROC oldwindow_proc = | |
| 264 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hwnd, GWLP_WNDPROC)); | |
| 265 SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(proc)); | |
| 266 return oldwindow_proc; | |
| 267 } | |
| 268 | |
| 269 void* SetWindowUserData(HWND hwnd, void* user_data) { | |
| 270 return | |
| 271 reinterpret_cast<void*>(SetWindowLongPtr(hwnd, GWLP_USERDATA, | |
| 272 reinterpret_cast<LONG_PTR>(user_data))); | |
| 273 } | |
| 274 | |
| 275 void* GetWindowUserData(HWND hwnd) { | |
| 276 return reinterpret_cast<void*>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); | |
| 277 } | |
| 278 | |
| 279 // Maps to the WNDPROC for a window that was active before the subclass was | |
| 280 // installed. | |
| 281 static const wchar_t* const kHandlerKey = L"__ORIGINAL_MESSAGE_HANDLER__"; | |
| 282 | |
| 283 bool IsSubclassed(HWND window, WNDPROC subclass_proc) { | |
| 284 WNDPROC original_handler = | |
| 285 reinterpret_cast<WNDPROC>(GetWindowLongPtr(window, GWLP_WNDPROC)); | |
| 286 return original_handler == subclass_proc; | |
| 287 } | |
| 288 | |
| 289 bool Subclass(HWND window, WNDPROC subclass_proc) { | |
| 290 WNDPROC original_handler = | |
| 291 reinterpret_cast<WNDPROC>(GetWindowLongPtr(window, GWLP_WNDPROC)); | |
| 292 if (original_handler != subclass_proc) { | |
| 293 win_util::SetWindowProc(window, subclass_proc); | |
| 294 SetProp(window, kHandlerKey, original_handler); | |
| 295 return true; | |
| 296 } | |
| 297 return false; | |
| 298 } | |
| 299 | |
| 300 bool Unsubclass(HWND window, WNDPROC subclass_proc) { | |
| 301 WNDPROC current_handler = | |
| 302 reinterpret_cast<WNDPROC>(GetWindowLongPtr(window, GWLP_WNDPROC)); | |
| 303 if (current_handler == subclass_proc) { | |
| 304 HANDLE original_handler = GetProp(window, kHandlerKey); | |
| 305 if (original_handler) { | |
| 306 RemoveProp(window, kHandlerKey); | |
| 307 win_util::SetWindowProc(window, | |
| 308 reinterpret_cast<WNDPROC>(original_handler)); | |
| 309 return true; | |
| 310 } | |
| 311 } | |
| 312 return false; | |
| 313 } | |
| 314 | |
| 315 WNDPROC GetSuperclassWNDPROC(HWND window) { | |
| 316 return reinterpret_cast<WNDPROC>(GetProp(window, kHandlerKey)); | |
| 317 } | |
| 318 | |
| 319 #pragma warning(pop) | |
| 320 | |
| 321 bool IsShiftPressed() { | |
| 322 return (::GetKeyState(VK_SHIFT) & 0x8000) == 0x8000; | |
| 323 } | |
| 324 | |
| 325 bool IsCtrlPressed() { | |
| 326 return (::GetKeyState(VK_CONTROL) & 0x8000) == 0x8000; | |
| 327 } | |
| 328 | |
| 329 bool IsAltPressed() { | |
| 330 return (::GetKeyState(VK_MENU) & 0x8000) == 0x8000; | |
| 331 } | |
| 332 | |
| 333 std::wstring GetClassName(HWND window) { | |
| 334 // GetClassNameW will return a truncated result (properly null terminated) if | |
| 335 // the given buffer is not large enough. So, it is not possible to determine | |
| 336 // that we got the entire class name if the result is exactly equal to the | |
| 337 // size of the buffer minus one. | |
| 338 DWORD buffer_size = MAX_PATH; | |
| 339 while (true) { | |
| 340 std::wstring output; | |
| 341 DWORD size_ret = | |
| 342 GetClassNameW(window, WriteInto(&output, buffer_size), buffer_size); | |
| 343 if (size_ret == 0) | |
| 344 break; | |
| 345 if (size_ret < (buffer_size - 1)) { | |
| 346 output.resize(size_ret); | |
| 347 return output; | |
| 348 } | |
| 349 buffer_size *= 2; | |
| 350 } | |
| 351 return std::wstring(); // error | |
| 352 } | |
| 353 | |
| 354 bool UserAccountControlIsEnabled() { | |
| 355 RegKey key(HKEY_LOCAL_MACHINE, | |
| 356 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", | |
| 357 KEY_READ); | |
| 358 DWORD uac_enabled; | |
| 359 if (!key.ReadValueDW(L"EnableLUA", &uac_enabled)) | |
| 360 return true; | |
| 361 // Users can set the EnableLUA value to something arbitrary, like 2, which | |
| 362 // Vista will treat as UAC enabled, so we make sure it is not set to 0. | |
| 363 return (uac_enabled != 0); | |
| 364 } | |
| 365 | |
| 366 std::wstring FormatMessage(unsigned messageid) { | |
| 367 wchar_t* string_buffer = NULL; | |
| 368 unsigned string_length = ::FormatMessage( | |
| 369 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | | |
| 370 FORMAT_MESSAGE_IGNORE_INSERTS, NULL, messageid, 0, | |
| 371 reinterpret_cast<wchar_t *>(&string_buffer), 0, NULL); | |
| 372 | |
| 373 std::wstring formatted_string; | |
| 374 if (string_buffer) { | |
| 375 formatted_string = string_buffer; | |
| 376 LocalFree(reinterpret_cast<HLOCAL>(string_buffer)); | |
| 377 } else { | |
| 378 // The formating failed. simply convert the message value into a string. | |
| 379 base::SStringPrintf(&formatted_string, L"message number %d", messageid); | |
| 380 } | |
| 381 return formatted_string; | |
| 382 } | |
| 383 | |
| 384 std::wstring FormatLastWin32Error() { | |
| 385 return FormatMessage(GetLastError()); | |
| 386 } | |
| 387 | |
| 388 bool SetAppIdForPropertyStore(IPropertyStore* property_store, | |
| 389 const wchar_t* app_id) { | |
| 390 DCHECK(property_store); | |
| 391 | |
| 392 // App id should be less than 128 chars and contain no space. And recommended | |
| 393 // format is CompanyName.ProductName[.SubProduct.ProductNumber]. | |
| 394 // See http://msdn.microsoft.com/en-us/library/dd378459%28VS.85%29.aspx | |
| 395 DCHECK(lstrlen(app_id) < 128 && wcschr(app_id, L' ') == NULL); | |
| 396 | |
| 397 PROPVARIANT property_value; | |
| 398 if (FAILED(InitPropVariantFromString(app_id, &property_value))) | |
| 399 return false; | |
| 400 | |
| 401 HRESULT result = property_store->SetValue(kPKEYAppUserModelID, | |
| 402 property_value); | |
| 403 if (S_OK == result) | |
| 404 result = property_store->Commit(); | |
| 405 | |
| 406 PropVariantClear(&property_value); | |
| 407 return SUCCEEDED(result); | |
| 408 } | |
| 409 | |
| 410 static const char16 kAutoRunKeyPath[] = | |
| 411 L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"; | |
| 412 | |
| 413 bool AddCommandToAutoRun(HKEY root_key, const string16& name, | |
| 414 const string16& command) { | |
| 415 RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); | |
| 416 return autorun_key.WriteValue(name.c_str(), command.c_str()); | |
| 417 } | |
| 418 | |
| 419 bool RemoveCommandFromAutoRun(HKEY root_key, const string16& name) { | |
| 420 RegKey autorun_key(root_key, kAutoRunKeyPath, KEY_SET_VALUE); | |
| 421 return autorun_key.DeleteValue(name.c_str()); | |
| 422 } | |
| 423 | |
| 424 } // namespace win_util | |
| 425 | |
| 426 #ifdef _MSC_VER | |
| 427 // | |
| 428 // If the ASSERT below fails, please install Visual Studio 2005 Service Pack 1. | |
| 429 // | |
| 430 extern char VisualStudio2005ServicePack1Detection[10]; | |
| 431 COMPILE_ASSERT(sizeof(&VisualStudio2005ServicePack1Detection) == sizeof(void*), | |
| 432 VS2005SP1Detect); | |
| 433 // | |
| 434 // Chrome requires at least Service Pack 1 for Visual Studio 2005. | |
| 435 // | |
| 436 #endif // _MSC_VER | |
| 437 | |
| 438 #ifndef COPY_FILE_COPY_SYMLINK | |
| 439 #error You must install the Windows 2008 or Vista Software Development Kit and \ | |
| 440 set it as your default include path to build this library. You can grab it by \ | |
| 441 searching for "download windows sdk 2008" in your favorite web search engine. \ | |
| 442 Also make sure you register the SDK with Visual Studio, by selecting \ | |
| 443 "Integrate Windows SDK with Visual Studio 2005" from the Windows SDK \ | |
| 444 menu (see Start - All Programs - Microsoft Windows SDK - \ | |
| 445 Visual Studio Registration). | |
| 446 #endif | |
| OLD | NEW |