Ir al contenido


Foto

Escanear el Registrro de Windows con la API


  • Por favor identifícate para responder
8 respuestas en este tema

#1 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 30 diciembre 2013 - 04:16

El manejo del registro de Windows es siempre engorroso. Las VCL tienen herramientas para acceder al registro que hacen bien su trabajo. En algunas aplicaciones no interesa usar para conseguir ejecutables mas compactos, son los casos en los que usamos la API directamente, entonces el acceso al registro parece que se nos complica un poco.

Tras la adaptación de código que tenía escrito para este fin, os presento una función escrita sólo con la API de Windows que nos permite navegar y buscar en el registro explorando incluso las subclaves.
La función la denomino ScanRegistry y tiene los siguientes parámetros:

delphi
  1. procedure ScanRegistry(Key, Name: PCHAR; RegAction: TRegAction; ScanType: DWORD; RegProgress: TRegProgress; var Stop: boolean);

Key: Será la clave donde comenzará el escáner, listado o búsqueda. Su notación será como en un árbol de directorios, comenzando con la clave principal. Por ejemplo:
'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\run\'
Name: Será un nombre de clave o valor o cadena que deseemos buscar en el registro, si no queremos realizar una búsqueda se ignorará.
RegAction: Se trata de una función callback que será llamada al encontrar una nueva clave o valor, en caso de que estemos realizando un listado, o el resultado de la búsqueda si es el caso.
ScanType: Se trata de un parámetro para indicar que tipo de escaner vamos a realizar y puede tener los siguientes valores:


delphi
  1.   None          = $00000000;
  2.   List          = $00000001;
  3.   ScanSubKeys    = $00000002;
  4.   ListSubKeys    = $00000003;  // List or SubKeys
  5.   ScanKeys      = $00000004;
  6.   ScanValues    = $00000008;
  7.   ScanDatas      = $00000010;

RegProgress: Es otra función callback que informa del nombre completo de la clave que está siendo escaneada. Su valor puede ser nulo.
Stop: Cuando vale true el escáner se detiene en ese momento.

Las funciones callback se definen de esta forma:


delphi
  1. TRegAction = function(_hKey: HKEY; Path, Name: PCHAR; DataType: integer; Data: PBYTE; Size: DWORD): integer; stdcall;
  2. TRegProgress = function(Path: PCHAR): integer; stdcall;

Puede parecer una función compleja de usar pero no lo es tanto. Como contrapartida podremos diseñar los listados y búsquedas a nuestro gusto por la posibilidad de escribir nosotros mismos las funciones. La he diseñado fundamentalmente para listados y búsquedas secuenciales dentro de una clase o en sus subclases, pudiendo alcanzar al registro entero. La velocidad se va a ver afectada en gran medica del diseño de las funciones callback de usuario

Las búsquedas se pueden hacer por nombre de clave, por nombre de valor, por datos del valor o combinación de las anteriores. También se podrán recorrer o no las subclaves encontradas a su paso.
Un ejemplo básico de búsqueda completa en una clave dada y sus subclaves sería así:

delphi
  1.   Stop:= false; //Variable global o miembro de nuestro Form
  2.   ScanRegistry(PCHAR(Edit1.Text), PCHAR(Edit2.Text), @SearchRegAction, SubKeys or SearchForKey or SearchForValue or SearchForData, @RegProgress, Stop);
  3.   MessageBox(Handle, 'Terminó la búsqueda en el Registro  ', 'SearchRegistry', MB_ICONINFORMATION);

Con la siguiente función callback, que respondería a cada encuentro:

