The strange case of error 0x80070005 “Access Denied” using PEVerify.exe with an add-in referencing a Visual Studio assembly not in the GAC

It took me quite a lot of time to fix the problem of Error 0x80070002 “The system cannot find the file specified” using PEVerify.exe to verify add-in with referenced assemblies from Visual Studio not in the GAC. I found the problem yesterday Sunday afternoon when I was with my laptop at a Starbucks with no connection to Internet other than a very weak GPRS connection with my mobile phone, so I could only get on the web that the solution was to use the codeBase tag in a .config file. The file that I used was:

<?xml version="1.0" ?>
<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <dependentAssembly>
            <assemblyIdentity name="Microsoft.VisualStudio.Data.Interop" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
            <codeBase version="8.0.0.0" href="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE" />
         </dependentAssembly>
      </assemblyBinding>
   </runtime>
</configuration>

Using this file I got this other error:

0x80070005 “Access Denied”

rather than:

0x80070002 “The system cannot find the file specified”.

So today with more Internet connectivity I have been investigating the problem. The only reference to this problem was Debugging Assembly Loading Failures, where Suzzane Cook stated the obvious, to check file locks without shared-read access and ACLs. But this was not the problem.

The fusion log showed this:

LOG: GAC Lookup was unsuccessful.
LOG: Attempting download of new URL file:///C:/Program Files/Microsoft Visual Studio 8/Common7/IDE.
LOG: Assembly download was successful. Attempting setup of file: C:\Program Files\Microsoft Visual Studio 8\Common7\IDE
LOG: Entering run-from-source setup phase.
ERR: Error extracting manifest import from file (hr = 0x80070005).
ERR: Failed to complete setup of assembly (hr = 0x80070005). Probing terminated.

So I was clueless until I found the string “ERR: Error extracting manifest import from file” in a .rc file inside  www.koders.com, the site that has the source code of some open source portions of the .NET Framework CLR and tools. The string led me to the identifier ID_FUSLOG_MANIFEST_EXTRACT_FAILURE which in turn led me to the following code in the bindhelpers.cpp file:

HRESULT BindToSystem(IAssemblyName *pNameSystem,
   LPCWSTR pwzSystemDirectory,
   IUnknown *pUnk,
   IAssembly **ppAsmOut,
   IAssembly **ppNIAsmOut,
   IFusionBindLog **ppdbglog)
{
   HRESULT hr = S_OK;
   IAssembly *pAsm = NULL;
   CAssembly *pCAsm = NULL;
   CNativeImageAssembly *pAsmNI = NULL;
   DWORD dwSize = 0;
   IAssemblyManifestImport *pAsmImport = NULL;
   INativeImageEvaluate *pNIEva = NULL;
   WCHAR wzManifestFilePath[MAX_PATH];
   IAssemblyName *pName = NULL;
   static BOOL bCalled = FALSE;
   CDebugLog *pdbglog = NULL;
   BOOL bFoundInDevOverride = FALSE;

   MEMORY_REPORT_CONTEXT_SCOPE("FusionBindToSystem");

   if (!pNameSystem || !ppAsmOut || CAssemblyName::IsPartial(pNameSystem) || !CAssemblyName::IsStronglyNamed(pNameSystem) || !pwzSystemDirectory) {
      return E_INVALIDARG;
   }

   *ppAsmOut = NULL;

   // should only be called once.
   if (bCalled) {
      *ppAsmOut = g_pSystemAssembly;
      if (g_pSystemAssembly) {
         g_pSystemAssembly->AddRef();
         return S_OK;
      }
      else {
         return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
      }
   }

   if (IsLoggingNeeded()) {
      CDebugLog::Create(NULL, pNameSystem, NULL, &pdbglog);
   }

   wzManifestFilePath[0] = L'';

   if (pUnk) {
      hr = pUnk->QueryInterface(IID_INativeImageEvaluate, (void **) &pNIEva);
      if (FAILED(hr)) {
         goto Exit;
      }
   }

   // temporary IAssemblyName for mscorlib
   g_pSystemAssemblyName = pNameSystem;
   g_pSystemAssemblyName->AddRef();

   // we are here because we cannot find the custom assembly,
   // or we are not asked for a custom assembly.

   if (!bFoundInDevOverride) {
      hr = CAssemblyName::CloneForBind(pNameSystem, &pName);
      if (FAILED(hr)) {
         goto Exit;
      }

      hr = CreateAssemblyFromCacheLookup(NULL, pName, ppAsmOut, NULL);
      if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
         goto Exit;
      }

      if (hr == S_OK) {
         DEBUGOUT(pdbglog, 1, ID_FUSLOG_CACHE_LOOKUP_SUCCESS);
      }
      else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)){
         DEBUGOUT(pdbglog, 1, ID_FUSLOG_ASSEMBLY_LOOKUP_FAILURE);
      }

      if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
         dwSize = MAX_PATH;
         hr = StringCchPrintf(wzManifestFilePath, MAX_PATH, L"%ws%ws.dll", pwzSystemDirectory, SYSTEM_ASSEMBLY_NAME);
         if (FAILED(hr)) {
            goto Exit;
         }

         DEBUGOUT1(pdbglog, 0, ID_FUSLOG_ATTEMPT_NEW_DOWNLOAD, wzManifestFilePath);
         hr = CreateAssemblyFromManifestFile(wzManifestFilePath, NULL, NULL, ppAsmOut);
         if (FAILED(hr)) {
            DEBUGOUT1(pdbglog, 1, ID_FUSLOG_MANIFEST_EXTRACT_FAILURE, hr);
            goto Exit;
         }
      }
   }

