Saturday, 3 September 2011

Optimizando consultas WMI

Hola a todos,

como he estado de vacaciones la semana pasada no posteé nada, intentaré compensar con dos entradas dedicadas a WMI. Cada vez que cuento las excelencias de WMI a otros desarrolladores, los dos principales inconvenientes que me comentan que tienen al intentar utilizarlo son su lentitud y lo fácil que parece algunas veces que deje de funcionar. Estoy casi completamente de acuerdo con ellos pero no del todo :) En este post me centraré en la primera parte, la lentitud. Os daré una explicación a "grosso modo" de por qué puede llegar a ser extremadamente lento e intentaré ofreceros algunos consejos para mejorar el rendimiento. En el siguiente hablaremos de como enfrentarnos a algunos errores.

 

Infraestructura

Para entender por qué las consultas pueden llegar a ser tan lentas es necesario conocer un poco la infraestructura. La arquitectura de WMI básicamente se compone del servicio WMI y el repositorio WMI. Una aplicación o script pide información al servicio WMI a través de las API en COM, el servicio se encarga de recuperarla o bien del repositorio o bien de pedir la información a los proveedores WMI también a través de COM (un proveedor no es más que un objeto COM y un archivo MOF). Por lo tanto la velocidad de WMI dependerá directamente del proveedor WMI que estemos consultando. Más aún, como solo la información estática de los objetos se almacena en el repositorio, el resto de información debe recuperarse dinámicamente del proveedor al momento de pedirla el cliente, lo que ocasiona un mayor retardo. Así que ya sabemos que aunque utilicemos WQL, un lenguaje parecido a SQL para recuperar información, no estamos leyendo de una base de datos si no que alguna de la información se puede estar generando al vuelo.

 

Cachear datos

Algunas de las optimizaciones que podemos aplicar son las mismas que aplicaríamos si tuviéramos que acceder a cualquier otro recurso lento. Por ejemplo cachear los resultados de la consulta si sabemos que se tendrá que repetir y no nos importa que no se reflejen las últimas actualizaciones. Esta técnica es especialmente útil si lo que queremos hacer son consultas y no actualizaciones o ejecución de métodos. Además, ganaremos espacio en memoria si extraemos la información del objeto ManagementObject y la almacenamos en las propiedades de un objeto POCO que sea el que finalmente guardaremos en la cache.

 

Consultas sobre la red

Uno de los usos más populares de WMI es acceder a la información de ordenadores remotos así que sí ya sabemos que las consultas WQL son generalmente lentas (ahora sabemos que porque normalmente deben crearse los datos al vuelo) este rendimiento puede empeorar todavía más si tenemos que añadir la latencia de la red. En el caso de recuperar datos de ordenadores remotos o referentes a la red es muy difícil optimizar las consultas. En estos caso hemos de intentar aglutinar todos los datos a recuperar de un ordenador remoto en una única consulta, de manera que se minimicen los viajes a través de la red. Por otra parte, también es aconsejable si queremos trabajar con un ordenador remoto intentar hacer ping primero y analizar el tiempo de respuesta de manera que sepamos cual es la causa de la lentitud o podamos aplazar el proceso para cuando la red esté menos congestionada. En cualquier caso, siempre es muy importante reducir al mínimo indispensable los datos a recuperar.

 

Campos indexados y enumeraciones

Ahora vamos a entrar más en detalle en como implementar una consulta en .Net para intentar optimizarla lo máximo posible. Para ello vamos a tener en cuenta lo que hemos comentado previamente de que los datos pueden estar siendo calculados en el momento de realizar la consulta, con lo que tendremos que evitar sentencias del tipo "SELECT *" y minimizar los datos recuperados, puesto que cada campo que añadamos puede estar generándose en ese instante. Una consulta tipo "SELECT *" es especialmente peligrosa en WMI ya que las clases suelen tener una gran cantidad de campos con lo que por ejemplo una consulta a Win32_Process con un "SELECT *" nos puede retornar una gran cantidad de información del procesador cuando solo queríamos , por ejemplo, la arquitectura.

 

