Write dword value in Registry with Delphi - delphi

My program has a TWebBrowser where the user can open all kinds of local documents. To avoid that for example a Word document is opened in Word instead of in the TWebBrowser (that is to say, in Internet Explorer), I successfully use a fix in the Registry, by executing a .reg file with this instruction:
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Word.Document.12]
"BrowserFlags"=dword:80000024
I am trying to introduce that instruction in the Delphi program itself, with this code:
procedure RegOpenExplorer;
var
reg: TRegistry;
begin
reg:= TRegistry.Create;
try
reg.RootKey:=HKEY_LOCAL_MACHINE;
reg.OpenKey('SOFTWARE\Classes\Word.Document.12\', true);
reg.WriteInteger('BrowserFlags',80000024);
reg.CloseKey;
finally
reg.Free;
end;
end;
It does not work, actually the effect is undoing the fix.
When successfully manipulated with the .reg file (or manually), the Registry key looks like this:
But with my unsuccessful Delphi Code, the key becomes as follows:
The difference is the number in brackets, but that is something that the Registry introduces automatically by itself.

The numeric value in the .reg file is encoded as hex. Since you are passing an integer literal to WriteInteger(), you need to prefix it with a $ to make the compiler interpret it as hex:
reg.WriteInteger('BrowserFlags', $80000024);
That being said, note that you are writing to HKEY_LOCAL_MACHINE, and more importantly you are opening the key with KEY_ALL_ACCESS access rights (the default access rights that TRegistry uses). This is going to require you to run your app elevated as an administrator, if it is not already. You should be setting the TRegistry.Access property to KEY_SET_VALUE instead, and maybe even writing to HKEY_CURRENT_USER instead.
procedure RegOpenExplorer;
var
reg: TRegistry;
begin
reg := TRegistry.Create(KEY_SET_VALUE);
try
reg.RootKey := HKEY_LOCAL_MACHINE; // or HKEY_CURRENT_USER
if reg.OpenKey('SOFTWARE\Classes\Word.Document.12\', true) then
try
reg.WriteInteger('BrowserFlags', $80000024);
finally
reg.CloseKey;
end;
finally
reg.Free;
end;
end;

Related

Acceptable replacement for WinExec()?

I want to run a command that the user defined in an INI file.
The commands can be EXE files, or other files (e.g. DOC files) and parameters should be allowed.
Since WinExec() can handle arguments (e.g. "cmd /?"), but ShellExec() can handle Non-EXE files (e.g. "Letter.doc"), I am using a combination of these both.
I am concerned about future Windows versions, because WinExec() is deprecated, and even from the 16 bit era.
Here is my current function:
procedure RunCMD(cmdLine: string; WindowMode: integer);
procedure ShowWindowsErrorMessage(r: integer);
begin
MessageDlg(SysErrorMessage(r), mtError, [mbOK], 0);
end;
var
r, g: Cardinal;
begin
// We need a function which does following:
// 1. Replace the Environment strings, e.g. %SystemRoot% --> ExpandEnvStr
// 2. Runs EXE files with parameters (e.g. "cmd.exe /?") --> WinExec
// 3. Runs EXE files without path (e.g. "calc.exe") --> WinExec
// 4. Runs EXE files without extension (e.g. "calc") --> WinExec
// 5. Runs non-EXE files (e.g. "Letter.doc") --> ShellExecute
// 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes.
cmdLine := ExpandEnvStr(cmdLine);
// TODO: Attention: WinExec() is deprecated, but there is no acceptable replacement
g := WinExec(PChar(cmdLine), WindowMode);
r := GetLastError;
if g = ERROR_BAD_FORMAT then
begin
// e.g. if the user tries to open a non-EXE file
ShellExecute(0, nil, PChar(cmdLine), '', '', WindowMode);
r := GetLastError;
end;
if r <> 0 then ShowWindowsErrorMessage(r);
end;
function ExpandEnvStr(const szInput: string): string;
// http://stackoverflow.com/a/2833147/3544341
const
MAXSIZE = 32768;
begin
SetLength(Result, MAXSIZE);
SetLength(Result, ExpandEnvironmentStrings(pchar(szInput),
#Result[1],length(Result)));
end;
Microsoft recommends using CreateProcess(), but I do not accept it as a real replacement for WinExec().
For example, given following command line:
"C:\Program Files\xyz.exe" /a /b /c
Since ShellExecute() and CreateProcess() require a strict separation of command and arguments, I would have to parse this string myself. Is that really the only way I can go? Has someone written a public available code featuring this functionality?
Additional note: The process should not be attached to the caller. My program will close, right after the command has started.
CreateProcess() is the replacement for WinExec(). The documentation explicitly states as much.
And BTW, the error handling in your original code is completely wrong. You are misusing GetLastError(). In fact, neither WinExec() nor ShellExecute() even report errors with GetLastError() to begin with. So, even if WinExec() or ShellExecute() are successful (and you are not even checking if ShellExecute() succeeds or fails), you risk reporting random errors from earlier API calls.
Try something more like this:
procedure RunCMD(cmdLine: string; WindowMode: integer);
procedure ShowWindowsErrorMessage(r: integer);
var
sMsg: string;
begin
sMsg := SysErrorMessage(r);
if (sMsg = '') and (r = ERROR_BAD_EXE_FORMAT) then
sMsg := SysErrorMessage(ERROR_BAD_FORMAT);
MessageDlg(sMsg, mtError, [mbOK], 0);
end;
var
si: TStartupInfo;
pi: TProcessInformation;
sei: TShellExecuteInfo;
err: Integer;
begin
// We need a function which does following:
// 1. Replace the Environment strings, e.g. %SystemRoot% --> ExpandEnvStr
// 2. Runs EXE files with parameters (e.g. "cmd.exe /?") --> WinExec
// 3. Runs EXE files without path (e.g. "calc.exe") --> WinExec
// 4. Runs EXE files without extension (e.g. "calc") --> WinExec
// 5. Runs non-EXE files (e.g. "Letter.doc") --> ShellExecute
// 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes.
cmdLine := ExpandEnvStr(cmdLine);
{$IFDEF UNICODE}
UniqueString(cmdLine);
{$ENDIF}
ZeroMemory(#si, sizeof(si));
si.cb := sizeof(si);
si.dwFlags := STARTF_USESHOWWINDOW;
si.wShowWindow := WindowMode;
if CreateProcess(nil, PChar(cmdLine), nil, nil, False, 0, nil, nil, si, pi) then
begin
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
Exit;
end;
err := GetLastError;
if (err = ERROR_BAD_EXE_FORMAT) or
(err = ERROR_BAD_FORMAT) then
begin
ZeroMemory(#sei, sizeof(sei));
sei.cbSize := sizeof(sei);
sei.lpFile := PChar(cmdLine);
sei.nShow := WindowMode;
if ShellExecuteEx(#sei) then Exit;
err := GetLastError;
end;
ShowWindowsErrorMessage(err);
end;
Although similar ShellExecute and CreateProcess serve a different purpose.
ShellExecute can open non executable files. It looks up the information in the registry for the associated program for the given file and will execute it. ShellExecute is also great for launching the default web browser and you can pass a URL to it it.
So if you were to pass an TXT file to ShellExecute it would open the associated program such as notepad. However it would fail with CreateProcess.
CreateProcess is a lower level function, that allows you to have better control over the input and output from the the process. For example you can call command line programs that have text output with CreateProcess and capture that output and react according.
Given the concerns you have you will need to use ShellExecute. You will however need to split the command from the parameters. This is would be the first non escaped whitespace character.
I personally rarely call ShellExecute or CreateProcess directly. I tend to use on of the following functions from the JCL that wrap these functions.
JclMiscel.pas
CreateDosProcessRedirected
WinExec32
WinExec32AndWait
WinExecAndRedirectOutput
CreateProcessAsUser
CreateProcessAsUserEx
JclShell.pas
ShellExecEx
ShellExec
ShellExecAndWwait
RunAsAdmin
JclSysUtils.pas
Execute (8 Overloaded versions and is the one I use the most)

Changes to TRegistry key dont 'hold'

From my Win32 app I'm reading and writing HKEY_CURRENT_USER\Software\Embarcadero\BDS\9.0\History Lists\hlRunParameters, that is where the Delphi XE2 IDE writes run-time parameters.
This is the write code:
procedure TFrmCleanIDEParams.BtnWriteClick(Sender: TObject);
var
lReg : TRegistry;
lValue,
lKey : String;
i,
lNrToWrite,
lNrRegVals: Integer;
begin
.....
lKey := Trim(EdtRegKey.Text); // '\Software\Embarcadero\BDS\9.0\History Lists\hlRunParameters'
if lKey = '' then Exit;
if lKey[1] = '\' then lKey := Copy(lKey,2);
lReg := TRegistry.Create(KEY_READ or KEY_WRITE);
lReg.RootKey := HKEY_CURRENT_USER;
if not lReg.OpenKey(lKey,false) then
begin
MessageDlg('Key not found', mtError, mbOKCancel, 0);
Exit;
end;
if not lReg.ValueExists('Count') then
begin
MessageDlg('Value ''Count'' not found', mtError, mbOKCancel, 0);
Exit;
end;
lNrRegVals := lReg.ReadInteger('Count');
lNrToWrite := CLBParams.Items.Count; // TCheckListBox
lReg.WriteInteger('Count',lNrToWrite);
for i := 0 to lNrToWrite-1 do
begin
lValue := 'Item' + IntToStr(i);
lReg.WriteString(lValue,CLBParams.Items[i]);
end;
// Remove the rest:
for i := lNrToWrite to lNrRegVals-1 do
lReg.DeleteValue('Item' + IntToStr(i));
end;
Issues:
In RegEdit I see the key contents changing as expected, but the Delphi IDE does not pick up these changes
Some time (reboot?) later the HKEY_CURRENT_USER key has its old values
I think several things could be the reason, but I'm not sure which ones to attack:
I should not use HKEY_CURRENT_USER, but HKEY_USERS. If this is the case, how do I then get the proper S-1-5-etc that I need to use?
It's a Windows 7 64-bit issue, although both my program and the Delphi IDE are 32 bit. (How) do I then need to change the TRegistry.Create?
I read this Delphi: Read 64-bits registry key from 32-bits process post but that still does not tell me if/when to use different 'access keys'.
Do I always need to use this KEY_WOW64_64KEY value regardless of my app being 32/64 bit? I see that HKEY_CURRENT_USER\Software is shared, not redirected. (How) do I need to treat these differently?
BTW UAC is off, it would be nice if my code worked with UAC on too.
The Delphi IDE will only read these values at start up. But you must make sure that you write the registry values after the IDE has finished writing to them.
You should be using HKEY_CURRENT_USER.
You should not be using an alternate registry view flag because that part of the registry is shared.
UAC won't have any impact here because HKEY_CURRENT_USER is writeable for the standard user token.
The only explanation that makes sense is that another process is modifying the values. My guess is that the Delphi IDE is that process.

How to set value of Registry Key

I am having one Delphi XE2 project to write something in registry key. So I have defined the following codes :
procedure TMainForm.BitBtn01Click(Sender: TObject);
var
RegistryEntry: TRegistry;
begin
RegistryEntry:= TRegistry.Create(KEY_READ);
RegistryEntry.RootKey:= HKEY_LOCAL_MACHINE;
if (not RegistryEntry.KeyExists('Software\MyCompanyName\MyName\')) then
begin
RegistryEntry.Access:= KEY_WRITE;
RegistryEntry.OpenKey('Software\MyCompanyName\MyName\',True);
end;
RegistryEntry.CloseKey();
RegistryEntry.Free;
end;
If any string addition I have defined the following codes :
if (not RegistryEntry.KeyExists('Licenced To')) then
begin
RegistryEntry.WriteString('Licenced To', 'MySurname MyFirstName');
end;
My requirements :
01. Setting the default value as shown :
02. In Win64 OS the node is created under HKEY_LOCAL_MACHINE\WOWSys64\Software but not under HKEY_LOCAL_MACHINE\Software.
that desired behavoir for 32-Bit applications.
If you need to write to 64-Bit root you can use KEY_WOW64_64KEY;
In any case you will need elevated rights for writung to HKEY_LOCAL_MACHINE
RegistryEntry.Access:= KEY_WRITE or KEY_WOW64_64KEY;

how to store settings in resource

i am trying to store some settings in resource of my application
but failed
i dont want to use ini file or registry methods
i am using this code
var
data :string;
procedure WriteSettings(ServerFile: string; Settings: string);
var
ResourceHandle: THandle;
pwServerFile: PWideChar;
begin
GetMem(pwServerFile, (Length(ServerFile) + 1) * 2);
try
StringToWideChar(ServerFile, pwServerFile, Length(ServerFile) * 2);
ResourceHandle := BeginUpdateResourceW(pwServerFile, False);
UpdateResourceW(ResourceHandle, MakeIntResourceW(10), 'SETTINGS', 0, #Settings[1], Length(Settings) + 1);
EndUpdateResourceW(ResourceHandle, False);
finally
FreeMem(pwServerFile);
end;
end;
function ReadSettings(ServerFile: string): string;
var
ServerModule: HMODULE;
ResourceLocation: HRSRC;
ResourceSize: dword;
ResourceHandle: THandle;
ResourcePointer: pointer;
begin
ServerModule := LoadLibrary(pchar(ServerFile));
try
ResourceLocation := FindResource(ServerModule, 'SETTINGS', RT_RCDATA);
ResourceSize := SizeofResource(ServerModule, ResourceLocation);
ResourceHandle := LoadResource(ServerModule, ResourceLocation);
ResourcePointer := LockResource(ResourceHandle);
if ResourcePointer <> nil then
begin
SetLength(Result, ResourceSize - 1);
CopyMemory(#Result[1], ResourcePointer, ResourceSize);
FreeResource(ResourceHandle);
end;
finally
FreeLibrary(ServerModule);
end;
end;
procedure TForm1.saveClick(Sender: TObject);
begin
writesettings(paramastr(0),'true');
end;
procedure TForm1.ReadClick(Sender: TObject);
begin
data:=readsettings(paramstr(0));
end;
begin
if data='true' then checkbox1.checked:=true;
end
but is nit storing the that i wrote to resource :(
is there any other better options?
any help please
The documentation for BeginUpdateResource clearly states why your code doesn't work (emphasis added):
pFileName [in]
LPCTSTR
The binary file in which to update resources. An application must be able to obtain write-access to this file; the file referenced by pFileName cannot be currently executing. If pFileName does not specify a full path, the system searches for the file in the current directory.
You might have been able to deduce the cause of the error yourself if you were checking the API function's return value and calling GetLastError on failure, like the documentation advises.
You can store settings in a resource, but you can't store settings in a resource of the program whose settings you're trying to store. And now that we've established that you're not allowed to store settings in the program itself, you may as well just abandon the resource idea and use a more conventional method of storing settings in an external location, such as the registry, an INI file, or whatever. You might still wish to read a set of default settings from a resource if you find that the external location doesn't yet have any settings, as might happen after a fresh install.
Having your program modify itself is a bad idea. As a couple people already pointed out, this will fail badly under Vista and Win7 in most cases. It's better not to fight the operating system. Windows already provides a couple different ways for your program to store its settings. You can drop an INI or other config file in some folder outside of Program Files, or you can store it in the Registry, which is probably the best option.

Detect if an OCX class is registered in Windows

i need to know how can detect if an OCX class (ClassID) is registred in Windows
something like
function IsClassRegistered(ClassID:string):boolean;
begin
//the magic goes here
end;
begin
if IsClassRegistered('{26313B07-4199-450B-8342-305BCB7C217F}') then
// do the work
end;
you can check the existence of the CLSID under the HKEY_CLASSES_ROOT in the windows registry.
check this sample
function ExistClassID(const ClassID :string): Boolean;
var
Reg: TRegistry;
begin
try
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_CLASSES_ROOT;
Result := Reg.KeyExists(Format('CLSID\%s',[ClassID]));
finally
Reg.Free;
end;
except
Result := False;
end;
end;
The problem with (many, many) suggestions of crawling the registry is that:
there is more than one registry location you would need to look at
a class can be registered and not exist in the registry
Registration-free COM allows a class to be available without it being registered. Conceptually you don't want to know if a class is "registered", you just want to know it is registered enough to be created.
Unfortunately the only (and best) way to do that is to create it:
//Code released into public domain. No attribution required.
function IsClassRegistered(const ClassID: TGUID): Boolean;
var
unk: IUnknown;
hr: HRESULT;
begin
hr := CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, {out}unk);
unk := nil;
Result := (hr <> REGDB_E_CLASSNOTREG);
end;
ActiveX/COM is a complex beast, registrations have many pieces to them, and Vista+ onward make it more complicated with UAC Registry Virtualization rules.
The best option is to simply attempt to instantiate the OCX and see if it succeeds or fails. That will tell you whether the OCX is registered correctly, all the pieces are hooked up, whether the OCX is even usable within the calling user's context, etc.

Resources