Exit:

   if (pdbglog) {
      pdbglog->SetResultCode(FUSION_BIND_LOG_CATEGORY_DEFAULT, hr);
      DUMPDEBUGLOG(pdbglog, g_dwLogLevel);
      DUMPDEBUGLOGNGEN(pdbglog, g_dwLogLevel);
      if (ppdbglog) {
         *ppdbglog = pdbglog;
         pdbglog->AddRef();
      }
   }

   SAFERELEASE(g_pSystemAssemblyName);

   if (SUCCEEDED(hr)) {
      pCAsm = static_cast (*ppAsmOut); // dynamic_cast
      pCAsm->SetIsSystemAssembly(TRUE);

      g_pSystemAssembly = *ppAsmOut;
      g_pSystemAssembly->AddRef();
      g_pSystemAssembly->GetAssemblyNameDef(&g_pSystemAssemblyName);
   }

   if (FAILED(hr)) {
      SAFERELEASE(*ppAsmOut);
   }

   SAFERELEASE(pdbglog);
   SAFERELEASE(pAsmImport);
   SAFERELEASE(pAsm);
   SAFERELEASE(pAsmNI);
   SAFERELEASE(pNIEva);
   SAFERELEASE(pName);

   bCalled = TRUE;
   return hr;
}

I have marked in bold the relevant code. It was not until I saw the name of the function CreateAssemblyFromManifestFile when I realized that I had to specify the full file name and not the folder of the assembly in the .config file. Once modified to the following it worked without errors:

<?xml version="1.0" ?>
<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <dependentAssembly>
            <assemblyIdentity name="Microsoft.VisualStudio.Data.Interop" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
            <codeBase version="8.0.0.0" href="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\Microsoft.VisualStudio.Data.Interop.dll" />
         </dependentAssembly>
      </assemblyBinding>
   </runtime>
</configuration>

The “access denied” error was misleading since it would be more useful to keep showing the “file not found” error.

Now, imagine the time that you could save diagnosing problems in extensions for Visual Studio if you could have access to the Visual Studio source code, even without being able to debug, just debugging with the brain as I did…