Por otra parte, también quería mencionar que hay algunos proveedores que proporcionan campos optimizados para poder filtrar por ellos, mejorando el rendimiento, con lo cual, dentro de lo posible, debemos siempre consultar la documentación del proveedor para filtrar por ellos en la clausula WHERE. En cualquier caso, un buen filtro siempre mejorará el rendimiento de nuestra consulta pero en el caso de los campos no optimizados es el motor WMI quien se encarga de realizar un filtrado "a posteriori" una vez calculados los datos por el proveedor. Un ejemplo de propiedades optimizadas son Drive y Path de la clase CIM_DataFile.

 

Otra forma de mejorar el rendimiento es reutilizar las conexiones WMI que establezcamos, sobre todo si es contra ordenadores remotos. No obstante, es muy importante no olvidarse de hacer un Dispose de los objetos innecesarios para ahorrar memoria.

 

Por último quería hacer hincapié en que se configuren las opciones de enumeración de una consulta, ya que se pueden conseguir grandes mejoras de rendimiento. Para configurar estas opciones se debe utilizar la clase EnumerationOptions que se asigna a la propiedad Options de la clase ManagementObjectSearcher o bien se le pasa en el constructor. Por defecto, la consulta permite navegar hacia delante y atrás en los datos recuperados pero es muy importante que si lo que deseamos es recuperar datos lo más rápidamente posible, se utilice un modo de enumeración solo hacia delante, solo lectura y semi-síncrona. Para ello estableceremos las propiedades ReturnImmediately a true y Rewindable a false. No obstante, estas configuraciones no suelen reflejarse en una mejora importante a menos que se usen para enumerar una clase con un número de entradas considerable. Además es importante recordar que es incompatible utilizar Rewindable a false y utilizar la propiedad Count. Con el modo semi-síncrono lo que logramos es que la llamada retorne inmediatamente y que los objetos sean recuperados en segundo plano y retornados bajo demanda, una vez se hayan creado.

 

Y ahora por fin algo de código:

  1. EnumerationOptions optionsQuery = new EnumerationOptions();
  2. optionsQuery.DirectRead = true;
  3. optionsQuery.EnumerateDeep = false;
  4. optionsQuery.ReturnImmediately = true;
  5. optionsQuery.Rewindable = false;            
  6. StringBuilder units = new StringBuilder(200); // DriveType 4 is a network drive
  7. WqlObjectQuery query = new WqlObjectQuery("SELECT Name, ProviderName FROM Win32_LogicalDisk WHERE DriveType=4");
  8. using (ManagementObjectSearcher diskSearch = new ManagementObjectSearcher(null, query, optionsQuery))
  9. {
  10.     foreach (ManagementObject disk in diskSearch.Get())
  11.     {
  12.         units.Append(disk["Name"].ToString());
  13.         units.Append("\t");
  14.         units.Append(disk["ProviderName"].ToString());
  15.         units.Append("\n");
  16.     }
  17. }

Cambio de enfoque

A veces el error está en el enfoque, ¿quizás es mejor utilizar eventos que consultas? WMI también nos permite instalar “eventwatchers”. Por ejemplo, el siguiente fragmento informa de desconexiones de la red.

 

  1. static void Test()
  2. {
  3.     ManagementEventWatcher w = null;
  4.     ManagementOperationObserver observer = new ManagementOperationObserver();
  5.     // Bind to local machine
  6.     ManagementScope scope = new ManagementScope("root\\wmi");
  7.     scope.Options.EnablePrivileges = true; //sets required privilege
  8.     try
  9.     {
  10.         WqlEventQuery q = new WqlEventQuery();
  11.         q.EventClassName = "MSNdis_StatusMediaDisConnect";
  12.         w = new ManagementEventWatcher(scope, q);
  13.         w.EventArrived += new EventArrivedEventHandler(MediaEventArrived);
  14.         w.Start();
  15.         Console.ReadLine(); // block main thread for test purposes
  16.     }
  17.     catch (Exception e)
  18.     {
  19.         Console.WriteLine(e.Message);
  20.     }
  21.  
  22.     finally
  23.     {
  24.         w.Stop();
  25.     }
  26. }
  27.  
  28. static void MediaEventArrived(object sender, EventArrivedEventArgs e)
  29. {
  30.     Console.WriteLine("Event arrived");
  31.     Console.WriteLine(e.NewEvent.Properties["InstanceName"].Value);
  32.     //Get the Event object and display it
  33.     Console.WriteLine(Convert.ToBoolean(e.NewEvent.Properties["Active"].Value) ? "Active" : "Inactive");
  34. }

 

