// -----------------------------------------------------------------------------
//
// TFileTypeRegistration-Klasse (Win32-API)
// Copyright (c) 2004 Mathias Simmack
//
// -----------------------------------------------------------------------------

// -- Revision history ---------------------------------------------------------
//
//   * erste Version
//
// -----------------------------------------------------------------------------
unit FTypeReg;

interface

uses
  Windows, ShlObj, SysUtils;

type
  TFileTypeRegistration = class
    FRegConnector : HKEY;
    FExtension,
    FInternalName : string;
    FVerb         : string;
  public
    constructor Create;
    destructor Destroy; override;
    function RegisterType(const Extension, InternalName: string;
      Description: string = ''; IconFile: string = '';
      IconIndex: integer = -1): boolean;
    function UnregisterExtension(const Extension: string): boolean;
    function UnregisterType(const Extension: string): boolean;
    procedure UpdateShell;
    function AddHandler(const HandlerVerb, CommandLine: string;
      HandlerDescription: string = ''): boolean; overload;
    function DeleteHandler(const HandlerVerb: string): boolean;
    function SetDefaultHandler: boolean; overload;
    function SetDefaultHandler(const HandlerVerb: string): boolean; overload;
    function GetInternalKey(const Extension: string): string;
    function AddNewFileSupport(const Extension: string): boolean;
    function RemoveNewFileSupport(const Extension: string): boolean;

    property Extension: string read FExtension;
    property InternalName: string read FInternalName;
    property CurrentVerb: string read FVerb;
  end;


implementation

(* *****************************************************************************

  Beispiel #1: Einen neuen Dateityp registrieren
  ----------------------------------------------

  ftr := TFileTypeRegistration.Create;
  if(ftr <> nil) then
  try
    // die Dateiendung ".foo" registrieren, der interne Schlüssel
    // lautet "FooFile", eine Beschreibung und eine Symboldatei
    // sind ebenfalls angegeben
    if(ftr.RegisterType('.foo','FooFile','FOO Description',
      'c:\folder\icon.ico')) then
    begin
      // fügt den Handler "open" hinzu und verknüpft ihn mit dem
      // Programm "foo.exe"
      ftr.AddHandler('open','"c:\folder\foo.exe" "%1"');

      // setzt den zuletzt benutzten Handler ("open" in dem Fall)
      // als Standard
      ftr.SetDefaultHandler;
    end;

    if(ftr.RegisterType('.foo','ThisIsNotTheFOOKey')) then
    // Das ist kein Fehler! Obwohl hier der interne Name
    // "ThisIsNotTheFOOKey" verwendet wird, benutzt die Funktion
    // intern den bereits vorhandenen Schlüssel "FooFile" (s. oben).
    begin
      // zwei neue Handler werden registriert, ...
      ftr.AddHandler('print','"c:\folder\foo.exe" /p "%1"');
      ftr.AddHandler('edit','notepad.exe "%1"');

      // ... & dank der überladenen Funktion "SetDefaultHandler"
      // kann diesmal auch "print" als Standardhandler gesetzt
      // werden
      ftr.SetDefaultHandler('print');
    end;
  finally
    ftr.Free;
  end;


  Beispiel #2: Einen neuen Typ mit einem vorhandenen Schlüssel
  verknüpfen
  ------------------------------------------------------------

  Das Beispiel registriert die Endung ".foo" auf die gleiche
  Weise wie Textdateien (.txt). Es wird einfach der interne
  Schlüsselname ermittelt und für die Endung ".foo" gesetzt

  ftr := TFileTypeRegistration.Create;
  if(ftr <> nil) then
  try
    strInternalTextFileKey := ftr.GetInternalKey('.txt');
    if(strInternalTextFileKey <> '') then
      ftr.RegisterType('.foo',strInternalTextFileKey);
  finally
    ftr.Free;
  end;


  Beispiel #3: Einen Handler entfernen
  ------------------------------------

  ftr := TFileTypeRegistration.Create;
  if(ftr <> nil) then
  try
    // den internen Schlüsselnamen des Typs ".foo" ermitteln, ...
    if(ftr.GetInternalKey('.foo') <> '') then
    // ... wobei das Ergebnis in dem Fall unwichtig ist, weil
    // intern auch die Eigenschaft "FInternalName" gesetzt
    // wird
    begin
      // den "print"-Handler entfernen, ...
      ftr.DeleteHandler('print');

      // ... & den Standardhandler aktualisieren
      ftr.SetDefaultHandler('open');
    end;
  finally
    ftr.Free;
  end;


  Beispiel #4: Nur eine Dateiendung entfernen
  -------------------------------------------

  In diesem Fall wird lediglich die Endung ".foo" entfernt. Der
  evtl. vorhandene interne Schlüssel bleibt bestehen. Das ist
  für das Beispiel #2 nützlich, wenn die Endung ".foo" entfernt
  werden soll, intern aber mit den Textdateien verlinkt ist, die
  ja im Normalfall nicht entfernt werden dürfen/sollten.

    ftr.UnregisterExtension('.foo');


  Beispiel #5: Den kompletten Dateityp entfernen
  ----------------------------------------------

  Dieses Beispiel entfernt dagegen den kompletten Dateityp,
  inkl. des evtl. vorhandenen internen Schlüssels (vgl. mit
  Beispiel #4).

    ftr.UnregisterType('.foo');

  Bezogen auf Beispiel #2 wäre das die fatale Lösung, weil dadurch
  zwar die Endung ".foo" deregistriert wird, gleichzeitig wird
  aber auch der intern verwendete Schlüssel der Textdateien
  gelöscht.

  ALSO, VORSICHT!!!

***************************************************************************** *)


//
// Admin-Rechte sind erforderlich (Funktion von NicoDE)
//
//{$INCLUDE IsAdmin.inc}
function GetAdminSid: PSID;
const
  // bekannte SIDs ... (WinNT.h)
  SECURITYNTAUTHORITY: TSIDIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5));
  // bekannte RIDs ... (WinNT.h)
  SECURITYBUILTINDOMAINRID: DWORD = $00000020;
  DOMAINALIASRIDADMINS: DWORD = $00000220;