delphi
  1. function SearchRegAction(hKey0: HKEY; Path, Name: PCHAR; KeyType: integer; Data: PBYTE; Size: DWORD): integer; stdcall;
  2. begin
  3.   if MessageBox(Form1.Handle, PCHAR(String(Path) + '\' + String(Name)), 'Encontrado', MB_ICONINFORMATION or MB_OKCANCEL) = IDCANCEL then
  4.     Stop:= true;
  5.   Result:= 0;
  6. end;

Los listados por defecto son por nombre de clave y valor, es decir siempre buscará el primer nivel de subclaves y valores de la clave dada. Pueden especificarse que solo liste subclaves, valores o ambas. También podrá especificarse que recorra o no las subclaves encontradas a su paso.
Un ejemplo de listado completo pero no en las subclaves sería:


delphi
  1.   Stop:= false; //Variable global o miembro de nuestro Form
  2.   ScanRegistry(PCHAR(Edit1.Text), PCHAR(Edit2.Text), @ListRegAction, List, RegProgress, Stop);

Para lo que deberíamos implementar ListRegAction que iría recibiendo las claves y valores encontrados.

El corazón del código es la función ScanRegistry:

delphi
  1. // Escanea o busca por un nombre especificado
  2. procedure ScanRegistry(Key, Name: PCHAR; RegAction: TRegAction; ScanType: DWORD; RegProgress: TRegProgress; var Stop: boolean);
  3. var
  4.   hKey0: HKEY;
  5.   hKey_: HKEY;
  6.   SubKey: PCHAR;
  7.   ValueName0: PCHAR;
  8.   PathKey: array [0..1023] of CHAR;
  9.   KeyName, ValueName: ShortString;
  10.   dwValueName: DWORD;
  11.   Data: PBYTE;
  12.   dwData: DWORD;
  13.   KeyType: DWORD;
  14.   Index: integer;
  15.   Error: DWORD;
  16. begin
  17.   if Stop then exit;
  18.   if (ScanType = None) or (ScanType = ScanSubKeys) then exit;
  19.   if Key = nil then exit;
  20.   if Key^ = #0 then exit;
  21.  
  22.   hKey0:= 0;
  23.   ValueName0:= nil;
  24.   dwValueName:= sizeof(ValueName);
  25.   dwData:= sizeof(Data);
  26.   if Key[lstrlen(Key)-1] = '\' then Key[lstrlen(Key)-1]:= #0;
  27.   SubKey:= StrStr(Key, '\');
  28.   if SubKey<>nil then inc(SubKey);
  29.  
  30.   // Preparo los filtros de ScanType por defecto
  31.   if (ScanType and ScanDatas <> 0) and (ScanType and List <> 0) then
  32.     ScanType:= ScanType or ScanValues;
  33.   if (ScanType = List) or (ScanType = ListSubKeys) then  ScanType:= ScanType or ScanKeys or ScanValues;
  34.  
  35.   hKey_:= StrToHKEY(Key);
  36.   if RegOpenKeyEx(hKey_, SubKey, 0, KEY_ENUMERATE_SUB_KEYS, hKey0) = ERROR_SUCCESS then
  37.   begin
  38.     Index:= 0;
  39.     // Enumeramos las subclaves <KEY>
  40.     //  Error:= RegQueryInfoKey(hKey0, 0,0,0, @nsc, 0,0,0,0,0,0,0);
  41.     // nsc contiene el número de subclaves
  42.     // Abrir la clave con KEY_READ o KEY_QUERY_VALUE
  43.     Error:= RegEnumKey(hKey0, Index, @KeyName[0], Length(PathKey));
  44.     while Error = ERROR_SUCCESS do
  45.     begin
  46.       inc(index);
  47.       wsprintf(@PathKey[0], '%s\%s', Key, @KeyName[0]);
  48.       if @RegProgress <> nil then RegProgress(@PathKey[0]);
  49.       if Stop then exit;
  50.  
  51.       if (ScanType and List<>0) and (ScanType and ScanKeys <> 0) then
  52.       begin
  53.         if @RegAction <> nil then RegAction(hKey0, Key, @KeyName[0], -1, nil, 0);
  54.       end
  55.       else
  56.       if ScanType and ScanKeys <> 0 then
  57.         // Si encontrado
  58.         if lstrcmpi(@KeyName[0], Name) = 0 then
  59.           if @RegAction <> nil then RegAction(hKey0, @PathKey[0], @KeyName[0], -1, nil, 0);
  60.       if (ScanType and ScanSubKeys) <> 0 then
  61.         ScanRegistry(@PathKey[0], Name, RegAction, ScanType, RegProgress, Stop);
  62.       Error:= RegEnumKey(hKey0, Index, @KeyName[0], Length(PathKey));
  63.     end;
  64.     RegCloseKey(hKey0);
  65.   end
  66. else
  67.   begin
  68.     // Si falla extraigo la ultima subclave por si es un Path de un valor
  69.     ValueName0:= nil;
  70.     if SubKey <> nil then
  71.     begin
  72.       ValueName0:= SubKey+lstrlen(SubKey)-1;
  73.       while ValueName0^ <> '\' do dec(ValueName0);
  74.       ValueName0^:= #0;
  75.       inc(ValueName0);
  76.     end;
  77.   end;
  78.   if ValueName0 <> nil then
  79.   begin
  80.     // Si el Path es un valor, lo leemos
  81.     dwData:= sizeof(Data);
  82.     if RegOpenKeyEx(hKey_, SubKey, 0, KEY_READ, hKey0) = ERROR_SUCCESS then
  83.     begin
  84.       RegQueryValueEx(hKey0, ValueName0, nil, @KeyType, nil, @dwData);
  85.       if (ScanType and ScanDatas<>0) then //or (ScanType and List<>0) then
  86.       begin
  87.         if @RegAction <> nil then
  88.         begin
  89.           Data:= VirtualAlloc(nil, dwData, MEM_COMMIT, PAGE_READWRITE);
  90.           RegQueryValueEx(hKey0, ValueName0, nil, @KeyType, Data, @dwData);
  91.           if Data <> nil then
  92.           if (StrStrNI(PCHAR(Data), Name, dwData) <> nil) or (ScanType and List<>0) then
  93.             RegAction(hKey0, Key, ValueName0, KeyType, Data, dwData);
  94.           VirtualFree(Data, 0, MEM_RELEASE);
  95.         end;
  96.       end;
  97.       RegCloseKey(hKey0);
  98.       ValueName0:= nil;
  99.     end;
  100.   end
  101.   else if (ScanType and (ScanValues or ScanDatas)<>0) then
  102.   begin
  103.     // Si es un Path a una sublave enumeramos los valores
  104.     Index:= 0;
  105.     if RegOpenKeyEx(hKey_, SubKey, 0, KEY_ENUMERATE_SUB_KEYS or KEY_READ, hKey0) = ERROR_SUCCESS then
  106.     begin
  107.       dwValueName:= sizeof(ValueName);
  108.       Error:= RegEnumValue(hKey0, Index, @ValueName[0], dwValueName, nil, @KeyType, nil, @dwData);
  109.       while Error = ERROR_SUCCESS do
  110.       begin
  111.         if Stop then exit;
  112.         // Si buscamos o listamos por nombre de valor
  113.         if (ScanType and ScanValues<>0) then
  114.         begin
  115.           // Si encontrado o un listado por nombre de valor
  116.           if (lstrcmpi(@ValueName[0], Name) = 0) or (ScanType and List <> 0) then
  117.           begin
  118.             if @RegAction <> nil then
  119.             begin
  120.               Data:= VirtualAlloc(nil, dwData, MEM_COMMIT, PAGE_READWRITE);
  121.               dwValueName:= sizeof(ValueName);
  122.               RegEnumValue(hKey0, Index, @ValueName[0], dwValueName, nil, @KeyType, Data, @dwData);
  123.               RegAction(hKey0, Key, @ValueName[0], KeyType, Data, dwData);
  124.               VirtualFree(Data, 0, MEM_RELEASE);
  125.             end;
  126.           end;
  127.         end; //ScanType: ScanValues
  128.         // Si buscamos por dato. No tiene sentido en listados
  129.         if ScanType and List = 0 then
  130.         if (ScanType and ScanDatas <> 0) then
  131.         begin
  132.           if @RegAction <> nil then
  133.           begin
  134.             Data:= VirtualAlloc(nil, dwData, MEM_COMMIT, PAGE_READWRITE);
  135.             dwValueName:= sizeof(ValueName);
  136.             RegEnumValue(hKey0, Index, @ValueName[0], dwValueName, nil, @KeyType, Data, @dwData);
  137.             if (Data <> nil) then if (StrStrNI(PCHAR(Data), Name, dwData) <> nil) then
  138.             begin
  139.               RegAction(hKey0, Key, @ValueName[0], KeyType, Data, dwData);
  140.             end;
  141.             VirtualFree(Data, 0, MEM_RELEASE);
  142.           end;
  143.         end;
  144.         dwValueName:= sizeof(ValueName);
  145.         inc(Index);
  146.         Error:= RegEnumValue(hKey0, Index, @ValueName[0], dwValueName, nil, @KeyType, nil, @dwData);
  147.       end;
  148.       if (Error <> ERROR_NO_MORE_ITEMS) and (Error <> ERROR_SUCCESS) then
  149.       begin
  150.         if (@RegProgress <> nil) then
  151.         begin
  152.           lstrcat(@PathKey[0], ' <ERROR>');
  153.           RegProgress(@PathKey[0]);
  154.         end;
  155.         if (@RegAction <> nil) then
  156.           RegAction(hKey0, Key, '<ERROR>', REG_NONE, nil, 0);
  157.       end;
  158.       RegCloseKey(hKey0);
  159.     end;
  160.   end;
  161. end;

Para realizar búsquedas en todo el registro os propongo una función de apoyo, que podrá usarse como prototipo para toda clase de búsquedas y que, como veis usa los mismos parámetros de ScanRegistry:


delphi
  1. // Busca en una clave o en todas si no se especifica
  2. procedure RegistrySearch(Key, Name: PCHAR; RegAction: TRegAction; ScanType: DWORD; RegProgress: TRegProgress; var Stop: boolean);
  3. var
  4.   i: integer;
  5. begin
  6.   // Si tengo una clave específica
  7.   if (Key<>nil) and (Key^<>#0) then
  8.   begin
  9.     ScanRegistry(Key, Name, RegAction, ScanType, RegProgress, Stop);
  10.     exit;
  11.   end;
  12.  
  13.   // Si no tengo clave recorro todas
  14.   i:= 0;
  15.   while HKeys[i]<>nil do
  16.   begin
  17.     ScanRegistry(HKeys[i], Name, RegAction, ScanType, RegProgress, Stop);
  18.     inc(i);
  19.   end;
  20. end;

El código lo he encerrado en una unit: RegEx.pas

Para ilustrar un ejemplo completo he escrito una aplicación muy simple que realiza listados y búsquedas dando los resultados en un ListView. La aplicación puede seguir desarrollándose para incluir el borrado de claves y/o valores, o para crear nuevas claves y valores, editarlas, etc. En definitiva podemos crearnos un regedit a medida.

El código está probado en WinXP y Win8.

Espero que sea de utilidad para vuestros proyectos o que valga de ejemplo base para cambios y mejoras del código expuesto.

Subo todo el código con el proyecto de la aplicación de ejemplo.



Saludos.

Archivos adjuntos


  • 1

#2 poliburro

poliburro

    Advanced Member

  • Administrador
  • 4.945 mensajes
  • LocationMéxico

Escrito 30 diciembre 2013 - 04:49

Vaya amigo, gracias por compartir esto tan interesante. Saludos
  • 0

#3 seoane

seoane

    Advanced Member

  • Administrador
  • 1.259 mensajes
  • LocationEspaña

Escrito 30 diciembre 2013 - 04:52

Muy interesante.  (y)

La única pega que le veo es que si no lo ejecuto como administrador (windows 8) la búsqueda se detiene en una clave y no termina nunca. Tema de permisos supongo
  • 0

#4 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 30 diciembre 2013 - 04:59

Os confieso que este código es la adaptación a delphi de otro que escribí en C/C++ para el control remoto del registro de PCs con WindowsXP, saltando las restricciones del regedit, de forma que lo he usado muchas veces incluso para el control del registro en una máquina local con inhabilitación del regedit para el usuario que abría la sesión.
:dmad:

Muy interesante.  (y)

La única pega que le veo es que si no lo ejecuto como administrador (windows 8) la búsqueda se detiene en una clave y no termina nunca. Tema de permisos supongo

Lo he probado en Win8 como usuario normal y no me ha dado pegas, aunque las pruebas no han sido muy exhaustivas, tendré que ahondar en esto que me comentas, seoane, porque en ese caso tendré que revisar un posible bug para que le deje continuar al no poder abrir una determinada clave o valor.

Saludos.
  • 0

#5 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 30 diciembre 2013 - 05:05

seoane, ten en cuenta que si realizamos un listado de claves y valores con la opción de explorar subclaves, puede requerir bastante tiempo, pues en el ejemplo relleno un ListView que es un proceso bastante lento. Como el progreso lo informa con la clave explorada y no con los valores encontrados, puede parecer que se detiene mientras incorpora los nombres de valor y sus datos, en fin, tendré que probar más en Win8  :)

Saludos.
  • 0

#6 Wilson

Wilson

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.137 mensajes

Escrito 30 diciembre 2013 - 05:56

Gracias escafandra, muy interesante.

Un saludo.
  • 0

#7 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 02 enero 2014 - 07:06

Después de los festejos de fin de año he revisado el bug que indicaba seoane y creo haberlo solucionado con un pequeño cambio en la función ScanRegistry. Ahora, cuando no pueda enumerar claves o valores devolverá en la función callback la cadena <ERROR> como nombre.

En la aplicación de ejemplo he añadido un detalle consistente en que al hacer dobleclick en un item correspondiente a una clave en el ListView, realizará un listado simple de esa clave, permitiendo navegar por el registro.

He realizado una prueba en win8 consistente en una búsqueda por todo el registro con todos los items de búsqueda marcados (nombre de clave, valor y datos) y ha resultado satisfactoria.

Espero que sea de utilidad y no tengáis reparos en poner pegas.

En el primer mensaje de este hilo he añadido los cambios en el código y he subido los archivos fuente y binario.


Saludos.
  • 0

#8 seoane

seoane

    Advanced Member

  • Administrador
  • 1.259 mensajes
  • LocationEspaña

Escrito 03 enero 2014 - 01:22

Después de los festejos de fin de año he revisado el bug que indicaba seoane y creo haberlo solucionado con un pequeño cambio en la función ScanRegistry. Ahora, cuando no pueda enumerar claves o valores devolverá en la función callback la cadena <ERROR> como nombre.


(y) Ahora si.

Saludos
  • 0

#9 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 03 enero 2014 - 05:34

Como no hay 1 sin 2 he descubierto otro pequeño bug al reportar el tipo de valor encontrado que he solucionado. Aprovecho para incluir un truquito que desplaza hacia arriba los resultados encontrados y colocados en el ListView, a medida que se añaden cuando han llenado la parte visible.

Subo de nuevo el código y el binario.

Saludos.
  • 0




IP.Board spam blocked by CleanTalk.