Conclusión

Así pues en conclusión los puntos que tenemos que revisar principalmente son:

  • Evitar "SELECT *"

  • Usar campos indexados en la WHERE

  • Configurar EnumerationSettings

  • Tener especial cuidado con las consultas sobre la red

  • Cachear los resultados

  • Analizar si el enfoque utilizado para recuperar información es el correcto

Más info en:

Secrets of Windows Management Instrumentation - Troubleshooting and Tips

Optimizing WMI query performances - avoid the nasty ‘select *’

How to make forward-only, read-only WMI queries in C#?

Calling a Method - Semisynchronous

Saturday, 13 August 2011

Error 0x80072015 borrando objetos del directorio activo

Hola a todos,

Esta semana no he tenido mucho tiempo así que el post será pequeñito. Hace poco me encontré con un error un tanto extraño al borrar cuentas de máquina del directorio activo y quería hablar un poco sobre esto.

System.DirectoryServices

Para borrar elementos del directorio activo existen muchas maneras pero quizás la más sencilla desde .Net es utilizar las clases del espacio de nombres System.DirectoryServices. Este espacio de nombres está definido en la DLL homónima así que no hay que olvidar añadir la referencia correspondiente.

Bajo System.DirectoryServices nos encontramos una serie de clases que nos permiten realizar todo tipo de operaciones contra el directorio activo encapsulando el acceso a las interfaces ADSI que Windows expone mediante COM. La clase principal a utilizar es DirectoryEntry que representa un objeto del directorio activo ya sea un usuario, un ordenador o cualquier otro tipo de entrada. Si bien esta clase nos permite acceder a todas las propiedades y métodos, es muy genérica y no nos los proporciona de forma fuertemente tipada.

ActiveDS

Si deseáramos esto último podríamos conseguirlo añadiendo una referencia a la DLL ActiveDS que nos proporciona el acceso directo a las interfaces COM de cada tipo de objeto.

En concreto podemos encontrar las siguientes interfaces principales en este ensamblado que se enlazan directamente con objetos del directorio activo:

  • IADsComputer
  • IADsDomain
  • IADsGroup
  • IADsOU
  • IADsService
  • IADsUser

    A continuación muestro como enlazar un objeto DirectoryEntry con una interfaz IADSUser.

    ActiveDS.IADsUser user = de.NativeObject as ActiveDS.IADsUser; 



    En cualquier caso, es recomendable utilizar DirectoryEntry en lugar de acceder desde ActiveDS ya que nos proporciona un nivel de abstracción más sobre la plataforma. Pero nos estamos desviando del objetivo del post o "dispersando como mantequilla untada sobre demasiado pan" como diría Bilbo Bolson, así que vamos a centrarnos.

    Error 0x80072015


    Para eliminar un objeto primero debemos encontrarlo y para eso podemos utilizar DirectorySearcher, la otra clase más importante dentro de System.DirectoryServices. A continuación podéis ver un fragmento de código muy sencillo de como podríamos realizar la búsqueda y la eliminación.

       1:  DirectorySearcher adSearcher = new DirectorySearcher(); 
       2:  adSearcher.Filter = "(&(objectCategory=computer)(name=MyTestComputer*))"; 
       3:  SearchResult adResult = adSearcher.FindOne(); 
       4:  if (adResult != null) 
       5:  { 
       6:      DirectoryEntry entry = adResult.GetDirectoryEntry(); 
       7:      entry.Parent.Children.Remove(entry); 
       8:  }

    Pues bien, en este código hemos incurrido ya en el error, puesto que el método Remove únicamente funciona si el objeto que estamos eliminando no tiene ningún objeto hijo. En el caso de que sí que los tenga se lanzará una COMException con el mensaje "The directory service can perform the requested operation only on a leaf object. (Exception from HRESULT: 0x80072015)" .

    Este fue el error que cometí yo pensando que un objeto "computer" no sería contenedor y por lo tanto siempre sería un "leaf object". Sin embargo, a pesar de que no se puedan ver mediante la herramienta "Active Directory Users and Computers" (dsa.msc) o incluso desde la herramienta ADSI Edit (adsiedit.msc) salvo que realices una búsqueda específica, los objetos computer sí que pueden tener nodos hijos y de hecho, es bastante habitual. En mi caso concreto los elementos que encontré bajo un ordenador fueron del tipo Configuración de MSMQ (MSMQ-Configuration) y es que cuando instalamos el componente de servidor de MSMQ en un ordenador unido al dominio, el sistema operativo publica la información de las colas en la cuenta de la máquina en el directorio activo automáticamente.

    La solución es bastante sencilla, simplemente hemos de sustituir la línea:

       7:      entry.Parent.Children.Remove(entry); 



    por

       7:      entry.DeleteTree() 



    que automáticamente elimina todo el árbol, incluida la entrada desde la que se invoca.

    Claro que hemos de tener cuidado utilizando este peligroso método ya que si lo invocamos sobre el nodo incorrecto podemos causar verdaderos desastres. Por otra parte, existe un inconveniente adicional, el método DeleteTree solo funciona si utilizamos el proveedor LDAP, con lo que si utilizamos otro proveedor tendríamos que implementarnos nosotros mismos un método recursivo para eliminar todos los objetos bajo la entrada que queremos eliminar.






    1. public void DeleteChildren(DirectoryEntry entry)

    2. {

    3.     foreach (DirectoryEntry child in entry.Children)

    4.     {

    5.         DeleteChildren(child);

    6.     }

    7.     entry.CommitChanges();

    8.     entry.Parent.Children.Remove(entry);

    9. }





    Más info en:
    Message Queuing and Active Directory
    MSMQ > LDAP Distinguished Names of Directory Objects 
    ADSI Edit (adsiedit.msc)
    Active Directory Users and Computers
    IADS Reference
    Quitar nodos de Active Directory
    DirectoryEntries.Remove
    DirectoryEntry.DeleteTree

  • Sunday, 24 July 2011

    Utilidades WMI

    Hola a todos,
    Siguiendo la temática del post anterior, me gustaría profundizar un poco más en como podemos explotar WMI desde nuestras aplicaciones. Desde el área de sistemas siempre ha sido más habitual utilizar WMI que no desde el área de desarrollo, por ello podemos encontrar más herramientas enfocadas a sistemas para explotar sus posibilidades. Sin embargo, estas herramientas también pueden ser muy útiles para el programador, entre ellas me gustaría destacar WMI Code Creator, Script-o-matic, wmic y PowerShell.
     

    WMI Code Creator

    Como su propio nombre indica, esta herramienta permite generar código para acceder a WMI. Genera código tanto en VB.Net y C# como en VBScript. Su objetivo principal era ayudar a el personal de IT y desarrolladores a generar scripts para consultar WMI pero, además de esto, también nos permite ejecutar métodos de clases WMI, recibir eventos y navegar por todas las clases WMI que tengamos instaladas en nuestro ordenador. Aunque el código generado no sea muy complejo ni nos permita acceder a la información mediante propiedades fuertemente tipadas, como hace la utilidad mgmtclassgen que ya comenté en mi post anterior, es una utilidad muy práctica para explorar la información que podemos extraer de WMI y para generar código de ejemplo para realizar tareas básicas.
    WMICodeCreator
    Sin embargo, uno de los aspectos que más me atrae de esta utilidad es que está escrita en C#. No es una aplicación que tengamos que tomar como ejemplo a la hora de nuestros desarrollos, incluso diría que se podrían haber esforzado un poco en separar el acceso a la información de la interfaz gráfica. No obstante, leer el código ajeno es una de las mejores maneras de aprender, y, en un solo archivo cs descargable, podemos aprender un montón acerca de como está estructurado WMI y de como podemos acceder tanto a su esquema como a sus valores. Además, si utilizamos a menudo WMI o tenemos que generar scripts con asiduidad, siempre podemos modificar el código para que se adapte a nuestras necesidades.
    WMI Code Creator puede descargarse del Microsoft Download Center aquí.
     

    Scriptomatic 2.0

    Esta utilidad es ampliamente conocida en el mundo del scripting. Es un archivo HTA (es decir HTML y VB Script), no genera código .Net sino VBScript, JScript, Perl y Python. Es bastante más limitada que WMI Code Creator desde el punto de vista del desarrollador pero aún así útil para enseñarnos los entresijos de escribir scripts para WMI. También está disponible en el Microsoft Download Center aquí.
     

    WMIC

    WMIC es sencillamente una utilidad de línea de comandos que nos permite acceder a WMI, es la utilidad más básica de las que he comentado pero es imprescindible conocerla si vas a trabajar extrayendo información del sistema por una u otra razón, ya que es la manera más sencilla de hacerlo y en algún momento tendrás la necesidad de corroborar de manera rápida los valores que retorna WMI. La desventaja es que hay que aprender a utilizarlo ya que, por una razón que escapa a mi conocimiento, utiliza alias para las clases y verbos para las acciones que son ligeramente distintos a los nombres de las clases WMI. Esta utilidad viene con el sistema operativo desde Windows XP, aquí os dejo el enlace que explica su sintaxis.
     

    PowerShell Get-WmiObject

    Para los que tengan la suerte de tener disponible PowerShell, existe el comando Get-WmiObject que nos da acceso a las clases de WMI desde la línea de comandos. Está claro que PowerShell es un gran avance para los administradores de sistemas pero como está basado en .Net también tiene una gran utilidad para los desarrolladores que sin apenas curva de aprendizaje podemos realizar scripts realmente complejos. Por ejemplo, con esta línea podemos recuperar todos los adaptadores de red
       1:  $NetworkAdapters = Get-WmiObject -Class Win32_NetworkAdapter 

    y en la linea siguiente deshabilitar uno de ellos:

       2:  $NetworkAdapters[0].Disable() 

    Aquí tenéis el enlace con la documentación de este método.

    Sunday, 17 July 2011

    WMI fuertemente tipado


    Hola a todos,


    hacía mucho tiempo que no tenía la ocasión de trabajar con WMI (Windows Management Instrumentation) y como siempre me ha parecido un recurso excepcional, me he decidido a dedicarle un post.


    WMI nos proporciona una manera unificada de acceder a la información del sistema, es extensible por nuestras aplicaciones y fácilmente consultable mediante WQL, evitando de esta forma las consultas engorrosas a las APIs de Windows o al registro. WMI es accesible desde .Net a través del espacio de nombres System.Management y desde script, ya que está expuesto por COM.


    WMI es muy potente porque, además de consultar información del sistema, también podemos modificarla e incluso realizar un amplio abanico de operaciones que van desde iniciar procesos a habilitar adaptadores de red, pasando por formatear particiones. Pero quizás, lo que le otorga mayor versatilidad, es poder realizar estas operaciones en ordenadores remotos, siempre que tengamos suficientes privilegios, claro está.


    También tiene inconvenientes, como todo en esta vida. Algunos de ellos son, por ejemplo, que no nos libramos de incompatibilidades entre las diferentes versiones del sistema operativo, el servicio Winmgmt debe estar ejecutándose en la máquina, las consultas pueden tener un rendimiento muy pobre, a la hora de cruzar información entre tablas el WQL es poco intuitivo y bastante complicado y seguro que muchas otras más que ahora mismo no recuerdo.


    Mgmtclassgen


    Os recomiendo esta utilidad que nos permite generar clases .Net a partir de clases WMI, de tal manera que podremos acceder a los atributos y operaciones de estos objetos mediante métodos y propiedades fuertemente tipados. Las clases generadas son bastante básicas y será trabajo nuestro modificarlas en el caso de que queramos realizar consultas complejas o bien optimizar el acceso a los datos, pero nos ahorra una gran cantidad de código que nos permite tener una aplicación mucho más legible.


    Aquí os dejo una enlace que explica el uso de esta utilidad: http://msdn.microsoft.com/en-us/library/2wkebaxa(v=VS.100).aspx


    Por ejemplo si ejecutamos el comando mgmtclassgen win32_process, se nos generará el archivo Process.cs que incluirá la clase Process,


    De manera que pasaremos a acceder a la información de la siguiente manera:


       1:  foreach(Process ps in Process.GetInstances())
       2:  {
       3:      Console.WriteLine(ps.Name);
       4:  }



    En lugar de lo que teníamos que hacer antes:


       1:  System.Management.ManagementScope mgmtScope = new System.Management.ManagementScope();
       2:  mgmtScope.Path.NamespacePath = "root\\CimV2";
       3:  System.Management.ManagementPath pathObj = new System.Management.ManagementPath();
       4:  pathObj.ClassName = "win32_process";
       5:  pathObj.NamespacePath = "root\\CimV2";
       6:  System.Management.ManagementClass clsObject = new System.Management.ManagementClass(mgmtScope, pathObj, null);
       7:  System.Management.EnumerationOptions enumOptions = new System.Management.EnumerationOptions();
       8:  enumOptions.EnsureLocatable = true;
       9:  foreach (System.Management.ManagementBaseObject obj in clsObject.GetInstances(enumOptions))
      10:  {
      11:      Console.WriteLine(obj["Name"].ToString());
      12:  }



    Ganando no solo legibilidad en nuestro código sino también comprobación de tipos en tiempo de compilación y evitamos errores en tiempo de ejecución por ejemplo por haber escrito mal el nombre de un atributo.


    Otro aspecto a tener en cuenta es que esta utilidad se basa en el esquema existente en la máquina donde se ejecuta la utilidad, con lo que las clases generadas pueden variar el comportamiento si se ejecutan en otros sistemas operativos.


    Early vs Late Bound


    Las clases generadas por esta utilidad intentan proporcionarnos un enfoque early-bound en lugar del late-bound que es el que tenemos por defecto utilizando las clases bajo System.Management. ¿Esto quiere decir que tenemos que decidir entre usar una aproximación late-bound o un acceso fuertemente tipado? Nada más lejos de la realidad, la utilísima propiedad AutoCommit que podemos establecer a false en cualquier momento nos permite trabajar con el objeto en memoria antes de enlazarlo con el objeto real. Además, también disponemos de la propiedad LateBoundObject que nos proporciona acceso al objeto WMI subyacente.


    Trabajar con early bound es la manera recomendada por Microsoft y la que deberíamos utilizar en la mayoría de las ocasiones por las ventajas que he comentado previamente. Sin embargo, hay ocasiones en las que es necesario utilizar un enfoque late-bound. Por ejemplo, si no conocemos en tiempo de diseño si un método o atributo existirá en tiempo de ejecución. También podemos encontrarnos que hay operaciones que son imposibles de realizar con una aproximación early-bound debido al diseño del componente, como por ejemplo la instalación de un nuevo driver de impresora. A continuación muestro un ejemplo de uso de la propiedad AutoCommit:


       1:  Printerdriver driver = Printerdriver.CreateInstance();
       2:  driver.AutoCommit = false;
       3:  driver.LateBoundObject["Name"] = "Epson AL-2600";
       4:  driver.SupportedPlatform = "Windows x64";
       5:  uint rc = Printerdriver.AddPrinterDriver(driver.LateBoundObject);

    Más info en:


    http://msdn.microsoft.com/en-us/library/aa394554(v=VS.85).aspx

    Friday, 1 July 2011

    Migrando aplicaciones .Net de 32 a 64 bits

    Hola a todos,
    ya sé que no es un tema novedoso pero esta semana he estado migrando unas aplicaciones a 64 bits, y esto a hecho que me encuentre con algunas cosas interesantes que me gustaría compartir.
    Lo primero que pensé cuando me asignaron la tarea fue ¿por qué tengo que revisar una aplicación .Net para que funcione en 64 bits si solamente generamos ensamblados que están en IL, no en código nativo? Bueno, en seguida me respondí a mi mismo, ya que, salvo que se haya compilado la aplicación con la opción, /clr:safe no seria realista pensar que cualquier aplicación se ejecutará de igual manera en arquitecturas de 64 o de 32 bits. Microsoft dice que se deben revisar las aplicaciones sobre todo si en nuestro código hacemos alguna de estas cosas: invocar APIs de la plataforma via p/invoke, invocar objetos COM, utilizar código no seguro, utilizar marshaling como mecanismo de intercambio de información o utilizar la serialización como una manera de persistir el estado.  Las dos primeras están a la orden del día así que será bastante difícil que no tengamos que hacer al menos una pequeña revisión en nuestro código.

    Framework64

    La principal restricción que nos encontramos cuando trabajamos con .Net Framework en un sistema operativo de 64 bits es que desde una aplicación  cargada en el entorno de 32 bits no se pueden cargar componentes de 64  bits y viceversa, si lo intentamos nos encontraremos con un  FileNotFoundException o BadImageFormatException dependiendo del tipo de  componente a cargar. Esto es así porque en los sistemas de 64 bits tenemos dos entornos de  ejecución desde la versión 2.0, el entorno que se ejecuta en 64 bits y  que se encuentra en el directorio %windir%\Microsoft.Net\Framework64 y  el de 32 bits que se ejecuta bajo el wow64 (Windows on windows) y está  en  %windir%\Microsoft.Net\Framework .

    Ldr64

    Para habilitar el entorno de 64 bits,  .Net Framework  crea la clave Enable64bits en el registro  de Windows que indica, si tiene el valor 1, que se utilizará este entorno y que, por lo tanto, los ensamblados sin marca, se intentarán cargar como  ensamblados de 64 bits. A la inversa, si lo que queremos es forzar que  los ensamblados compilados con "Any CPU" se ejecuten bajo el WoW,  podemos establecer este valor a 0. Para modificar este valor, mejor que  hacerlo a mano, podemos utilizar la utilidad ldr64.exe que se encuentra  en el directorio Framework64. Con esto conseguimos además que se aplique inmediatamente la configuración  seleccionada y que los ensamblados del .Net Framework que se utilicen sean los de la plataforma indicada.
    La utilidad ldr64 tiene una sintaxis muy sencilla, ya que  solo se le pueden indicar los parámetros query, setwow o set64, que son  autoexplicativos. No he encontrado en la Web mucha documentación de esta utilidad y no sé  de su robustez más allá de para ser usada por programadores pero podría ser interesante utilizarla en entornos de producción que dispongan de un sistema operativo de 64 bits pero con muchos ensamblados con dependencias en 32 bits, de esta manera no sería necesario recompilarlos.

    CorFlags

     Una vez instalados ambos frameworks, cuando se ejecuta una aplicación, el CLR debe decidir en
    cual de estos  dos entornos se cargará el proceso y para eso utiliza unos flags que se establecen en el PE del ejecutable (portable execution header). Para  poder ver estos flags sobre un ensamblado e incluso modificarlos sin tener que recompilar, aunque  Microsoft no lo recomienda, tenemos la utilidad corflags.exe,  disponible con el Visual Studio. Cuidado con los ensamblados firmados con un strong name, porque si cambiamos los flags con esta utilidad tendremos que volverlos a firmar.

    Hay que tener en cuenta que  cuando seleccionamos el tipo de plataforma  en Visual Studio, principalmente estamos definiendo el valor de algunos de estos flags, concretamente el flag 32BIT. Este flag funciona de una manera un tanto peculiar ya que  establecerlo indica que, en un SO de 64 bits, se ejecutará bajo el  wow64, mientras que no establecerlo indica que el CLR podrá cargarlo como ensamblado de 64 bits si así lo desea.


    Aquí muestro los flags que se establecen con cada configuración:
    Plataforma
    PE
    32BIT
    anycpu
    PE32
    0
    x86
    PE32
    1
    x64
    PE32+
    0

     Para quien no lo sepa, podemos descubrir que procesos se ejecutan en 32 bits simplemente mirando en la pestaña Procesos del administrador de tareas, los procesos que se ejecutan bajo el wow64 tienen añadido *32 al final del nombre. También podemos utilizar el método Module.GetPEKind para comprobar programáticamente la configuración de un ensamblado. Mientras que en tiempo de ejecución podemos consultar la propiedad IntPtr.Size (4 en 32 bits, 8 en 64). Precisamente, uno de los problemas habituales al migrar código a 64 bits que hace llamadas código nativo es utilizar el tipo de datos int en lugar de la clase IntPtr cuando se hace referencia a punteros, claro que si realizamos este tipo de prácticas deberíamos usar la clase SafeHandle, pero este tema se merece un Post entero así que ya lo comentaremos en otra ocasión.

    GAC_64

    Al principio del Post comenté que podríamos encontrarnos con una FileNotFoundException, esta excepción puede  lanzarse a causa del algoritmo de búsqueda del ensamblado ya que al instalar  el framework de 64 bits, igual que se crea una carpeta Framework64, también  se crea una nueva carpeta gac_64 bajo el directorio  c:\windows\assembly. Esta carpeta, que contendrá los ensamblados  específicos de 64 bits, se añade a las ya existentes: gac (para  frameworks 1.0 y 1.1), gac_32 y gac_msil (para ensamblados "platform  agnostic"). Dentro de c:\windows\assembly también observaremos que, si hemos generado imágenes nativas para nuestro ensamblados mediante la utilidad ngen, éstas estarán instaladas en carpetas del tipo NativeImages_%framework_version%_%platform%, es decir, por ejemplo, NativeImages_v2.0.50727_64.

    Así pues, cuando el CLR busque un ensamblado, primero  buscará en el directorio propio de la plataforma y luego en gac_msil.  Esto puede ocasionar que no se encuentre el ensamblado en el caso de que  la aplicación se esté ejecutando bajo el contexto de 64 bits y el  ensamblado necesario se encuentre en el directorio gac_32 o a la  inversa.  Esta casuística se puede ver muy claramente gracias a utilidades como Process Monitor o Fuslogvw, que nos muestra los archivos que nuestra aplicación ha intentado abrir sin éxito.
    Mas info en los siguientes enlaces:
    Corflags.exe
    How to switch between the 32-bit versions of ASP.NET 1.1 and the 64-bit version  of ASP.NET 2.0

    on a 64-bit version of Windows
    Migrating 32-bit Managed Code to 64-bit
    64-bit Applications
    Run 32 bit .NET applications on 64 bit machines
    Moving from 32-bit to 64-bit application development on .NET Framework
    Hablan del ldr64 pero para ponerlo en set64