begin
  Result := nil;
  AllocateAndInitializeSid(SECURITYNTAUTHORITY, 2, SECURITYBUILTINDOMAINRID,
    DOMAINALIASRIDADMINS, 0, 0, 0, 0, 0, 0, Result);
end;

function IsAdmin: LongBool;
var
  TokenHandle      : THandle;
  ReturnLength     : DWORD;
  TokenInformation : PTokenGroups;
  AdminSid         : PSID;
  Loop             : Integer;
  wv               : TOSVersionInfo;
begin
  wv.dwOSVersionInfoSize := sizeof(TOSversionInfo);
  GetVersionEx(wv);

  Result := (wv.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS);

  if(wv.dwPlatformId = VER_PLATFORM_WIN32_NT) then
    begin
      TokenHandle := 0;
      if OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, TokenHandle) then
        try
          ReturnLength := 0;
          GetTokenInformation(TokenHandle, TokenGroups, nil, 0, ReturnLength);
          TokenInformation := GetMemory(ReturnLength);
          if Assigned(TokenInformation) then
            try
              if GetTokenInformation(TokenHandle, TokenGroups,
                TokenInformation, ReturnLength, ReturnLength) then
              begin
                AdminSid := GetAdminSid;
                for Loop := 0 to TokenInformation^.GroupCount - 1 do
                  begin
                    if EqualSid(TokenInformation^.Groups[Loop].Sid, AdminSid) then
                      begin
                        Result := True;
                        break;
                      end;
                  end;
                FreeSid(AdminSid);
              end;
            finally
              FreeMemory(TokenInformation);
            end;
        finally
          CloseHandle(TokenHandle);
        end;
    end;
end;

function WVersion: string;
var
  OSInfo: TOSVersionInfo;
begin
  Result := '3X';
  OSInfo.dwOSVersionInfoSize := sizeof(TOSVERSIONINFO);
  GetVersionEx(OSInfo);
  case OSInfo.dwPlatformID of
    VER_PLATFORM_WIN32S: begin
        Result := '3X';
        Exit;
      end;
    VER_PLATFORM_WIN32_WINDOWS: begin
        Result := '9X';
        Exit;
      end;
    VER_PLATFORM_WIN32_NT: begin
        Result := 'NT';
        Exit;
      end;
  end; //case
end;


// -----------------------------------------------------------------------------
//
// Registry
//
// -----------------------------------------------------------------------------

function RegWriteSubKeyVal(const parent: HKEY; SubKeyName: string;
  ValueName, Value: string): boolean;
var
  tmp    : HKEY;
begin
  Result := false;
  if(parent = INVALID_HANDLE_VALUE) or
    (SubKeyName = '') then exit;

  if(RegCreateKeyEx(parent,pchar(SubKeyName),0,nil,0,KEY_READ or KEY_WRITE,
    nil,tmp,nil) = ERROR_SUCCESS) then
  try
    Result := (RegSetValueEx(tmp,pchar(ValueName),0,REG_SZ,pchar(Value),
      length(Value) + 1) = ERROR_SUCCESS);
  finally
    RegCloseKey(tmp);
  end;
end;

function RegReadSubKeyStr(const parent: HKEY; SubKeyName: string;
  ValueName: string): string;
var
  tmp     : HKEY;
  lpData,
  dwLen   : dword;
begin
  Result  := '';
  if(parent = INVALID_HANDLE_VALUE) or
    (SubKeyName = '') then exit;

  if(RegOpenKeyEx(parent,pchar(SubKeyName),0,KEY_READ,
    tmp) = ERROR_SUCCESS) then
  try
    lpData := REG_NONE;
    dwLen  := 0;
    if(RegQueryValueEx(tmp,pchar(ValueName),nil,@lpData,nil,
         @dwLen) = ERROR_SUCCESS) and
      (lpData in[REG_SZ,REG_EXPAND_SZ]) and
      (dwLen > 0) then
    begin
      SetLength(Result,dwLen);

      if(RegQueryValueEx(tmp,pchar(ValueName),nil,@lpData,
           @Result[1],@dwLen) = ERROR_SUCCESS) then
        SetLength(Result,dwLen - 1)
      else
        Result := '';
    end;
  finally
    RegCloseKey(tmp);
  end;
end;

function RegKeyExists(const parent: HKEY; KeyName: string): boolean;
var
  tmp    : HKEY;
begin
  Result := (RegOpenKeyEx(parent,pchar(KeyName),0,KEY_READ,tmp) =
    ERROR_SUCCESS);
  if(Result) then RegCloseKey(tmp);
end;

function RegDeleteWholeKey(parent: HKEY; KeyName: string): boolean;
var
  reg       : HKEY;
  dwSubkeys : dword;
  dwLen     : dword;
  i         : integer;
  buf       : array[0..MAX_PATH]of char;
begin
  if(RegOpenKeyEx(parent,pchar(KeyName),0,KEY_READ,reg) = ERROR_SUCCESS) then
  try
    if(RegQueryInfoKey(reg,nil,nil,nil,@dwSubKeys,nil,
      nil,nil,nil,nil,nil,nil) = ERROR_SUCCESS) and
      (dwSubKeys > 0) then
    for i := 0 to dwSubKeys - 1 do begin
      ZeroMemory(@buf,sizeof(buf));
      dwLen   := MAX_PATH;

      if(RegEnumKeyEx(reg,i,buf,dwLen,nil,nil,nil,nil) = ERROR_SUCCESS) and
        (dwLen > 0) then
      RegDeleteWholeKey(reg,buf);
    end;
  finally
    RegCloseKey(reg);
  end;

  Result := (RegDeleteKey(parent,pchar(KeyName)) = ERROR_SUCCESS);
end;


// -----------------------------------------------------------------------------
//
// TFileTypeRegistration-Klasse
//
// -----------------------------------------------------------------------------

constructor TFileTypeRegistration.Create;
var
  key: HKEY;
  sub: PChar;
begin
  FExtension    := '';
  FInternalName := '';
  FVerb         := '';

  // Zugriff auf die Registry, & HKEY_CLASSES_ROOT
  // als Root setzen
  if(WVersion='9X') or IsAdmin then begin
    key:=HKEY_CLASSES_ROOT;
    sub:=nil;
  end else begin
    key:=HKEY_CURRENT_USER;
    sub:=PChar('SOFTWARE\Classes');
  end;

  if RegOpenKeyEx(key,sub,0,KEY_ALL_ACCESS, FRegConnector) <> ERROR_SUCCESS then
    FRegConnector := INVALID_HANDLE_VALUE;
end;

destructor TFileTypeRegistration.Destroy;
begin
  if(FRegConnector <> INVALID_HANDLE_VALUE) then
    RegCloseKey(FRegConnector);
end;

function TFileTypeRegistration.RegisterType(const Extension,
  InternalName: string; Description: string = ''; IconFile: string = '';
  IconIndex: integer = -1): boolean;
var
  strDummy : string;
begin
  // Standardergebnis
  Result         := false;
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (Extension = '') or
    (Extension[1] <> '.') then exit;

  // ist dieser Typ evtl. schon registriert?
  strDummy := self.GetInternalKey(Extension);

  // Nein. :o)
  if(strDummy = '') then strDummy := InternalName;

  // den Schlüssel mit der Dateiendung anlegen oder aktualisieren
  Result := RegWriteSubKeyVal(FRegConnector,Extension,'',strDummy);
  if(not Result) then exit;

  // den internen Schlüssel öffnen
  if(Result) then
  begin
    // Beschreibung anlegen
    if(Description <> '') then
      RegWriteSubKeyVal(FRegConnector,strDummy,'',Description);

    // Symbol zuweisen (Datei muss existieren!)
    if(IconFile <> '') and
      (fileexists(IconFile)) then
    begin
      if(IconIndex <> -1) then
        RegWriteSubKeyVal(FRegConnector,strDummy + '\DefaultIcon',
          '',Format('%s,%d',[IconFile,IconIndex]))
      else
        RegWriteSubKeyVal(FRegConnector,strDummy + '\DefaultIcon',
          '',IconFile);
    end;
  end;

  // Systemsymbole aktualisieren
  self.UpdateShell;

  // Properties aktualisieren
  if(Result) then
  begin
    FExtension    := Extension;
    FInternalName := strDummy;
  end;
end;

function TFileTypeRegistration.UnregisterExtension(const Extension: string):
  boolean;
begin
  Result := false;
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (Extension = '') or
    (Extension[1] <> '.') then exit;

  // die Endung entfernen
  Result := (RegKeyExists(FRegConnector,Extension)) and
    (RegDeleteWholeKey(FRegConnector,Extension));

  // Systemsymbole aktualisieren
  self.UpdateShell;
end;

function TFileTypeRegistration.UnregisterType(const Extension: string):
  boolean;
var
  strDummy : string;
begin
  Result   := false;
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (Extension = '') or
    (Extension[1] <> '.') then exit;

  // den internen Namen der Endung ermitteln
  strDummy := self.GetInternalKey(Extension);

  // die Endung entfernen (s. "UnregisterExtension"), ...
  Result   := (self.UnregisterExtension(Extension)) and
  // ... & den internen Schlüssel löschen
    (strDummy <> '') and
    (RegKeyExists(FRegConnector,strDummy)) and
    (RegDeleteWholeKey(FRegConnector,strDummy));

  // Systemsymbole aktualisieren
  self.UpdateShell;
end;

procedure TFileTypeRegistration.UpdateShell;
begin
  SHChangeNotify(SHCNE_ASSOCCHANGED,SHCNF_IDLIST,nil,nil);
end;


const
  ShellKey = '%s\shell\%s';

function TFileTypeRegistration.AddHandler(const HandlerVerb,
  CommandLine: string; HandlerDescription: string = ''): boolean;
begin
  // Standardergebnis
  Result := false;
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (FInternalName = '') or
    (HandlerVerb = '') or
    (CommandLine = '') then exit;

  // der interne Schlüssel muss existieren
  if(RegKeyExists(FRegConnector,FInternalName)) then
  begin
    // den Handler (= Verb) erzeugen
    Result := RegWriteSubKeyVal(FRegConnector,
      Format(ShellKey + '\command',[FInternalName,HandlerVerb]),
      '',
      CommandLine);

    // ggf. Beschreibung für Handler setzen
    if(HandlerDescription <> '') then
      RegWriteSubKeyVal(FRegConnector,
        Format(ShellKey,[FInternalName,HandlerVerb]),
        '',
        HandlerDescription);
  end;

  // interne Eigenschaft anpassen (für "SetDefaultHandler")
  if(Result) then
    FVerb := HandlerVerb;
end;

function TFileTypeRegistration.DeleteHandler(const HandlerVerb: string):
  boolean;
begin
  // Standardergebnis
  Result := false;
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (FInternalName = '') or
    (HandlerVerb = '') then exit;

  // Handlerschlüssel entfernen (sofern vorhanden)
  Result :=
    (RegKeyExists(FRegConnector,
       Format(ShellKey,[FInternalName,HandlerVerb]))) and
    (RegDeleteWholeKey(FRegConnector,
       Format(ShellKey,[FInternalName,HandlerVerb])));
end;

function TFileTypeRegistration.SetDefaultHandler: boolean;
begin
  if(FInternalName <> '') and (FVerb <> '') then
    Result := self.SetDefaultHandler(FVerb)
  else
    Result := false;
end;

function TFileTypeRegistration.SetDefaultHandler(const HandlerVerb: string):
  boolean;
begin
  Result := false;
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (FInternalName = '') or
    (HandlerVerb = '') then exit;

  // interner Schlüssel muss existieren, ...
  if(RegKeyExists(FRegConnector,FInternalName)) and
  // ... & Handler muss existieren, ...
    (RegKeyExists(FRegConnector,
       Format(ShellKey,[FInternalName,HandlerVerb]))) then
  begin
  // ... dann den Handler als Standard eintragen
    Result := RegWriteSubKeyVal(FRegConnector,FInternalName + '\shell',
      '',HandlerVerb);
  end;
end;

function TFileTypeRegistration.GetInternalKey(const Extension: string): string;
begin
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (Extension = '') or
    (Extension[1] <> '.') then exit;

  // einen evtl. eingestellten internen Namen zurücksetzen
  FInternalName   := '';

  // den Schlüssel der Dateiendung öffnen, ...
  if(RegKeyExists(FRegConnector,Extension)) then
    FInternalName := RegReadSubKeyStr(FRegConnector,Extension,'');

  // ... als Funktionsergebnis zurückliefern
  if(not RegKeyExists(FRegConnector,FInternalName)) then
    FInternalName := '';

  Result := FInternalName;
end;


function TFileTypeRegistration.AddNewFileSupport(const Extension: string):
  boolean;
var
  Description : string;
begin
  Result      := false;
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (Extension = '') or
    (Extension[1] <> '.') then exit;

  // interne Beschreibung des Typs ermitteln
  if(self.GetInternalKey(Extension) <> '') then
    Description := RegReadSubKeyStr(FRegConnector,FInternalName,'')
  else
    Description := '';

  // die Beschreibung darf keine Leerzeichen enthalten, weil sie
  // als Referenz für den neuen Dateinamen verwendet wird, ...
  if(pos(#32,Description) > 0) or
  // ... & sie darf auch nicht leer sein
    (Description = '') then exit;

  Result := (RegKeyExists(FRegConnector,Extension)) and
    (RegWriteSubKeyVal(FRegConnector,Extension + '\ShellNew','NullFile',''));
end;

function TFileTypeRegistration.RemoveNewFileSupport(const Extension: string):
  boolean;
begin
  Result := false;
  if(FRegConnector = INVALID_HANDLE_VALUE) or
    (Extension = '') or
    (Extension[1] <> '.') then exit;

  Result := (RegKeyExists(FRegConnector,Extension + '\ShellNew')) and
    (RegDeleteWholeKey(FRegConnector,Extension + '\ShellNew'));
end;

end.