Task

  • Automate the removal of a control from the IE’s Downloaded Program Files folder.

Issues

  • Controls may have more than one control associated with them
  • Name in the “Downloaded Program Files” folder seen in Explorer is different than what is actually stored.
  • Even knowing the name of the file you can’t just delete it.
    • It must be unregistered (regsvr32.exe /u {filename}
      • This won’t unregister associated controls
    • Control and .inf can then be deleted
  • Even after unregister and delete the folder viewed in Explorer will show a GUID associated with the control and mark the control as damaged

Research

  • Looking in “Downloaded Program Files” we discover each item has a .inf file associate with it.  Usually the main DLL is also there but in cases like the Shock Wave controls they are elsewhere.
  • Each of the .inf files are a bit different but in each (with one exception of swflash.inf) there is a line that equals clsid={guid} for swflash.inf the line is GUID = “{guid}”.  Further look shows that the ID in .INF files can be found by collecting the numbers between the first “{“ and “}”.
  • Looking the registry HKEY_CLASSES_ROOT\CLSID\{guid}
    • Root string (default) will contain the text shown in the “Downloaded Programs Files” shown in Explorer
    • Root of \InprocServer32\ will contain the controls path and name
  • After running regsvr32.exe /u on the DLL and then searching for the GUID there will be only one entry left at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Code Store Database\Distribution Units\{guid}
    • Deleting this key will remove the item from the Explorer file and complete the uninstall
  • I searched for the “Code Store Database\Distribution Units” path on search.msn.com and discovered the path was owned by a control named OCCACHE.DLL
    • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ActiveX Cache\ contains keys labeled 0, 1, 2, etc… with paths to the cache.  Most machines just having a 0 which points to “C:\{windows}\Downloaded Program Files”
    • I used a dependency walker again the OCCACHE.DLL and discovered it had a public function named RemoveControlByName which needs a path to the core DLL and the CLSID.  With those two things it will remove all registry entries and files pertaining to the DLL (much better than regsvr32.exe /u) and the Distribution Units entry also.

Solution

Assuming you want to remove the control by passing in its friendly name (one seen in Explorer)

  • Find path to download files via the ActiveX Cache registry setting (may be more than one path)
  • Search those directories for each .inf file
    • Get CLSID from file
    • Look in HKCR\CLSID\{guid} to see if name matches one passed in
  • Once a match is found get the InprocServer32 path and call RemoveControlByName passing that path and the CLSID
  • Delete the registry key in the  Distribution Units path

Code Pieces

  // The control has been uninstalled successfully.
  const int S_OK = 0;   

  // A minor error occurred, but not serious enough to abort the operation. The control has been uninstalled successfully.
  const int S_FALSE = -1; 
 
  // Catastrophic failure
  const int E_UNEXPECTED = -2147418113;

  /// <summary>
  /// Removes the registry entries and all of the files associated with the specified control.
  /// </summary>
  /// <param name="filePath">value that contains the full path to the main file (usually the .ocx file) of the control.</param>
  /// <param name="classID">value that contains the class identifier (CLSID) or distribution unit name associated with the control.</param>
  /// <param name="typeLibraryId">value that contains the type library CLSID of the control. This can be set to NULL.</param>
  /// <param name="forceRemove">value that specifies the method used to remove the control. If set to FALSE, the removal routine first checks to see if it is safe to remove the control. If set to TRUE, the control is removed, regardless of the removal status, unless there is a sharing violation. This value only applies to the control file itself.</param>
  /// <param name="isDistributionUnit">value that specifies if this control is part of a distribution unit. This must be set to TRUE if it is part of a distribution unit, or FALSE otherwise.</param>
  /// <returns>S_OK / S_FALSE: A minor error occurred, but not serious enough to abort the operation. The control has been uninstalled successfully.</returns>
  [DllImport("OCCACHE.DLL")] 
 
static extern int RemoveControlByName(string filePath, string classID, string typeLibraryId, bool forceRemove, System.UInt32 isDistributionUnit);

  public static bool RemoveControl(string ControlPath, string CLSID)
  {
   RemoveControlByName(ControlPath,
    CLSID,
    null,
    true,
    Convert.ToUInt32(true));

   if(File.Exists(ControlPath) == false)
   {
    return true;
   }

   return false;
  }

  public static string GetCLSID(string ControlName)
  {
   const string PATTERN_INF = "*.inf";
   const string GUID_START = "{";
   const string GUID_END = "}";
   string InfFileContent = String.Empty;
   string CLSID = null;
   string[] DownloadFolderPaths = GetFolderPaths();

   foreach(string path in DownloadFolderPaths)
   {
    foreach(string file in Directory.GetFiles(path, PATTERN_INF))
    {
     InfFileContent = File.OpenText(file).ReadToEnd();
     CLSID = GUID_START + GetTextBetween(InfFileContent,GUID_START,GUID_END) + GUID_END;
     if(GetControlName(CLSID) == ControlName)
     {
      return CLSID;
     }
    }
   }

   return null;
  }

public static string GetTextBetween(string text, string startDelimiter, string endDelimiter)
{
 
int start = text.IndexOf(startDelimiter) + 1;
 
int end = text.IndexOf(endDelimiter) - text.IndexOf(startDelimiter) - 1;
 
return text.Substring(start, end);
}

 

  public static string GetControlPath(string CLSID)
  {
   string REG_CLSID = "CLSID\\" + CLSID + "\\InprocServer32";
   RegistryKey rk = Registry.ClassesRoot;
   return (string)rk.OpenSubKey(REG_CLSID).GetValue(String.Empty);
  }
  public static string GetControlName(string CLSID)
  {
   string REG_CLSID = "CLSID\\" + CLSID;
   RegistryKey rk = Registry.ClassesRoot;
   return (string)rk.OpenSubKey(REG_CLSID).GetValue(String.Empty);
  }

  public static string[] GetFolderPaths()
  {
   const string REG_ACTIVEX_CACHE = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings\ActiveX Cache";
   RegistryKey rk = Registry.LocalMachine;
   string[] ValueNames = rk.OpenSubKey(REG_ACTIVEX_CACHE).GetValueNames();
   string[] DownloadFolderPaths = new string[ValueNames.Length];
   for(int i = 0; i < ValueNames.Length; i++)
   {
    DownloadFolderPaths[i] = (string)rk.OpenSubKey(REG_ACTIVEX_CACHE).GetValue(ValueNames[i]);
   }

   return DownloadFolderPaths;
  }

Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm